1use serde::Deserialize;
2use serde::Serialize;
3use serde_json::Value as JsonValue;
4use serde_json::json;
5use std::collections::BTreeMap;
6use std::collections::BTreeSet;
7
8const DEFINITION_TABLE_KEYS: [&str; 2] = ["$defs", "definitions"];
9const SCHEMA_CHILD_KEYS: [&str; 4] = ["items", "anyOf", "oneOf", "allOf"];
10const COMPOSITION_SCHEMA_KEYS: [&str; 3] = ["anyOf", "oneOf", "allOf"];
11
12#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
20#[serde(rename_all = "lowercase")]
21pub enum JsonSchemaPrimitiveType {
22 String,
23 Number,
24 Boolean,
25 Integer,
26 Object,
27 Array,
28 Null,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(untagged)]
34pub enum JsonSchemaType {
35 Single(JsonSchemaPrimitiveType),
36 Multiple(Vec<JsonSchemaPrimitiveType>),
37}
38
39#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
41pub struct JsonSchema {
42 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
43 pub schema_ref: Option<String>,
44 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
45 pub schema_type: Option<JsonSchemaType>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub description: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub encrypted: Option<bool>,
51 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
52 pub enum_values: Option<Vec<JsonValue>>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub items: Option<Box<JsonSchema>>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub properties: Option<BTreeMap<String, JsonSchema>>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub required: Option<Vec<String>>,
59 #[serde(
60 rename = "additionalProperties",
61 skip_serializing_if = "Option::is_none"
62 )]
63 pub additional_properties: Option<AdditionalProperties>,
64 #[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
65 pub any_of: Option<Vec<JsonSchema>>,
66 #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
67 pub one_of: Option<Vec<JsonSchema>>,
68 #[serde(rename = "allOf", skip_serializing_if = "Option::is_none")]
69 pub all_of: Option<Vec<JsonSchema>>,
70 #[serde(rename = "$defs", skip_serializing_if = "Option::is_none")]
71 pub defs: Option<BTreeMap<String, JsonSchema>>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub definitions: Option<BTreeMap<String, JsonSchema>>,
74}
75
76impl JsonSchema {
77 fn typed(schema_type: JsonSchemaPrimitiveType, description: Option<String>) -> Self {
79 Self {
80 schema_type: Some(JsonSchemaType::Single(schema_type)),
81 description,
82 ..Default::default()
83 }
84 }
85
86 pub fn any_of(variants: Vec<JsonSchema>, description: Option<String>) -> Self {
87 Self {
88 description,
89 any_of: Some(variants),
90 ..Default::default()
91 }
92 }
93
94 pub fn one_of(variants: Vec<JsonSchema>, description: Option<String>) -> Self {
95 Self {
96 description,
97 one_of: Some(variants),
98 ..Default::default()
99 }
100 }
101
102 pub fn all_of(variants: Vec<JsonSchema>, description: Option<String>) -> Self {
103 Self {
104 description,
105 all_of: Some(variants),
106 ..Default::default()
107 }
108 }
109
110 pub fn boolean(description: Option<String>) -> Self {
111 Self::typed(JsonSchemaPrimitiveType::Boolean, description)
112 }
113
114 pub fn string(description: Option<String>) -> Self {
115 Self::typed(JsonSchemaPrimitiveType::String, description)
116 }
117
118 pub fn with_encrypted(mut self) -> Self {
119 self.encrypted = Some(true);
120 self
121 }
122
123 pub fn number(description: Option<String>) -> Self {
124 Self::typed(JsonSchemaPrimitiveType::Number, description)
125 }
126
127 pub fn integer(description: Option<String>) -> Self {
128 Self::typed(JsonSchemaPrimitiveType::Integer, description)
129 }
130
131 pub fn null(description: Option<String>) -> Self {
132 Self::typed(JsonSchemaPrimitiveType::Null, description)
133 }
134
135 pub fn string_enum(values: Vec<JsonValue>, description: Option<String>) -> Self {
136 Self {
137 schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::String)),
138 description,
139 enum_values: Some(values),
140 ..Default::default()
141 }
142 }
143
144 pub fn array(items: JsonSchema, description: Option<String>) -> Self {
145 Self {
146 schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Array)),
147 description,
148 items: Some(Box::new(items)),
149 ..Default::default()
150 }
151 }
152
153 pub fn object(
154 properties: BTreeMap<String, JsonSchema>,
155 required: Option<Vec<String>>,
156 additional_properties: Option<AdditionalProperties>,
157 ) -> Self {
158 Self {
159 schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)),
160 properties: Some(properties),
161 required,
162 additional_properties,
163 ..Default::default()
164 }
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170#[serde(untagged)]
171pub enum AdditionalProperties {
172 Boolean(bool),
173 Schema(Box<JsonSchema>),
174}
175
176impl From<bool> for AdditionalProperties {
177 fn from(value: bool) -> Self {
178 Self::Boolean(value)
179 }
180}
181
182impl From<JsonSchema> for AdditionalProperties {
183 fn from(value: JsonSchema) -> Self {
184 Self::Schema(Box::new(value))
185 }
186}
187
188pub fn parse_tool_input_schema(input_schema: &JsonValue) -> Result<JsonSchema, serde_json::Error> {
190 let mut input_schema = prepare_tool_input_schema(input_schema);
191 compact_large_tool_schema(&mut input_schema);
192 deserialize_tool_input_schema(input_schema)
193}
194
195pub fn parse_tool_input_schema_without_compaction(
197 input_schema: &JsonValue,
198) -> Result<JsonSchema, serde_json::Error> {
199 deserialize_tool_input_schema(prepare_tool_input_schema(input_schema))
200}
201
202fn prepare_tool_input_schema(input_schema: &JsonValue) -> JsonValue {
203 let mut input_schema = input_schema.clone();
204 sanitize_json_schema(&mut input_schema);
205 prune_unreachable_definitions(&mut input_schema);
206 input_schema
207}
208
209fn deserialize_tool_input_schema(input_schema: JsonValue) -> Result<JsonSchema, serde_json::Error> {
210 let schema: JsonSchema = serde_json::from_value(input_schema)?;
211 if matches!(
212 schema.schema_type,
213 Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Null))
214 ) {
215 return Err(singleton_null_schema_error());
216 }
217 Ok(schema)
218}
219
220const MAX_COMPACT_TOOL_SCHEMA_BYTES: usize = 5_000;
223const MAX_COMPACT_TOOL_SCHEMA_DEPTH: usize = 3;
224
225fn compact_large_tool_schema(value: &mut JsonValue) {
230 for pass in LARGE_SCHEMA_COMPACTION_PASSES {
231 if compact_schema_fits_budget(value) {
232 break;
233 }
234 pass(value);
235 }
236}
237
238type LargeSchemaCompactionPass = fn(&mut JsonValue);
239
240const LARGE_SCHEMA_COMPACTION_PASSES: &[LargeSchemaCompactionPass] = &[
241 strip_schema_descriptions,
242 drop_schema_definitions,
243 collapse_deep_schema_objects_from_root,
244 prune_schema_compositions,
245];
246
247fn collapse_deep_schema_objects_from_root(value: &mut JsonValue) {
248 collapse_deep_schema_objects(value, 0);
249}
250
251fn compact_schema_fits_budget(value: &JsonValue) -> bool {
252 compact_normalized_schema_len(value) <= MAX_COMPACT_TOOL_SCHEMA_BYTES
253}
254
255fn compact_normalized_schema_len(value: &JsonValue) -> usize {
256 serde_json::from_value::<JsonSchema>(value.clone())
257 .and_then(|schema| serde_json::to_vec(&schema))
258 .map(|json| json.len())
259 .unwrap_or(0)
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263enum DefinitionTraversal {
264 Include,
265 Skip,
266}
267
268fn for_each_schema_child(
269 map: &serde_json::Map<String, JsonValue>,
270 definition_traversal: DefinitionTraversal,
271 visitor: &mut impl FnMut(&JsonValue),
272) {
273 if let Some(properties) = map.get("properties")
274 && let Some(properties_map) = properties.as_object()
275 {
276 for value in properties_map.values() {
277 visitor(value);
278 }
279 }
280
281 for key in SCHEMA_CHILD_KEYS {
282 if let Some(value) = map.get(key) {
283 visitor(value);
284 }
285 }
286
287 if let Some(additional_properties) = map.get("additionalProperties")
288 && !matches!(additional_properties, JsonValue::Bool(_))
289 {
290 visitor(additional_properties);
291 }
292
293 if definition_traversal == DefinitionTraversal::Include {
294 for key in DEFINITION_TABLE_KEYS {
295 if let Some(definitions) = map.get(key)
296 && let Some(definitions_map) = definitions.as_object()
297 {
298 for value in definitions_map.values() {
299 visitor(value);
300 }
301 }
302 }
303 }
304}
305
306fn strip_schema_descriptions(value: &mut JsonValue) {
307 match value {
308 JsonValue::Array(values) => {
309 for value in values {
310 strip_schema_descriptions(value);
311 }
312 }
313 JsonValue::Object(map) => {
314 map.remove("description");
315 for_each_schema_child_mut(map, DefinitionTraversal::Include, &mut |value| {
316 strip_schema_descriptions(value);
317 });
318 }
319 _ => {}
320 }
321}
322
323fn for_each_schema_child_mut(
324 map: &mut serde_json::Map<String, JsonValue>,
325 definition_traversal: DefinitionTraversal,
326 visitor: &mut impl FnMut(&mut JsonValue),
327) {
328 if let Some(properties) = map.get_mut("properties")
329 && let Some(properties_map) = properties.as_object_mut()
330 {
331 for value in properties_map.values_mut() {
332 visitor(value);
333 }
334 }
335
336 for key in SCHEMA_CHILD_KEYS {
337 if let Some(value) = map.get_mut(key) {
338 visitor(value);
339 }
340 }
341
342 if let Some(additional_properties) = map.get_mut("additionalProperties")
343 && !matches!(additional_properties, JsonValue::Bool(_))
344 {
345 visitor(additional_properties);
346 }
347
348 if definition_traversal == DefinitionTraversal::Include {
349 for key in DEFINITION_TABLE_KEYS {
350 if let Some(definitions) = map.get_mut(key)
351 && let Some(definitions_map) = definitions.as_object_mut()
352 {
353 for value in definitions_map.values_mut() {
354 visitor(value);
355 }
356 }
357 }
358 }
359}
360
361fn drop_schema_definitions(value: &mut JsonValue) {
365 rewrite_definition_refs_to_empty_schemas(value);
366
367 let JsonValue::Object(map) = value else {
368 return;
369 };
370
371 for key in DEFINITION_TABLE_KEYS {
372 map.remove(key);
373 }
374}
375
376fn rewrite_definition_refs_to_empty_schemas(value: &mut JsonValue) {
377 match value {
378 JsonValue::Array(values) => {
379 for value in values {
380 rewrite_definition_refs_to_empty_schemas(value);
381 }
382 }
383 JsonValue::Object(map) => {
384 if map
385 .get("$ref")
386 .and_then(JsonValue::as_str)
387 .and_then(parse_local_definition_ref)
388 .is_some()
389 {
390 *value = json!({});
391 return;
392 }
393
394 for_each_schema_child_mut(map, DefinitionTraversal::Skip, &mut |value| {
395 rewrite_definition_refs_to_empty_schemas(value);
396 });
397 }
398 _ => {}
399 }
400}
401
402fn collapse_deep_schema_objects(value: &mut JsonValue, depth: usize) {
403 match value {
404 JsonValue::Array(values) => {
405 for value in values {
406 collapse_deep_schema_objects(value, depth);
407 }
408 }
409 JsonValue::Object(map) => {
410 if depth >= MAX_COMPACT_TOOL_SCHEMA_DEPTH && is_complex_schema_object(map) {
411 *value = json!({});
412 return;
413 }
414
415 for_each_schema_child_mut(map, DefinitionTraversal::Skip, &mut |value| {
416 collapse_deep_schema_objects(value, depth + 1);
417 });
418 }
419 _ => {}
420 }
421}
422
423fn prune_schema_compositions(value: &mut JsonValue) {
424 match value {
425 JsonValue::Array(values) => {
426 for value in values {
427 prune_schema_compositions(value);
428 }
429 }
430 JsonValue::Object(map) => {
431 if has_composition_keyword(map) {
432 *value = json!({});
433 return;
434 }
435
436 for_each_schema_child_mut(map, DefinitionTraversal::Skip, &mut |value| {
437 prune_schema_compositions(value);
438 });
439 }
440 _ => {}
441 }
442}
443
444fn is_complex_schema_object(map: &serde_json::Map<String, JsonValue>) -> bool {
445 SCHEMA_CHILD_KEYS.iter().any(|key| map.contains_key(*key))
446 || map.contains_key("properties")
447 || map.contains_key("additionalProperties")
448 || map.contains_key("$ref")
449}
450
451fn has_composition_keyword(map: &serde_json::Map<String, JsonValue>) -> bool {
452 COMPOSITION_SCHEMA_KEYS
453 .into_iter()
454 .any(|key| map.contains_key(key))
455}
456
457fn sanitize_json_schema(value: &mut JsonValue) {
467 match value {
468 JsonValue::Bool(_) => {
469 *value = json!({ "type": "string" });
471 }
472 JsonValue::Array(values) => {
473 for value in values {
474 sanitize_json_schema(value);
475 }
476 }
477 JsonValue::Object(map) => {
478 if let Some(properties) = map.get_mut("properties")
479 && let Some(properties_map) = properties.as_object_mut()
480 {
481 for value in properties_map.values_mut() {
482 sanitize_json_schema(value);
483 }
484 }
485 if let Some(items) = map.get_mut("items") {
486 sanitize_json_schema(items);
487 }
488 if let Some(additional_properties) = map.get_mut("additionalProperties")
489 && !matches!(additional_properties, JsonValue::Bool(_))
490 {
491 sanitize_json_schema(additional_properties);
492 }
493 if let Some(value) = map.get_mut("prefixItems") {
494 sanitize_json_schema(value);
495 }
496 for key in COMPOSITION_SCHEMA_KEYS {
497 if let Some(value) = map.get_mut(key) {
498 sanitize_json_schema(value);
499 }
500 }
501 for table in DEFINITION_TABLE_KEYS {
502 sanitize_schema_table(map, table);
503 }
504
505 if let Some(const_value) = map.remove("const") {
506 map.insert("enum".to_string(), JsonValue::Array(vec![const_value]));
507 }
508
509 let mut schema_types = normalized_schema_types(map);
510
511 if schema_types.is_empty() && (map.contains_key("$ref") || has_composition_keyword(map))
512 {
513 return;
514 }
515
516 if schema_types.is_empty() {
517 if map.contains_key("properties")
518 || map.contains_key("required")
519 || map.contains_key("additionalProperties")
520 {
521 schema_types.push(JsonSchemaPrimitiveType::Object);
522 } else if map.contains_key("items") || map.contains_key("prefixItems") {
523 schema_types.push(JsonSchemaPrimitiveType::Array);
524 } else if map.contains_key("enum") || map.contains_key("format") {
525 schema_types.push(JsonSchemaPrimitiveType::String);
526 } else if map.contains_key("minimum")
527 || map.contains_key("maximum")
528 || map.contains_key("exclusiveMinimum")
529 || map.contains_key("exclusiveMaximum")
530 || map.contains_key("multipleOf")
531 {
532 schema_types.push(JsonSchemaPrimitiveType::Number);
533 } else {
534 map.clear();
535 return;
536 }
537 }
538
539 write_schema_types(map, &schema_types);
540 ensure_default_children_for_schema_types(map, &schema_types);
541 }
542 _ => {}
543 }
544}
545
546fn sanitize_schema_table(map: &mut serde_json::Map<String, JsonValue>, key: &str) {
553 let should_remove = match map.get_mut(key) {
554 Some(JsonValue::Object(definitions)) => {
555 for definition in definitions.values_mut() {
556 sanitize_json_schema(definition);
557 }
558 false
559 }
560 Some(_) => true,
561 None => false,
562 };
563
564 if should_remove {
565 map.remove(key);
566 }
567}
568
569fn ensure_default_children_for_schema_types(
570 map: &mut serde_json::Map<String, JsonValue>,
571 schema_types: &[JsonSchemaPrimitiveType],
572) {
573 if schema_types.contains(&JsonSchemaPrimitiveType::Object) && !map.contains_key("properties") {
574 map.insert(
575 "properties".to_string(),
576 JsonValue::Object(serde_json::Map::new()),
577 );
578 }
579
580 if schema_types.contains(&JsonSchemaPrimitiveType::Array) && !map.contains_key("items") {
581 map.insert("items".to_string(), json!({ "type": "string" }));
582 }
583}
584
585#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
586struct DefinitionPointer {
587 table: &'static str,
588 name: String,
589}
590
591fn prune_unreachable_definitions(value: &mut JsonValue) {
594 let reachable = collect_reachable_definitions(value);
595 let JsonValue::Object(map) = value else {
596 return;
597 };
598
599 for table in DEFINITION_TABLE_KEYS {
600 prune_schema_table(map, table, &reachable);
601 }
602}
603
604fn prune_schema_table(
605 map: &mut serde_json::Map<String, JsonValue>,
606 table: &'static str,
607 reachable: &BTreeSet<DefinitionPointer>,
608) {
609 let Some(JsonValue::Object(definitions)) = map.get_mut(table) else {
610 return;
611 };
612
613 definitions.retain(|name, _| {
614 reachable.contains(&DefinitionPointer {
615 table,
616 name: name.clone(),
617 })
618 });
619
620 if definitions.is_empty() {
621 map.remove(table);
622 }
623}
624
625fn collect_reachable_definitions(value: &JsonValue) -> BTreeSet<DefinitionPointer> {
626 let mut reachable = BTreeSet::new();
627 let mut pending = Vec::new();
628
629 collect_refs_outside_definitions(value, &mut pending);
630
631 while let Some(pointer) = pending.pop() {
632 if !reachable.insert(pointer.clone()) {
633 continue;
634 }
635
636 if let Some(definition) = definition_for_pointer(value, &pointer) {
637 collect_refs(definition, &mut pending);
638 }
639 }
640
641 reachable
642}
643
644fn collect_refs_outside_definitions(value: &JsonValue, refs: &mut Vec<DefinitionPointer>) {
645 match value {
646 JsonValue::Array(values) => {
647 for value in values {
648 collect_refs_outside_definitions(value, refs);
649 }
650 }
651 JsonValue::Object(map) => {
652 collect_ref_from_map(map, refs);
653 for_each_schema_child(map, DefinitionTraversal::Skip, &mut |value| {
654 collect_refs_outside_definitions(value, refs);
655 });
656 }
657 _ => {}
658 }
659}
660
661fn collect_refs(value: &JsonValue, refs: &mut Vec<DefinitionPointer>) {
662 match value {
663 JsonValue::Array(values) => {
664 for value in values {
665 collect_refs(value, refs);
666 }
667 }
668 JsonValue::Object(map) => {
669 collect_ref_from_map(map, refs);
670 for value in map.values() {
671 collect_refs(value, refs);
672 }
673 }
674 _ => {}
675 }
676}
677
678fn collect_ref_from_map(
679 map: &serde_json::Map<String, JsonValue>,
680 refs: &mut Vec<DefinitionPointer>,
681) {
682 if let Some(JsonValue::String(schema_ref)) = map.get("$ref")
683 && let Some(pointer) = parse_local_definition_ref(schema_ref)
684 {
685 refs.push(pointer);
686 }
687}
688
689fn definition_for_pointer<'a>(
690 value: &'a JsonValue,
691 pointer: &DefinitionPointer,
692) -> Option<&'a JsonValue> {
693 let JsonValue::Object(map) = value else {
694 return None;
695 };
696
697 map.get(pointer.table)
698 .and_then(JsonValue::as_object)
699 .and_then(|definitions| definitions.get(&pointer.name))
700}
701
702fn parse_local_definition_ref(schema_ref: &str) -> Option<DefinitionPointer> {
703 let fragment = schema_ref.strip_prefix('#')?;
704 let pointer = urlencoding::decode(fragment).ok()?;
705 let pointer = jsonptr::Pointer::parse(pointer.as_ref()).ok()?;
706
707 let (table_token, pointer) = pointer.split_front()?;
708 let table = table_token.decoded();
709 let table = DEFINITION_TABLE_KEYS
710 .into_iter()
711 .find(|candidate| table.as_ref() == *candidate)?;
712
713 let (name, _) = pointer.split_front()?;
716 Some(DefinitionPointer {
717 table,
718 name: name.decoded().into_owned(),
719 })
720}
721
722fn normalized_schema_types(
723 map: &serde_json::Map<String, JsonValue>,
724) -> Vec<JsonSchemaPrimitiveType> {
725 let Some(schema_type) = map.get("type") else {
726 return Vec::new();
727 };
728
729 match schema_type {
730 JsonValue::String(schema_type) => schema_type_from_str(schema_type).into_iter().collect(),
731 JsonValue::Array(schema_types) => schema_types
732 .iter()
733 .filter_map(JsonValue::as_str)
734 .filter_map(schema_type_from_str)
735 .collect(),
736 _ => Vec::new(),
737 }
738}
739
740fn write_schema_types(
741 map: &mut serde_json::Map<String, JsonValue>,
742 schema_types: &[JsonSchemaPrimitiveType],
743) {
744 match schema_types {
745 [] => {
746 map.remove("type");
747 }
748 [schema_type] => {
749 map.insert(
750 "type".to_string(),
751 JsonValue::String(schema_type_name(*schema_type).to_string()),
752 );
753 }
754 _ => {
755 map.insert(
756 "type".to_string(),
757 JsonValue::Array(
758 schema_types
759 .iter()
760 .map(|schema_type| {
761 JsonValue::String(schema_type_name(*schema_type).to_string())
762 })
763 .collect(),
764 ),
765 );
766 }
767 }
768}
769
770fn schema_type_from_str(schema_type: &str) -> Option<JsonSchemaPrimitiveType> {
771 match schema_type {
772 "string" => Some(JsonSchemaPrimitiveType::String),
773 "number" => Some(JsonSchemaPrimitiveType::Number),
774 "boolean" => Some(JsonSchemaPrimitiveType::Boolean),
775 "integer" => Some(JsonSchemaPrimitiveType::Integer),
776 "object" => Some(JsonSchemaPrimitiveType::Object),
777 "array" => Some(JsonSchemaPrimitiveType::Array),
778 "null" => Some(JsonSchemaPrimitiveType::Null),
779 _ => None,
780 }
781}
782
783fn schema_type_name(schema_type: JsonSchemaPrimitiveType) -> &'static str {
784 match schema_type {
785 JsonSchemaPrimitiveType::String => "string",
786 JsonSchemaPrimitiveType::Number => "number",
787 JsonSchemaPrimitiveType::Boolean => "boolean",
788 JsonSchemaPrimitiveType::Integer => "integer",
789 JsonSchemaPrimitiveType::Object => "object",
790 JsonSchemaPrimitiveType::Array => "array",
791 JsonSchemaPrimitiveType::Null => "null",
792 }
793}
794
795fn singleton_null_schema_error() -> serde_json::Error {
796 serde_json::Error::io(std::io::Error::new(
797 std::io::ErrorKind::InvalidInput,
798 "tool input schema must not be a singleton null type",
799 ))
800}
801
802#[cfg(test)]
803#[path = "json_schema_tests.rs"]
804mod tests;