gproxy_protocol/transform/generate/gemini_schema/
to_json.rs1use crate::{
2 transform::{Converted, Report, TransformError},
3 wire::gemini::content::{Schema, SchemaType},
4};
5use serde_json::{Map, Value};
6
7pub fn to_json(input: &Schema) -> Result<Converted<Map<String, Value>>, TransformError> {
10 let mut report = Report::default();
11 let value = convert(input, "schema", &mut report)?;
12 Ok(Converted { value, report })
13}
14
15fn convert(
16 input: &Schema,
17 path: &str,
18 report: &mut Report,
19) -> Result<Map<String, Value>, TransformError> {
20 let mut out = Map::new();
21 let type_name = match input.type_ {
22 SchemaType::Unspecified => None,
23 SchemaType::String => Some("string"),
24 SchemaType::Number => Some("number"),
25 SchemaType::Integer => Some("integer"),
26 SchemaType::Boolean => Some("boolean"),
27 SchemaType::Array => Some("array"),
28 SchemaType::Object => Some("object"),
29 SchemaType::Null => Some("null"),
30 };
31 if let Some(name) = type_name {
32 out.insert("type".into(), name.into());
33 } else if input.any_of.as_ref().is_none_or(Vec::is_empty) {
34 return Err(TransformError::shape(
35 format!("{path}.type"),
36 "unspecified type requires an anyOf schema",
37 ));
38 }
39 for (key, value) in [
40 ("format", &input.format),
41 ("title", &input.title),
42 ("description", &input.description),
43 ("pattern", &input.pattern),
44 ] {
45 if let Some(value) = value {
46 out.insert(key.into(), value.clone().into());
47 }
48 }
49 for (key, value) in [
50 ("minItems", &input.min_items),
51 ("maxItems", &input.max_items),
52 ("minLength", &input.min_length),
53 ("maxLength", &input.max_length),
54 ("minProperties", &input.min_properties),
55 ("maxProperties", &input.max_properties),
56 ] {
57 if let Some(value) = value {
58 let number = value
59 .parse::<i64>()
60 .ok()
61 .filter(|n| *n >= 0)
62 .ok_or_else(|| {
63 TransformError::shape(
64 format!("{path}.{key}"),
65 "expected a nonnegative int64 string",
66 )
67 })?;
68 out.insert(key.into(), number.into());
69 }
70 }
71 for (key, value) in [("minimum", input.minimum), ("maximum", input.maximum)] {
72 if let Some(value) = value {
73 let number = serde_json::Number::from_f64(value).ok_or_else(|| {
74 TransformError::shape(format!("{path}.{key}"), "bound must be finite")
75 })?;
76 out.insert(key.into(), Value::Number(number));
77 }
78 }
79 if let Some(values) = &input.r#enum {
80 if values.is_empty() {
81 return Err(TransformError::shape(
82 format!("{path}.enum"),
83 "enum must be nonempty",
84 ));
85 }
86 if input.type_ != SchemaType::String {
87 return Err(TransformError::shape(
88 format!("{path}.enum"),
89 "Gemini enum is declared only for STRING schemas",
90 ));
91 }
92 out.insert("enum".into(), serde_json::to_value(values)?);
93 }
94 if let Some(values) = &input.required {
95 out.insert("required".into(), serde_json::to_value(values)?);
96 }
97 if let Some(values) = &input.properties {
98 let mut properties = Map::new();
99 for (name, schema) in values {
100 properties.insert(
101 name.clone(),
102 Value::Object(convert(
103 schema,
104 &format!("{path}.properties.{name}"),
105 report,
106 )?),
107 );
108 }
109 out.insert("properties".into(), Value::Object(properties));
110 }
111 if let Some(items) = &input.items {
112 out.insert(
113 "items".into(),
114 Value::Object(convert(items, &format!("{path}.items"), report)?),
115 );
116 }
117 if let Some(values) = &input.any_of {
118 if values.is_empty() {
119 return Err(TransformError::shape(
120 format!("{path}.anyOf"),
121 "anyOf must be nonempty",
122 ));
123 }
124 let mut variants = Vec::with_capacity(values.len());
125 for (index, value) in values.iter().enumerate() {
126 variants.push(Value::Object(convert(
127 value,
128 &format!("{path}.anyOf[{index}]"),
129 report,
130 )?));
131 }
132 out.insert("anyOf".into(), Value::Array(variants));
133 }
134 if input.nullable == Some(true)
137 && let Some(name) = type_name.filter(|name| *name != "null")
138 {
139 out.insert("type".into(), serde_json::json!([name, "null"]));
140 }
141 if let Some(value) = &input.default {
142 out.insert("default".into(), value.clone());
143 }
144 if let Some(value) = &input.example {
145 out.insert("examples".into(), Value::Array(vec![value.clone()]));
146 }
147 if input.property_ordering.is_some() {
148 report.omitted(
149 format!("{path}.propertyOrdering"),
150 "JSON Schema has no property emission ordering constraint",
151 );
152 }
153 Ok(out)
154}