-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathJsonRpcRequestConverter.cs
More file actions
188 lines (165 loc) · 7.04 KB
/
JsonRpcRequestConverter.cs
File metadata and controls
188 lines (165 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
using A2A.AspNetCore;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace A2A;
/// <summary>
/// Custom JsonConverter for JsonRpcRequest that validates fields during deserialization.
/// </summary>
internal sealed class JsonRpcRequestConverter : JsonConverter<JsonRpcRequest>
{
/// <summary>
/// The supported JSON-RPC version.
/// </summary>
private const string JsonRpcSupportedVersion = "2.0";
public override JsonRpcRequest? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
try
{
// Create JsonElement from Utf8JsonReader
using var jsonDoc = JsonDocument.ParseValue(ref reader);
var rootElement = jsonDoc.RootElement;
// Validate the JSON-RPC request structure
var idField = ReadAndValidateIdField(rootElement);
var requestId = idField.ToString();
return new JsonRpcRequest
{
Id = idField,
JsonRpc = ReadAndValidateJsonRpcField(rootElement, requestId),
Method = ReadAndValidateMethodField(rootElement, requestId),
Params = ReadAndValidateParamsField(rootElement, requestId)
};
}
catch (JsonException ex)
{
throw new A2AException("Invalid JSON-RPC request payload.", ex, A2AErrorCode.ParseError);
}
}
public override void Write(Utf8JsonWriter writer, JsonRpcRequest value, JsonSerializerOptions options)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value), "Cannot serialize a null JsonRpcRequest.");
}
writer.WriteStartObject();
writer.WriteString("jsonrpc", value.JsonRpc);
writer.WritePropertyName("id");
if (!value.Id.HasValue)
{
writer.WriteNullValue();
}
else if (value.Id.IsString)
{
writer.WriteStringValue(value.Id.AsString());
}
else if (value.Id.IsNumber)
{
writer.WriteNumberValue(value.Id.AsNumber()!.Value);
}
else
{
writer.WriteNullValue();
}
writer.WriteString("method", value.Method);
if (value.Params.HasValue)
{
writer.WritePropertyName("params");
value.Params.Value.WriteTo(writer);
}
writer.WriteEndObject();
}
/// <summary>
/// Reads and validates the 'id' field of a JSON-RPC request.
/// </summary>
/// <param name="rootElement">The root JSON element containing the request.</param>
/// <returns>The extracted request ID as a JsonRpcId.</returns>
private static JsonRpcId ReadAndValidateIdField(JsonElement rootElement)
{
if (rootElement.TryGetProperty("id", out var idElement))
{
if ((idElement.ValueKind != JsonValueKind.String &&
idElement.ValueKind != JsonValueKind.Number &&
idElement.ValueKind != JsonValueKind.Null)
|| (idElement.ValueKind is JsonValueKind.Number && !idElement.TryGetInt64(out var _)))
{
throw new A2AException("Invalid JSON-RPC request: 'id' field must be a string, non-fractional number, or null.", A2AErrorCode.InvalidRequest);
}
return idElement.ValueKind switch
{
JsonValueKind.Null => new JsonRpcId((string?)null),
JsonValueKind.String => new JsonRpcId(idElement.GetString()),
JsonValueKind.Number => new JsonRpcId(idElement.GetInt64()),
_ => new JsonRpcId((string?)null)
};
}
return new JsonRpcId((string?)null);
}
/// <summary>
/// Reads and validates the 'jsonrpc' field of a JSON-RPC request.
/// </summary>
/// <param name="rootElement">The root JSON element containing the request.</param>
/// <param name="requestId">The request ID for error context.</param>
/// <returns>The JSON-RPC version as a string.</returns>
private static string ReadAndValidateJsonRpcField(JsonElement rootElement, string? requestId)
{
if (rootElement.TryGetProperty("jsonrpc", out var jsonRpcElement))
{
var jsonRpc = jsonRpcElement.GetString();
if (jsonRpc != JsonRpcSupportedVersion)
{
throw new A2AException("Invalid JSON-RPC request: 'jsonrpc' field must be '2.0'.", A2AErrorCode.InvalidRequest)
.WithRequestId(requestId);
}
return jsonRpc;
}
throw new A2AException("Invalid JSON-RPC request: missing 'jsonrpc' field.", A2AErrorCode.InvalidRequest)
.WithRequestId(requestId);
}
/// <summary>
/// Reads and validates the 'method' field of a JSON-RPC request.
/// </summary>
/// <param name="rootElement">The root JSON element containing the request.</param>
/// <param name="requestId">The request ID for error context.</param>
/// <returns>The method name as a string.</returns>
private static string ReadAndValidateMethodField(JsonElement rootElement, string? requestId)
{
if (rootElement.TryGetProperty("method", out var methodElement))
{
var method = methodElement.GetString();
if (string.IsNullOrEmpty(method))
{
throw new A2AException("Invalid JSON-RPC request: missing 'method' field.", A2AErrorCode.InvalidRequest)
.WithRequestId(requestId);
}
if (!A2AMethods.IsValidMethod(method!))
{
throw new A2AException("Invalid JSON-RPC request: 'method' field is not a valid A2A method.", A2AErrorCode.MethodNotFound)
.WithRequestId(requestId);
}
return method!;
}
throw new A2AException("Invalid JSON-RPC request: missing 'method' field.", A2AErrorCode.InvalidRequest)
.WithRequestId(requestId);
}
/// <summary>
/// Reads and validates the 'params' field of a JSON-RPC request.
/// </summary>
/// <param name="rootElement">The root JSON element containing the request.</param>
/// <param name="requestId">The request ID for error context.</param>
/// <returns>The 'params' element if it exists and is valid.</returns>
private static JsonElement? ReadAndValidateParamsField(JsonElement rootElement, string? requestId)
{
if (rootElement.TryGetProperty("params", out var paramsElement))
{
if (paramsElement.ValueKind != JsonValueKind.Object &&
paramsElement.ValueKind != JsonValueKind.Undefined &&
paramsElement.ValueKind != JsonValueKind.Null)
{
throw new A2AException("Invalid JSON-RPC request: 'params' field must be an object or null.", A2AErrorCode.InvalidParams)
.WithRequestId(requestId);
}
}
return paramsElement.ValueKind == JsonValueKind.Null || paramsElement.ValueKind == JsonValueKind.Undefined
? null
: paramsElement.Clone();
}
}