1use heck::ToSnakeCase;
4use proc_macro2::TokenStream;
5use quote::quote;
6use schemars::schema::{InstanceType, Metadata, ObjectValidation, Schema, SchemaObject};
7
8use crate::{
9 output::{OutputSpace, OutputSpaceMod},
10 type_entry::{
11 StructProperty, StructPropertyRename, StructPropertyState, TypeEntry, TypeEntryStruct,
12 WrappedValue,
13 },
14 util::{get_type_name, metadata_description, recase, Case},
15 Name, Result, TypeEntryDetails, TypeId, TypeSpace,
16};
17
18impl TypeSpace {
19 pub(crate) fn struct_members(
20 &mut self,
21 type_name: Option<String>,
22 validation: &ObjectValidation,
23 ) -> Result<(Vec<StructProperty>, bool)> {
24 let required_unspecified = validation.required.iter().filter_map(|prop_name| {
33 (!validation.properties.contains_key(prop_name))
34 .then_some((prop_name, &Schema::Bool(true)))
35 });
36
37 let mut properties = validation
38 .properties
39 .iter()
40 .chain(required_unspecified)
41 .filter_map(|(prop_name, schema)| {
42 match schema {
43 Schema::Bool(false) => None,
55 _ => {
56 let sub_type_name = type_name
60 .as_ref()
61 .map(|base| format!("{}_{}", base, prop_name.to_snake_case()));
62 Some(self.struct_property(
63 sub_type_name,
64 &validation.required,
65 prop_name,
66 schema,
67 ))
68 }
69 }
70 })
71 .collect::<Result<Vec<_>>>()?;
72
73 properties.sort_by(|a, b| a.name.cmp(&b.name));
75
76 let deny_unknown_fields = match &validation.additional_properties {
81 Some(a) if a.as_ref() == &Schema::Bool(false) => true,
84
85 Some(a) if a.as_ref() == &Schema::Bool(true) => false,
92 None => false,
93
94 additional_properties @ Some(_) => {
99 let sub_type_name = type_name.as_ref().map(|base| format!("{}_extra", base));
100 let map_type = self.make_map(
101 sub_type_name,
102 &validation.property_names,
103 additional_properties,
104 )?;
105 let map_type_id = self.assign_type(map_type);
106 let extra_prop = StructProperty {
107 name: "extra".to_string(),
108 rename: StructPropertyRename::Flatten,
109 state: StructPropertyState::Required,
110 description: None,
111 type_id: map_type_id,
112 };
113
114 properties.push(extra_prop);
115 false
116 }
117 };
118
119 Ok((properties, deny_unknown_fields))
120 }
121
122 fn struct_property(
123 &mut self,
124 type_name: Option<String>,
125 required: &schemars::Set<String>,
126 prop_name: &str,
127 schema: &schemars::schema::Schema,
128 ) -> Result<StructProperty> {
129 let sub_type_name = match type_name {
130 Some(name) => Name::Suggested(name),
131 None => Name::Unknown,
132 };
133 let (mut type_id, metadata) = self.id_for_schema(sub_type_name, schema)?;
134
135 let state = if required.contains(prop_name) {
136 StructPropertyState::Required
137 } else {
138 match has_default(
158 self,
159 &type_id,
160 metadata.as_ref().and_then(|m| m.default.as_ref()),
161 ) {
162 StructPropertyState::Required => {
163 type_id = self.id_to_option(&type_id);
164 StructPropertyState::Optional
165 }
166 other => other,
167 }
168 };
169
170 let (name, rename) = recase(prop_name, Case::Snake);
171 let rename = match rename {
172 Some(old_name) => StructPropertyRename::Rename(old_name),
173 None => StructPropertyRename::None,
174 };
175
176 Ok(StructProperty {
177 name,
178 rename,
179 state,
180 description: metadata_description(metadata),
181 type_id,
182 })
183 }
184
185 pub(crate) fn make_map(
186 &mut self,
187 type_name: Option<String>,
188 property_names: &Option<Box<Schema>>,
189 additional_properties: &Option<Box<Schema>>,
190 ) -> Result<TypeEntry> {
191 let key_id = match property_names.as_deref() {
192 Some(Schema::Bool(true)) | None => self.assign_type(TypeEntryDetails::String.into()),
193
194 Some(Schema::Bool(false)) => todo!(),
197
198 Some(Schema::Object(obj)) => {
199 let key_type_name = match &type_name {
200 Some(name) => Name::Suggested(format!("{}Key", name)),
201 None => Name::Unknown,
202 };
203 self.id_for_schema_string(key_type_name, obj)?
204 }
205 };
206
207 let (value_id, _) = match additional_properties {
208 Some(value_schema) => {
209 let value_type_name = match &type_name {
210 Some(name) => Name::Suggested(format!("{}Value", name)),
211 None => Name::Unknown,
212 };
213 self.id_for_schema(value_type_name, value_schema)?
214 }
215
216 None => self.id_for_schema(Name::Unknown, &Schema::Bool(true))?,
217 };
218
219 Ok(TypeEntryDetails::Map(key_id, value_id).into())
220 }
221
222 pub(crate) fn id_for_schema_string(
224 &mut self,
225 type_name: Name,
226 schema_obj: &SchemaObject,
227 ) -> Result<TypeId> {
228 match schema_obj {
229 SchemaObject {
232 instance_type: None,
233 subschemas: None,
234 reference: None,
235 ..
236 } => {
237 let schema = Schema::Object(SchemaObject {
238 instance_type: Some(InstanceType::String.into()),
239 ..schema_obj.clone()
240 });
241 Ok(self.id_for_schema(type_name, &schema)?.0)
242 }
243
244 _ => {
247 let schema = Schema::Object(schema_obj.clone());
248 Ok(self.id_for_schema(type_name, &schema)?.0)
249 }
250 }
251 }
252
253 pub(crate) fn flattened_union_struct<'a>(
269 &mut self,
270 type_name: Name,
271 original_schema: &'a Schema,
272 metadata: &'a Option<Box<Metadata>>,
273 subschemas: &[Schema],
274 optional: bool,
275 ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
276 let properties = subschemas
277 .iter()
278 .enumerate()
279 .map(|(idx, schema)| {
280 let type_name = match get_type_name(&type_name, metadata) {
281 Some(name) => Name::Suggested(format!("{}Subtype{}", name, idx)),
282 None => Name::Unknown,
283 };
284
285 let (mut type_id, _) = self.id_for_schema(type_name, schema)?;
286 if optional {
287 type_id = self.id_to_option(&type_id);
288 }
289
290 let name = format!("subtype_{}", idx);
293
294 Ok(StructProperty {
295 name,
296 rename: StructPropertyRename::Flatten,
297 state: if optional {
298 StructPropertyState::Optional
299 } else {
300 StructPropertyState::Required
301 },
302 description: None,
303 type_id,
304 })
305 })
306 .collect::<Result<Vec<_>>>()?;
307
308 Ok((
309 TypeEntryStruct::from_metadata(
310 self,
311 type_name,
312 metadata,
313 properties,
314 false,
315 original_schema.clone(),
316 ),
317 metadata,
318 ))
319 }
320}
321
322pub(crate) enum DefaultFunction {
323 None,
324 Default,
325 Custom(String),
326}
327
328pub(crate) fn generate_serde_attr(
337 type_name: &str,
338 prop_name: &str,
339 naming: &StructPropertyRename,
340 state: &StructPropertyState,
341 prop_type: &TypeEntry,
342 type_space: &TypeSpace,
343 output: &mut OutputSpace,
344) -> (TokenStream, DefaultFunction) {
345 let mut serde_options = Vec::new();
346 match naming {
347 StructPropertyRename::Rename(s) => serde_options.push(quote! { rename = #s }),
348 StructPropertyRename::Flatten => serde_options.push(quote! { flatten }),
349 StructPropertyRename::None => (),
350 }
351
352 let default_fn = match (state, &prop_type.details) {
353 (StructPropertyState::Optional, TypeEntryDetails::Option(_)) => {
354 serde_options.push(quote! { default });
355 serde_options.push(quote! { skip_serializing_if = "::std::option::Option::is_none" });
356 DefaultFunction::Default
357 }
358 (StructPropertyState::Optional, TypeEntryDetails::Vec(_)) => {
359 serde_options.push(quote! { default });
360 serde_options.push(quote! { skip_serializing_if = "::std::vec::Vec::is_empty" });
361 DefaultFunction::Default
362 }
363 (StructPropertyState::Optional, TypeEntryDetails::Map(key_id, value_id)) => {
364 serde_options.push(quote! { default });
365
366 let map_to_use = &type_space.settings.map_type;
367 let key_ty = type_space
368 .id_to_entry
369 .get(key_id)
370 .expect("unresolved key type id for map");
371 let value_ty = type_space
372 .id_to_entry
373 .get(value_id)
374 .expect("unresolved value type id for map");
375
376 if key_ty.details == TypeEntryDetails::String
377 && value_ty.details == TypeEntryDetails::JsonValue
378 {
379 serde_options.push(quote! {
380 skip_serializing_if = "::serde_json::Map::is_empty"
381 });
382 } else {
383 let is_empty = format!("{}::is_empty", map_to_use);
384 serde_options.push(quote! {
385 skip_serializing_if = #is_empty
386 });
387 }
388 DefaultFunction::Default
389 }
390 (StructPropertyState::Optional, _) => {
391 serde_options.push(quote! { default });
392 DefaultFunction::Default
393 }
394
395 (StructPropertyState::Default(WrappedValue(value)), _) => {
396 let (fn_name, default_fn) =
397 prop_type.default_fn(value, type_space, type_name, prop_name);
398 serde_options.push(quote! { default = #fn_name });
399
400 if let Some(default_fn) = default_fn {
401 output.add_item(OutputSpaceMod::Defaults, type_name, default_fn);
402 }
403 DefaultFunction::Custom(fn_name)
404 }
405
406 (StructPropertyState::Required, _) => DefaultFunction::None,
407 };
408
409 let serde = if serde_options.is_empty() {
410 quote! {}
411 } else {
412 quote! {
413 #[serde( #(#serde_options),*)]
414 }
415 };
416
417 (serde, default_fn)
418}
419
420fn has_default(
424 type_space: &mut TypeSpace,
425 type_id: &TypeId,
426 default: Option<&serde_json::Value>,
427) -> StructPropertyState {
428 match (
433 type_space
434 .id_to_entry
435 .get(type_id)
436 .map(|type_entry| &type_entry.details),
437 default,
438 ) {
439 (Some(TypeEntryDetails::Option(_)), None) => StructPropertyState::Optional,
441 (Some(TypeEntryDetails::Vec(_)), None) => StructPropertyState::Optional,
442 (Some(TypeEntryDetails::Map(..)), None) => StructPropertyState::Optional,
443 (Some(TypeEntryDetails::Unit), None) => StructPropertyState::Optional,
444 (_, None) => StructPropertyState::Required,
445
446 (Some(TypeEntryDetails::Option(_)), Some(serde_json::Value::Null)) => {
448 StructPropertyState::Optional
449 }
450 (Some(TypeEntryDetails::Vec(_)), Some(serde_json::Value::Array(a))) if a.is_empty() => {
452 StructPropertyState::Optional
453 }
454 (Some(TypeEntryDetails::Map(..)), Some(serde_json::Value::Object(m))) if m.is_empty() => {
456 StructPropertyState::Optional
457 }
458 (Some(TypeEntryDetails::Boolean), Some(serde_json::Value::Bool(false))) => {
460 StructPropertyState::Optional
461 }
462 (Some(TypeEntryDetails::Integer(_)), Some(serde_json::Value::Number(n)))
464 if n.as_u64() == Some(0) =>
465 {
466 StructPropertyState::Optional
467 }
468 (Some(TypeEntryDetails::Integer(_)), Some(serde_json::Value::Number(n)))
470 if n.as_f64() == Some(0.0) =>
471 {
472 StructPropertyState::Optional
473 }
474 (Some(TypeEntryDetails::String), Some(serde_json::Value::String(s))) if s.is_empty() => {
476 StructPropertyState::Optional
477 }
478
479 (None, Some(default)) => StructPropertyState::Default(WrappedValue(default.clone())),
481 (Some(_), Some(default)) => StructPropertyState::Default(WrappedValue(default.clone())),
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use schema::Schema;
490 use schemars::JsonSchema;
491 use serde::Serialize;
492
493 use crate::{test_util::validate_output, Name, TypeSpace};
494
495 #[allow(dead_code)]
496 #[derive(Serialize, JsonSchema, Schema)]
497 #[serde(deny_unknown_fields)]
498 struct SimpleStruct {
499 alpha: u32,
500 bravo: String,
501 charlie: Vec<(String, u32)>,
502 delta: Option<String>,
503 echo: Option<(u32, String)>,
504 }
505
506 #[test]
507 fn test_simple_struct() {
508 validate_output::<SimpleStruct>();
509 }
510
511 #[allow(dead_code)]
512 #[derive(Serialize, JsonSchema, Schema)]
513 struct LessSimpleStruct {
514 thing: SimpleStruct,
515 things: Vec<SimpleStruct>,
516 }
517
518 #[test]
519 fn test_less_simple_struct() {
520 validate_output::<LessSimpleStruct>();
521 }
522
523 #[allow(dead_code)]
524 #[derive(Serialize, JsonSchema, Schema)]
525 struct SomeMaps {
526 strings: ::std::collections::HashMap<String, String>,
527 things: ::serde_json::Map<String, ::serde_json::Value>,
528 }
529
530 #[test]
531 fn test_some_maps() {
532 validate_output::<SomeMaps>();
533 }
534
535 #[allow(dead_code)]
536 #[derive(Serialize, JsonSchema, Schema)]
537 #[serde(deny_unknown_fields)]
538 struct FlattenStuff {
539 number: i32,
540 #[serde(flatten)]
541 extra: ::std::collections::HashMap<String, String>,
542 }
543
544 #[test]
545 fn test_flatten_stuff() {
546 validate_output::<FlattenStuff>();
547 }
548
549 #[test]
550 fn test_default_field() {
551 #[allow(dead_code)]
552 #[derive(Serialize, JsonSchema, Schema)]
553 #[serde(deny_unknown_fields)]
554 struct DefaultField {
555 #[serde(default)]
556 number: i32,
557 }
558
559 validate_output::<DefaultField>();
560 }
561
562 #[test]
563 fn test_object_no_validation() {
564 let schema = schemars::schema::Schema::Object(schemars::schema::SchemaObject {
565 instance_type: Some(schemars::schema::InstanceType::Object.into()),
566 ..Default::default()
567 });
568
569 let mut type_space = TypeSpace::default();
570 let (ty, _) = type_space.convert_schema(Name::Unknown, &schema).unwrap();
571 let output = ty.type_name(&type_space).replace(" ", "");
572 assert_eq!(
573 output,
574 "::serde_json::Map<::std::string::String,::serde_json::Value>"
575 );
576 }
577}