1use serde_json::Value;
51use utoipa::openapi::schema::{
52 AllOf, Array, Object, OneOf, Schema, SchemaFormat, SchemaType, Type,
53};
54use utoipa::openapi::{Ref, RefOr};
55
56pub fn json_schema_to_schema(value: &Value) -> RefOr<Schema> {
79 match value {
80 Value::Object(map) => {
81 if let Some(Value::String(ref_path)) = map.get("$ref") {
83 return RefOr::Ref(Ref::new(ref_path.clone()));
84 }
85
86 if let Some(Value::Array(items)) = map.get("oneOf") {
88 let schemas: Vec<RefOr<Schema>> = items.iter().map(json_schema_to_schema).collect();
89 let mut one_of = OneOf::new();
90 one_of.items = schemas;
91 return RefOr::T(Schema::OneOf(one_of));
92 }
93
94 if let Some(Value::Array(items)) = map.get("allOf") {
96 let schemas: Vec<RefOr<Schema>> = items.iter().map(json_schema_to_schema).collect();
97 let mut all_of = AllOf::new();
98 all_of.items = schemas;
99 return RefOr::T(Schema::AllOf(all_of));
100 }
101
102 let type_str = map.get("type").and_then(|v| v.as_str()).unwrap_or("object");
104
105 match type_str {
106 "array" => {
107 let items_schema = map
108 .get("items")
109 .map(json_schema_to_schema)
110 .unwrap_or_else(|| RefOr::T(Schema::Object(Object::default())));
111
112 let mut arr = Array::new(items_schema);
113
114 if let Some(n) = map.get("minItems").and_then(|v| v.as_u64()) {
115 arr.min_items = Some(n as usize);
116 }
117 if let Some(n) = map.get("maxItems").and_then(|v| v.as_u64()) {
118 arr.max_items = Some(n as usize);
119 }
120
121 RefOr::T(Schema::Array(arr))
122 }
123 "object" => convert_object(map),
124 _ => convert_primitive(map, type_str),
125 }
126 }
127 Value::Bool(true) => RefOr::T(Schema::Object(Object::new())),
129 _ => RefOr::T(Schema::Object(Object::default())),
130 }
131}
132
133fn convert_object(map: &serde_json::Map<String, Value>) -> RefOr<Schema> {
134 let mut obj = Object::with_type(SchemaType::Type(Type::Object));
135
136 if let Some(Value::Object(props)) = map.get("properties") {
138 for (key, val) in props {
139 obj.properties
140 .insert(key.clone(), json_schema_to_schema(val));
141 }
142 }
143
144 if let Some(Value::Array(req)) = map.get("required") {
146 obj.required = req
147 .iter()
148 .filter_map(|v| v.as_str().map(String::from))
149 .collect();
150 }
151
152 if let Some(Value::String(desc)) = map.get("description") {
154 obj.description = Some(desc.clone());
155 }
156
157 if let Some(Value::String(title)) = map.get("title") {
159 obj.title = Some(title.clone());
160 }
161
162 if let Some(default) = map.get("default") {
164 obj.default = Some(default.clone());
165 }
166
167 if let Some(example) = map.get("example") {
169 obj.example = Some(example.clone());
170 }
171
172 RefOr::T(Schema::Object(obj))
173}
174
175fn to_number(n: f64) -> utoipa::Number {
176 if n.fract() == 0.0 {
177 utoipa::Number::Int(n as isize)
178 } else {
179 utoipa::Number::Float(n)
180 }
181}
182
183fn convert_primitive(map: &serde_json::Map<String, Value>, type_str: &str) -> RefOr<Schema> {
184 let schema_type = match type_str {
185 "string" => SchemaType::Type(Type::String),
186 "number" => SchemaType::Type(Type::Number),
187 "integer" => SchemaType::Type(Type::Integer),
188 "boolean" => SchemaType::Type(Type::Boolean),
189 "null" => SchemaType::Type(Type::Null),
190 _ => SchemaType::Type(Type::Object),
191 };
192
193 let mut obj = Object::with_type(schema_type);
194
195 if let Some(Value::String(fmt)) = map.get("format") {
197 obj.format = Some(SchemaFormat::Custom(fmt.clone()));
198 }
199
200 if let Some(Value::String(desc)) = map.get("description") {
202 obj.description = Some(desc.clone());
203 }
204
205 if let Some(Value::String(title)) = map.get("title") {
207 obj.title = Some(title.clone());
208 }
209
210 if let Some(default) = map.get("default") {
212 obj.default = Some(default.clone());
213 }
214
215 if let Some(example) = map.get("example") {
217 obj.example = Some(example.clone());
218 }
219
220 if let Some(Value::Array(vals)) = map.get("enum") {
222 obj.enum_values = Some(vals.clone());
223 }
224
225 if let Some(Value::String(pat)) = map.get("pattern") {
227 obj.pattern = Some(pat.clone());
228 }
229
230 if let Some(n) = map.get("minLength").and_then(|v| v.as_u64()) {
232 obj.min_length = Some(n as usize);
233 }
234 if let Some(n) = map.get("maxLength").and_then(|v| v.as_u64()) {
235 obj.max_length = Some(n as usize);
236 }
237
238 if let Some(n) = map.get("minimum").and_then(|v| v.as_f64()) {
240 obj.minimum = Some(to_number(n));
241 }
242 if let Some(n) = map.get("maximum").and_then(|v| v.as_f64()) {
243 obj.maximum = Some(to_number(n));
244 }
245 if let Some(n) = map.get("exclusiveMinimum").and_then(|v| v.as_f64()) {
246 obj.exclusive_minimum = Some(to_number(n));
247 }
248 if let Some(n) = map.get("exclusiveMaximum").and_then(|v| v.as_f64()) {
249 obj.exclusive_maximum = Some(to_number(n));
250 }
251 if let Some(n) = map.get("multipleOf").and_then(|v| v.as_f64()) {
252 obj.multiple_of = Some(to_number(n));
253 }
254
255 RefOr::T(Schema::Object(obj))
256}
257
258#[macro_export]
298macro_rules! impl_to_schema {
299 ($ty:ty) => {
300 impl $crate::utoipa::PartialSchema for $ty {
301 fn schema() -> $crate::utoipa::openapi::RefOr<$crate::utoipa::openapi::schema::Schema> {
302 $crate::json_schema_to_schema(&<$ty>::json_schema())
303 }
304 }
305
306 impl $crate::utoipa::ToSchema for $ty {
307 fn schemas(
308 schemas: &mut ::std::vec::Vec<(
309 ::std::string::String,
310 $crate::utoipa::openapi::RefOr<$crate::utoipa::openapi::schema::Schema>,
311 )>,
312 ) {
313 #[allow(unused_imports)]
314 use $crate::__VldNestedSchemasFallback as _;
315 for (name, schema_fn) in <$ty>::__vld_nested_schemas() {
316 let json = schema_fn();
317 schemas.push((name.to_string(), $crate::json_schema_to_schema(&json)));
318 }
319 }
320 }
321 };
322 ($ty:ty, $name:expr) => {
323 impl $crate::utoipa::PartialSchema for $ty {
324 fn schema() -> $crate::utoipa::openapi::RefOr<$crate::utoipa::openapi::schema::Schema> {
325 $crate::json_schema_to_schema(&<$ty>::json_schema())
326 }
327 }
328
329 impl $crate::utoipa::ToSchema for $ty {
330 fn name() -> ::std::borrow::Cow<'static, str> {
331 ::std::borrow::Cow::Borrowed($name)
332 }
333
334 fn schemas(
335 schemas: &mut ::std::vec::Vec<(
336 ::std::string::String,
337 $crate::utoipa::openapi::RefOr<$crate::utoipa::openapi::schema::Schema>,
338 )>,
339 ) {
340 #[allow(unused_imports)]
341 use $crate::__VldNestedSchemasFallback as _;
342 for (name, schema_fn) in <$ty>::__vld_nested_schemas() {
343 let json = schema_fn();
344 schemas.push((name.to_string(), $crate::json_schema_to_schema(&json)));
345 }
346 }
347 }
348 };
349}
350
351pub use utoipa;
353
354#[doc(hidden)]
355pub trait __VldNestedSchemasFallback {
356 #[allow(clippy::type_complexity)]
357 fn __vld_nested_schemas() -> Vec<(&'static str, fn() -> serde_json::Value)> {
358 Vec::new()
359 }
360}
361
362impl<T: ?Sized> __VldNestedSchemasFallback for T {}
363
364pub mod prelude {
366 pub use crate::impl_to_schema;
367 pub use crate::json_schema_to_schema;
368 pub use vld::prelude::*;
369}