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;
023import java.net.URI;
024
025/**
026 * Immutable application event that identifies a coarse MCP subscription-visible
027 * change.
028 * <p>
029 * Events do not identify an endpoint, authorization partition, or connected
030 * client. Soklet applies endpoint configuration and accepted subscription
031 * filters before wire emission. The authorization partition stored when a
032 * subscription is admitted scopes registration and quota accounting; it is not
033 * an event target and does not authorize catalog or resource access.
034 *
035 * <p>
036 * This type may gain reviewed permitted subtypes in Soklet 4.x; use a forward-compatible default when switching over its values.
037 *
038 * @author <a href="https://www.revetkn.com">Mark Allen</a>
039 */
040@ThreadSafe
041public sealed interface McpSubscriptionEvent
042                permits McpSubscriptionEvent.ResourcesListChanged,
043                McpSubscriptionEvent.ResourceUpdated,
044                McpSubscriptionEvent.ToolsListChanged,
045                McpSubscriptionEvent.PromptsListChanged {
046        /**
047         * Creates a resource-list-changed event.
048         *
049         * @return resource-list-changed event
050         */
051        @NonNull
052        static ResourcesListChanged resourcesListChanged() {
053                return ResourcesListChanged.INSTANCE;
054        }
055
056        /**
057         * Creates a resource-updated event.
058         *
059         * @param resourceUri changed resource URI
060         * @return resource-updated event
061         * @throws NullPointerException if {@code resourceUri} is null
062         * @throws IllegalArgumentException if the URI is relative, not normalized,
063         *                                  or not in ASCII wire form
064         */
065        @NonNull
066        static ResourceUpdated resourceUpdated(@NonNull URI resourceUri) {
067                return new ResourceUpdated(resourceUri);
068        }
069
070        /**
071         * Creates a tool-list-changed event.
072         *
073         * @return tool-list-changed event
074         */
075        @NonNull
076        static ToolsListChanged toolsListChanged() {
077                return ToolsListChanged.INSTANCE;
078        }
079
080        /**
081         * Creates a prompt-list-changed event.
082         *
083         * @return prompt-list-changed event
084         */
085        @NonNull
086        static PromptsListChanged promptsListChanged() {
087                return PromptsListChanged.INSTANCE;
088        }
089
090        /**
091         * Signals that clients should request {@code resources/list} again.
092         *
093         * @author <a href="https://www.revetkn.com">Mark Allen</a>
094         */
095        @ThreadSafe
096        public final class ResourcesListChanged implements McpSubscriptionEvent {
097                @NonNull
098                private static final ResourcesListChanged INSTANCE =
099                                new ResourcesListChanged();
100
101                private ResourcesListChanged() {
102                }
103
104                /** @return whether the other value is also a list-changed event */
105                @Override
106                public boolean equals(@Nullable Object other) {
107                        return other instanceof ResourcesListChanged;
108                }
109
110                /** @return value-based hash code */
111                @Override
112                public int hashCode() {
113                        return 0;
114                }
115
116                /** @return safe diagnostic rendering */
117                @Override
118                @NonNull
119                public String toString() {
120                        return "ResourcesListChanged{}";
121                }
122        }
123
124        /**
125         * Signals that the representation at one absolute normalized resource URI
126         * in ASCII wire form changed.
127         *
128         * @author <a href="https://www.revetkn.com">Mark Allen</a>
129         */
130        @ThreadSafe
131        public final class ResourceUpdated
132                        implements McpSubscriptionEvent {
133                @NonNull
134                private final URI resourceUri;
135
136                /**
137                 * Validates this resource-updated event.
138                 *
139                 * @throws NullPointerException if {@code resourceUri} is null
140                 * @throws IllegalArgumentException if the URI is relative, not normalized,
141                 *                                  or not in ASCII wire form
142                 */
143                private ResourceUpdated(@NonNull URI resourceUri) {
144                        this.resourceUri = McpResourceValueSupport.requireAbsoluteNormalizedUri(
145                                        resourceUri);
146                }
147
148                /** @return absolute normalized changed-resource URI in ASCII wire form */
149                @NonNull
150                public URI getResourceUri() {
151                        return this.resourceUri;
152                }
153
154                /** @return whether this event identifies the same changed resource URI */
155                @Override
156                public boolean equals(@Nullable Object other) {
157                        if (this == other)
158                                return true;
159                        if (!(other instanceof ResourceUpdated updated))
160                                return false;
161                        return this.resourceUri.equals(updated.resourceUri);
162                }
163
164                /** @return value-based hash code */
165                @Override
166                public int hashCode() {
167                        return this.resourceUri.hashCode();
168                }
169
170                /** @return rendering that does not expose the resource URI */
171                @Override
172                @NonNull
173                public final String toString() {
174                        return "ResourceUpdated{resourceUri=<redacted>}";
175                }
176        }
177
178        /**
179         * Requests reevaluation of the caller-visible {@code tools/list} catalog for
180         * interested subscriptions. Publication alone does not assert that the
181         * visible catalog changed.
182         *
183         * @author <a href="https://www.revetkn.com">Mark Allen</a>
184         */
185        @ThreadSafe
186        public final class ToolsListChanged implements McpSubscriptionEvent {
187                @NonNull
188                private static final ToolsListChanged INSTANCE = new ToolsListChanged();
189
190                private ToolsListChanged() {
191                }
192
193                /** @return whether the other value is also a tool-list-changed event */
194                @Override
195                public boolean equals(@Nullable Object other) {
196                        return other instanceof ToolsListChanged;
197                }
198
199                /** @return value-based hash code */
200                @Override
201                public int hashCode() {
202                        return 0;
203                }
204
205                /** @return safe diagnostic rendering */
206                @Override
207                @NonNull
208                public String toString() {
209                        return "ToolsListChanged{}";
210                }
211        }
212
213        /**
214         * Requests reevaluation of the caller-visible {@code prompts/list} catalog for
215         * interested subscriptions. Publication alone does not assert that the
216         * visible catalog changed.
217         *
218         * @author <a href="https://www.revetkn.com">Mark Allen</a>
219         */
220        @ThreadSafe
221        public final class PromptsListChanged implements McpSubscriptionEvent {
222                @NonNull
223                private static final PromptsListChanged INSTANCE =
224                                new PromptsListChanged();
225
226                private PromptsListChanged() {
227                }
228
229                /** @return whether the other value is also a prompt-list-changed event */
230                @Override
231                public boolean equals(@Nullable Object other) {
232                        return other instanceof PromptsListChanged;
233                }
234
235                /** @return value-based hash code */
236                @Override
237                public int hashCode() {
238                        return 0;
239                }
240
241                /** @return safe diagnostic rendering */
242                @Override
243                @NonNull
244                public String toString() {
245                        return "PromptsListChanged{}";
246                }
247        }
248}