turul_mcp_protocol_2025_06_18/
schema.rs1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8pub trait JsonSchemaGenerator {
10 fn json_schema() -> crate::tools::ToolSchema;
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(tag = "type", rename_all = "lowercase")]
17pub enum JsonSchema {
18 String {
20 #[serde(skip_serializing_if = "Option::is_none")]
21 description: Option<String>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pattern: Option<String>,
24 #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
25 min_length: Option<u64>,
26 #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
27 max_length: Option<u64>,
28 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
29 enum_values: Option<Vec<String>>,
30 },
31 Number {
33 #[serde(skip_serializing_if = "Option::is_none")]
34 description: Option<String>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 minimum: Option<f64>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 maximum: Option<f64>,
39 },
40 Integer {
42 #[serde(skip_serializing_if = "Option::is_none")]
43 description: Option<String>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 minimum: Option<i64>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 maximum: Option<i64>,
48 },
49 Boolean {
51 #[serde(skip_serializing_if = "Option::is_none")]
52 description: Option<String>,
53 },
54 Array {
56 #[serde(skip_serializing_if = "Option::is_none")]
57 description: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 items: Option<Box<JsonSchema>>,
60 #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
61 min_items: Option<u64>,
62 #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
63 max_items: Option<u64>,
64 },
65 Object {
67 #[serde(skip_serializing_if = "Option::is_none")]
68 description: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 properties: Option<HashMap<String, JsonSchema>>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 required: Option<Vec<String>>,
73 #[serde(
74 rename = "additionalProperties",
75 skip_serializing_if = "Option::is_none"
76 )]
77 additional_properties: Option<bool>,
78 },
79}
80
81impl JsonSchema {
82 pub fn string() -> Self {
84 Self::String {
85 description: None,
86 pattern: None,
87 min_length: None,
88 max_length: None,
89 enum_values: None,
90 }
91 }
92
93 pub fn string_with_description(description: impl Into<String>) -> Self {
95 Self::String {
96 description: Some(description.into()),
97 pattern: None,
98 min_length: None,
99 max_length: None,
100 enum_values: None,
101 }
102 }
103
104 pub fn string_enum(values: Vec<String>) -> Self {
106 Self::String {
107 description: None,
108 pattern: None,
109 min_length: None,
110 max_length: None,
111 enum_values: Some(values),
112 }
113 }
114
115 pub fn number() -> Self {
117 Self::Number {
118 description: None,
119 minimum: None,
120 maximum: None,
121 }
122 }
123
124 pub fn number_with_description(description: impl Into<String>) -> Self {
126 Self::Number {
127 description: Some(description.into()),
128 minimum: None,
129 maximum: None,
130 }
131 }
132
133 pub fn integer() -> Self {
135 Self::Integer {
136 description: None,
137 minimum: None,
138 maximum: None,
139 }
140 }
141
142 pub fn integer_with_description(description: impl Into<String>) -> Self {
144 Self::Integer {
145 description: Some(description.into()),
146 minimum: None,
147 maximum: None,
148 }
149 }
150
151 pub fn boolean() -> Self {
153 Self::Boolean { description: None }
154 }
155
156 pub fn boolean_with_description(description: impl Into<String>) -> Self {
158 Self::Boolean {
159 description: Some(description.into()),
160 }
161 }
162
163 pub fn array(items: JsonSchema) -> Self {
165 Self::Array {
166 description: None,
167 items: Some(Box::new(items)),
168 min_items: None,
169 max_items: None,
170 }
171 }
172
173 pub fn array_with_description(items: JsonSchema, description: impl Into<String>) -> Self {
175 Self::Array {
176 description: Some(description.into()),
177 items: Some(Box::new(items)),
178 min_items: None,
179 max_items: None,
180 }
181 }
182
183 pub fn object() -> Self {
185 Self::Object {
186 description: None,
187 properties: None,
188 required: None,
189 additional_properties: None,
190 }
191 }
192
193 pub fn object_with_properties(properties: HashMap<String, JsonSchema>) -> Self {
195 Self::Object {
196 description: None,
197 properties: Some(properties),
198 required: None,
199 additional_properties: None,
200 }
201 }
202
203 pub fn object_with_required(
205 properties: HashMap<String, JsonSchema>,
206 required: Vec<String>,
207 ) -> Self {
208 Self::Object {
209 description: None,
210 properties: Some(properties),
211 required: Some(required),
212 additional_properties: None,
213 }
214 }
215
216 pub fn with_description(mut self, description: impl Into<String>) -> Self {
218 match &mut self {
219 JsonSchema::String { description: d, .. } => *d = Some(description.into()),
220 JsonSchema::Number { description: d, .. } => *d = Some(description.into()),
221 JsonSchema::Integer { description: d, .. } => *d = Some(description.into()),
222 JsonSchema::Boolean { description: d, .. } => *d = Some(description.into()),
223 JsonSchema::Array { description: d, .. } => *d = Some(description.into()),
224 JsonSchema::Object { description: d, .. } => *d = Some(description.into()),
225 }
226 self
227 }
228
229 pub fn with_minimum(mut self, minimum: f64) -> Self {
231 match &mut self {
232 JsonSchema::Number { minimum: m, .. } => *m = Some(minimum),
233 JsonSchema::Integer { minimum: m, .. } => *m = Some(minimum as i64),
234 _ => {} }
236 self
237 }
238
239 pub fn with_maximum(mut self, maximum: f64) -> Self {
241 match &mut self {
242 JsonSchema::Number { maximum: m, .. } => *m = Some(maximum),
243 JsonSchema::Integer { maximum: m, .. } => *m = Some(maximum as i64),
244 _ => {} }
246 self
247 }
248
249 pub fn with_properties(mut self, properties: HashMap<String, JsonSchema>) -> Self {
251 if let JsonSchema::Object { properties: p, .. } = &mut self {
252 *p = Some(properties);
253 }
254 self
255 }
256
257 pub fn with_required(mut self, required: Vec<String>) -> Self {
259 if let JsonSchema::Object { required: r, .. } = &mut self {
260 *r = Some(required);
261 }
262 self
263 }
264}
265
266pub trait ToJsonSchema {
268 fn to_json_schema() -> JsonSchema;
269}
270
271impl ToJsonSchema for String {
272 fn to_json_schema() -> JsonSchema {
273 JsonSchema::string()
274 }
275}
276
277impl ToJsonSchema for &str {
278 fn to_json_schema() -> JsonSchema {
279 JsonSchema::string()
280 }
281}
282
283impl ToJsonSchema for i32 {
284 fn to_json_schema() -> JsonSchema {
285 JsonSchema::integer()
286 }
287}
288
289impl ToJsonSchema for i64 {
290 fn to_json_schema() -> JsonSchema {
291 JsonSchema::integer()
292 }
293}
294
295impl ToJsonSchema for f32 {
296 fn to_json_schema() -> JsonSchema {
297 JsonSchema::number()
298 }
299}
300
301impl ToJsonSchema for f64 {
302 fn to_json_schema() -> JsonSchema {
303 JsonSchema::number()
304 }
305}
306
307impl ToJsonSchema for bool {
308 fn to_json_schema() -> JsonSchema {
309 JsonSchema::boolean()
310 }
311}
312
313impl<T: ToJsonSchema> ToJsonSchema for Vec<T> {
314 fn to_json_schema() -> JsonSchema {
315 JsonSchema::array(T::to_json_schema())
316 }
317}
318
319impl<T: ToJsonSchema> ToJsonSchema for Option<T> {
320 fn to_json_schema() -> JsonSchema {
321 T::to_json_schema()
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_string_schema() {
331 let schema = JsonSchema::string_with_description("A test string");
332 let json = serde_json::to_string(&schema).unwrap();
333 assert!(json.contains("string"));
334 assert!(json.contains("A test string"));
335 }
336
337 #[test]
338 fn test_object_schema() {
339 let mut properties = HashMap::new();
340 properties.insert("name".to_string(), JsonSchema::string());
341 properties.insert("age".to_string(), JsonSchema::integer());
342
343 let schema = JsonSchema::object_with_required(properties, vec!["name".to_string()]);
344
345 let json = serde_json::to_string(&schema).unwrap();
346 assert!(json.contains("object"));
347 assert!(json.contains("name"));
348 assert!(json.contains("age"));
349 }
350
351 #[test]
352 fn test_array_schema() {
353 let schema = JsonSchema::array(JsonSchema::string());
354 let json = serde_json::to_string(&schema).unwrap();
355 assert!(json.contains("array"));
356 }
357
358 #[test]
359 fn test_enum_schema() {
360 let schema = JsonSchema::string_enum(vec!["option1".to_string(), "option2".to_string()]);
361 let json = serde_json::to_string(&schema).unwrap();
362 assert!(json.contains("option1"));
363 assert!(json.contains("option2"));
364 }
365
366 #[test]
367 fn test_to_json_schema_trait() {
368 assert!(matches!(
369 String::to_json_schema(),
370 JsonSchema::String { .. }
371 ));
372 assert!(matches!(i32::to_json_schema(), JsonSchema::Integer { .. }));
373 assert!(matches!(f64::to_json_schema(), JsonSchema::Number { .. }));
374 assert!(matches!(bool::to_json_schema(), JsonSchema::Boolean { .. }));
375 }
376}