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;
020
021import javax.annotation.concurrent.NotThreadSafe;
022import javax.annotation.concurrent.ThreadSafe;
023import java.util.LinkedHashMap;
024import java.util.Map;
025import java.util.Optional;
026
027import static java.util.Objects.requireNonNull;
028
029/**
030 * Immutable registry used to resolve annotation and programmatic rate-limiter
031 * names while an {@link McpServer} is built.
032 *
033 * @author <a href="https://www.revetkn.com">Mark Allen</a>
034 */
035@ThreadSafe
036public final class McpRateLimiterRegistry {
037        @NonNull
038        private static final McpRateLimiterRegistry EMPTY =
039                        new McpRateLimiterRegistry(Map.of());
040        @NonNull
041        private final Map<@NonNull String, @NonNull McpRateLimiter> rateLimiters;
042
043        /**
044         * Returns the canonical empty registry.
045         *
046         * @return empty registry
047         */
048        @NonNull
049        public static McpRateLimiterRegistry emptyInstance() {
050                return EMPTY;
051        }
052
053        /**
054         * Vends a new registry builder.
055         *
056         * @return registry builder
057         */
058        @NonNull
059        public static Builder builder() {
060                return new Builder();
061        }
062
063        private McpRateLimiterRegistry(
064                        @NonNull Map<@NonNull String, @NonNull McpRateLimiter> rateLimiters) {
065                this.rateLimiters = Map.copyOf(rateLimiters);
066        }
067
068        /**
069         * Returns the immutable name-to-limiter mapping.
070         *
071         * @return registered rate limiters
072         */
073        @NonNull
074        public Map<@NonNull String, @NonNull McpRateLimiter> getRateLimiters() {
075                return this.rateLimiters;
076        }
077
078        /**
079         * Finds a rate limiter by name.
080         *
081         * @param name nonblank limiter name
082         * @return registered limiter, or the empty optional
083         */
084        @NonNull
085        public Optional<@NonNull McpRateLimiter> find(@NonNull String name) {
086                return Optional.ofNullable(this.rateLimiters.get(requireName(name)));
087        }
088
089        @NonNull
090        private static String requireName(@NonNull String name) {
091                McpContentValueSupport.requireWellFormedString(requireNonNull(name));
092                if (name.isBlank())
093                        throw new IllegalArgumentException("Rate-limiter name must not be blank.");
094                return name;
095        }
096
097        /**
098         * Single-threaded builder for immutable rate-limiter registries.
099         *
100         * @author <a href="https://www.revetkn.com">Mark Allen</a>
101         */
102        @NotThreadSafe
103        public static final class Builder {
104                @NonNull
105                private final Map<@NonNull String, @NonNull McpRateLimiter> rateLimiters;
106
107                private Builder() {
108                        this.rateLimiters = new LinkedHashMap<>();
109                }
110
111                /**
112                 * Adds one uniquely named rate limiter.
113                 *
114                 * @param name nonblank limiter name
115                 * @param rateLimiter application-owned limiter
116                 * @return this builder
117                 * @throws IllegalArgumentException if the name is blank or duplicated
118                 */
119                @NonNull
120                public Builder addRateLimiter(@NonNull String name,
121                                @NonNull McpRateLimiter rateLimiter) {
122                        String validatedName = requireName(name);
123                        requireNonNull(rateLimiter);
124                        if (this.rateLimiters.putIfAbsent(validatedName, rateLimiter) != null)
125                                throw new IllegalArgumentException(
126                                                "Duplicate rate limiter: " + validatedName);
127                        return this;
128                }
129
130                /**
131                 * Builds an immutable registry.
132                 *
133                 * @return rate-limiter registry
134                 */
135                @NonNull
136                public McpRateLimiterRegistry build() {
137                        return new McpRateLimiterRegistry(this.rateLimiters);
138                }
139        }
140}