001/*
002 * Copyright 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.soklet;
018
019import org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import java.io.IOException;
023import java.util.Optional;
024
025import static java.lang.String.format;
026import static java.util.Objects.requireNonNull;
027
028/**
029 * Thrown when a response stream has been canceled.
030 *
031 * @author <a href="https://www.revetkn.com">Mark Allen</a>
032 */
033public class StreamingResponseCanceledException extends IOException {
034        private static final long serialVersionUID = 1L;
035
036        @NonNull
037        private final StreamTerminationReason cancelationReason;
038
039        /**
040         * Creates an exception with the given cancelation reason.
041         *
042         * @param cancelationReason the cancelation reason
043         */
044        public StreamingResponseCanceledException(@NonNull StreamTerminationReason cancelationReason) {
045                this(cancelationReason, null);
046        }
047
048        /**
049         * Creates an exception with the given cancelation reason and cause.
050         *
051         * @param cancelationReason the cancelation reason
052         * @param cancelationCause  the underlying cause, or {@code null} if unavailable
053         */
054        public StreamingResponseCanceledException(@NonNull StreamTerminationReason cancelationReason,
055                                                                                                                                                                                @Nullable Throwable cancelationCause) {
056                super(format("Streaming response was canceled: %s", requireNonNull(cancelationReason).name()), cancelationCause);
057                if (cancelationReason == StreamTerminationReason.COMPLETED)
058                        throw new IllegalArgumentException("Cancelation reason cannot be COMPLETED");
059                this.cancelationReason = cancelationReason;
060        }
061
062        /**
063         * The cancelation reason.
064         *
065         * @return the cancelation reason
066         */
067        @NonNull
068        public StreamTerminationReason getCancelationReason() {
069                return this.cancelationReason;
070        }
071
072        /**
073         * The underlying cancelation cause, if available.
074         *
075         * @return the underlying cancelation cause
076         */
077        @NonNull
078        public Optional<Throwable> getCancelationCause() {
079                return Optional.ofNullable(getCause());
080        }
081}