001/*
002 * Copyright 2015-2022 Transmogrify LLC, 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.pyranid;
018
019import org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import javax.annotation.concurrent.ThreadSafe;
023import java.util.List;
024import java.util.Optional;
025
026import static java.util.Objects.requireNonNull;
027
028/**
029 * Result of a successful {@link Database#transactionWithRetry(RetryPolicy, ReturningTransactionalOperation)} call.
030 * <p>
031 * Retry methods return this type instead of a bare {@link Optional} so callers can inspect failures that were recovered
032 * before the transaction eventually succeeded.
033 * <p>
034 * If retry attempts are exhausted, Pyranid throws the final {@link DatabaseException} instead of returning this type. Prior
035 * failed attempts are attached to the thrown exception as suppressed exceptions.
036 *
037 * @author <a href="https://www.revetkn.com">Mark Allen</a>
038 * @since 4.4.0
039 */
040@ThreadSafe
041public final class TransactionRetryResult<T> {
042        @Nullable
043        private final T value;
044        @NonNull
045        private final List<@NonNull DatabaseException> failures;
046
047        private TransactionRetryResult(@Nullable T value,
048                                                                                                                                 @NonNull List<@NonNull DatabaseException> failures) {
049                requireNonNull(failures);
050
051                this.value = value;
052                this.failures = List.copyOf(failures);
053        }
054
055        @NonNull
056        static <T> TransactionRetryResult<T> of(@NonNull Optional<T> value,
057                                                                                                                                                                        @NonNull List<@NonNull DatabaseException> failures) {
058                requireNonNull(value);
059                requireNonNull(failures);
060
061                return new TransactionRetryResult<>(value.orElse(null), failures);
062        }
063
064        /**
065         * Gets the value returned by the successful transaction attempt.
066         *
067         * @return successful transaction value, or empty if no value was returned
068         */
069        @NonNull
070        public Optional<T> getValue() {
071                return Optional.ofNullable(this.value);
072        }
073
074        /**
075         * Gets the failed attempts that were retried before the transaction eventually succeeded.
076         * <p>
077         * Failures are returned in occurrence order. The returned list is immutable.
078         *
079         * @return failed attempts that were retried before success
080         */
081        @NonNull
082        public List<@NonNull DatabaseException> getFailures() {
083                return this.failures;
084        }
085
086        /**
087         * Gets the number of transaction attempts, including the successful final attempt.
088         *
089         * @return attempt count
090         */
091        @NonNull
092        public Integer getAttemptCount() {
093                return getFailures().size() + 1;
094        }
095
096        /**
097         * Indicates whether at least one failed attempt was retried before success.
098         *
099         * @return {@code true} if the transaction succeeded after retrying, {@code false} otherwise
100         */
101        @NonNull
102        public Boolean wasRetried() {
103                return !getFailures().isEmpty();
104        }
105}