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.math.BigInteger;
024import java.util.Objects;
025import java.util.Optional;
026
027import static java.util.Objects.requireNonNull;
028
029/**
030 * An immutable MCP JSON-RPC request identifier.
031 *
032 * @author <a href="https://www.revetkn.com">Mark Allen</a>
033 */
034@ThreadSafe
035public final class McpRequestId {
036        @Nullable
037        private final String stringValue;
038        @Nullable
039        private final BigInteger integerValue;
040
041        /**
042         * Creates a request identifier from its string wire value.
043         *
044         * @param value the string identifier
045         * @return the request identifier
046         */
047        @NonNull
048        public static McpRequestId fromString(@NonNull String value) {
049                return new McpRequestId(McpContentValueSupport.requireWellFormedString(value), null);
050        }
051
052        /**
053         * Creates a request identifier from its integral numeric wire value.
054         *
055         * @param value the integral identifier
056         * @return the request identifier
057         */
058        @NonNull
059        public static McpRequestId fromInteger(@NonNull BigInteger value) {
060                return new McpRequestId(null, requireNonNull(value));
061        }
062
063        private McpRequestId(@Nullable String stringValue, @Nullable BigInteger integerValue) {
064                this.stringValue = stringValue;
065                this.integerValue = integerValue;
066        }
067
068        /**
069         * Returns the string representation when this is a string request ID.
070         *
071         * @return the string value, or the empty optional for an integer request ID
072         */
073        @NonNull
074        public Optional<@NonNull String> asString() {
075                return Optional.ofNullable(this.stringValue);
076        }
077
078        /**
079         * Returns the numeric representation when this is an integer request ID.
080         *
081         * @return the integer value, or the empty optional for a string request ID
082         */
083        @NonNull
084        public Optional<@NonNull BigInteger> asInteger() {
085                return Optional.ofNullable(this.integerValue);
086        }
087
088        /**
089         * Compares request IDs by wire type and value.
090         *
091         * @param object the object to compare
092         * @return {@code true} if the object is an equal request ID
093         */
094        @Override
095        public boolean equals(@Nullable Object object) {
096                if (this == object)
097                        return true;
098                if (!(object instanceof McpRequestId other))
099                        return false;
100                return Objects.equals(this.stringValue, other.stringValue)
101                                && Objects.equals(this.integerValue, other.integerValue);
102        }
103
104        /**
105         * Returns a hash code derived from the request ID's wire type and value.
106         *
107         * @return the request ID hash code
108         */
109        @Override
110        public int hashCode() {
111                return Objects.hash(this.stringValue, this.integerValue);
112        }
113
114        /**
115         * Returns a redacted diagnostic representation that preserves only the MCP
116         * wire type. Request identifiers are client-supplied and may contain secrets
117         * or high-cardinality values; use {@link #asString()} or
118         * {@link #asInteger()} only when exact application processing requires
119         * them.
120         *
121         * @return the safe diagnostic representation
122         */
123        @Override
124        @NonNull
125        public String toString() {
126                return this.integerValue == null
127                                ? "McpRequestId{string=<redacted>}"
128                                : "McpRequestId{integer=<redacted>}";
129        }
130}