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 javax.annotation.concurrent.ThreadSafe;
023
024import static java.util.Objects.requireNonNull;
025
026/**
027 * Immutable accepted or rejected MCP admission decision. Decision
028 * carriers are safe for concurrent access; application-owned values reachable
029 * through an accepted identity retain their own thread-safety contracts.
030 *
031 * <p>
032 * This is a closed domain; exhaustive switches are supported, and any new alternative requires an explicit reviewed contract amendment.
033 *
034 * @author <a href="https://www.revetkn.com">Mark Allen</a>
035 */
036@ThreadSafe
037public sealed interface McpAdmissionDecision permits McpAdmissionDecision.Accepted,
038                McpAdmissionDecision.Rejected {
039        /**
040         * Creates an accepted decision carrying an application identity.
041         *
042         * @param identity admitted identity
043         * @return accepted decision
044         */
045        @NonNull
046        static Accepted accepted(@NonNull McpAdmissionIdentity identity) {
047                return new Accepted(identity);
048        }
049
050        /**
051         * Creates an accepted decision carrying the canonical anonymous identity.
052         *
053         * @return anonymous accepted decision
054         */
055        @NonNull
056        static Accepted accepted() {
057                return accepted(McpAdmissionIdentity.anonymousInstance());
058        }
059
060        /**
061         * Creates a rejected decision.
062         *
063         * @param rejection safe client-visible rejection
064         * @return rejected decision
065         */
066        @NonNull
067        static Rejected rejected(@NonNull McpAdmissionRejection rejection) {
068                return new Rejected(rejection);
069        }
070
071        /**
072         * Accepted admission decision carrying an admitted identity.
073         *
074         * @author <a href="https://www.revetkn.com">Mark Allen</a>
075         */
076        @ThreadSafe
077        public final class Accepted implements McpAdmissionDecision {
078                @NonNull
079                private final McpAdmissionIdentity identity;
080
081                /**
082                 * Creates an accepted decision.
083                 *
084                 * @param identity admitted identity
085                 */
086                private Accepted(@NonNull McpAdmissionIdentity identity) {
087                        this.identity = requireNonNull(identity);
088                }
089
090                /** @return admitted identity */
091                @NonNull
092                public McpAdmissionIdentity getIdentity() {
093                        return this.identity;
094                }
095
096                /** @return whether this decision carries the same admitted identity */
097                @Override
098                public boolean equals(@Nullable Object other) {
099                        if (this == other)
100                                return true;
101                        if (!(other instanceof Accepted accepted))
102                                return false;
103                        return this.identity.equals(accepted.identity);
104                }
105
106                /** @return value-based hash code */
107                @Override
108                public int hashCode() {
109                        return this.identity.hashCode();
110                }
111
112                /** @return redacted diagnostic rendering */
113                @Override
114                @NonNull
115                public String toString() {
116                        return "Accepted{identity=<redacted>}";
117                }
118        }
119
120        /**
121         * Rejected admission decision carrying a safe client-visible rejection.
122         *
123         * @author <a href="https://www.revetkn.com">Mark Allen</a>
124         */
125        @ThreadSafe
126        public final class Rejected implements McpAdmissionDecision {
127                @NonNull
128                private final McpAdmissionRejection rejection;
129
130                /**
131                 * Creates a rejected decision.
132                 *
133                 * @param rejection safe client-visible rejection
134                 */
135                private Rejected(@NonNull McpAdmissionRejection rejection) {
136                        this.rejection = requireNonNull(rejection);
137                }
138
139                /** @return safe client-visible rejection */
140                @NonNull
141                public McpAdmissionRejection getRejection() {
142                        return this.rejection;
143                }
144
145                /** @return whether this decision carries the same rejection */
146                @Override
147                public boolean equals(@Nullable Object other) {
148                        if (this == other)
149                                return true;
150                        if (!(other instanceof Rejected rejected))
151                                return false;
152                        return this.rejection.equals(rejected.rejection);
153                }
154
155                /** @return value-based hash code */
156                @Override
157                public int hashCode() {
158                        return this.rejection.hashCode();
159                }
160
161                /** @return redacted diagnostic rendering */
162                @Override
163                @NonNull
164                public String toString() {
165                        return "Rejected{rejection=<redacted>}";
166                }
167        }
168}