1#[macro_use]
50extern crate serde_derive;
51
52#[macro_use]
53extern crate quote;
54
55pub mod generator;
56
57mod schema;
61
62use std::{borrow::Cow, convert::TryFrom};
63
64use inflector::Inflector;
65
66use serde_json::Value;
67
68use uriparse::{Fragment, URI};
69
70pub use schema::{Schema, SimpleTypes};
71
72pub use generator::{Generator, GeneratorBuilder};
73
74use proc_macro2::{Span, TokenStream};
75
76fn replace_invalid_identifier_chars(s: &str) -> String {
77 s.strip_prefix('$')
78 .unwrap_or(s)
79 .replace(|c: char| !c.is_alphanumeric() && c != '_', "_")
80}
81
82fn replace_numeric_start(s: &str) -> String {
83 if s.chars().next().map(|c| c.is_numeric()).unwrap_or(false) {
84 format!("_{}", s)
85 } else {
86 s.to_string()
87 }
88}
89
90fn remove_excess_underscores(s: &str) -> String {
91 let mut result = String::new();
92 let mut char_iter = s.chars().peekable();
93
94 while let Some(c) = char_iter.next() {
95 let next_c = char_iter.peek();
96 if c != '_' || !matches!(next_c, Some('_')) {
97 result.push(c);
98 }
99 }
100
101 result
102}
103
104pub fn str_to_ident(s: &str) -> syn::Ident {
105 if s.is_empty() {
106 return syn::Ident::new("empty_", Span::call_site());
107 }
108
109 if s.chars().all(|c| c == '_') {
110 return syn::Ident::new("underscore_", Span::call_site());
111 }
112
113 let s = replace_invalid_identifier_chars(s);
114 let s = replace_numeric_start(&s);
115 let s = remove_excess_underscores(&s);
116
117 if s.is_empty() {
118 return syn::Ident::new("invalid_", Span::call_site());
119 }
120
121 let keywords = [
122 "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
123 "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
124 "return", "self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use",
125 "where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv",
126 "typeof", "unsized", "virtual", "yield", "async", "await", "try",
127 ];
128 if keywords.iter().any(|&keyword| keyword == s) {
129 return syn::Ident::new(&format!("{}_", s), Span::call_site());
130 }
131
132 syn::Ident::new(&s, Span::call_site())
133}
134
135fn rename_keyword(prefix: &str, s: &str) -> Option<TokenStream> {
136 let n = str_to_ident(s);
137
138 if n == s {
139 return None;
140 }
141
142 if prefix.is_empty() {
143 Some(quote! {
144 #[serde(rename = #s)]
145 #n
146 })
147 } else {
148 let prefix = syn::Ident::new(prefix, Span::call_site());
149 Some(quote! {
150 #[serde(rename = #s)]
151 #prefix #n
152 })
153 }
154}
155
156fn field(s: &str) -> TokenStream {
157 if let Some(t) = rename_keyword("pub", s) {
158 return t;
159 }
160 let snake = s.to_snake_case();
161 if snake == s && !snake.contains(|c: char| c == '$' || c == '#') {
162 let field = syn::Ident::new(s, Span::call_site());
163 return quote!( pub #field );
164 }
165
166 let field = if snake.is_empty() {
167 syn::Ident::new("underscore", Span::call_site())
168 } else {
169 str_to_ident(&snake)
170 };
171
172 quote! {
173 #[serde(rename = #s)]
174 pub #field
175 }
176}
177
178fn merge_option<T, F>(mut result: &mut Option<T>, r: &Option<T>, f: F)
179where
180 F: FnOnce(&mut T, &T),
181 T: Clone,
182{
183 *result = match (&mut result, r) {
184 (&mut &mut Some(ref mut result), &Some(ref r)) => return f(result, r),
185 (&mut &mut None, &Some(ref r)) => Some(r.clone()),
186 _ => return,
187 };
188}
189
190fn merge_all_of(result: &mut Schema, r: &Schema) {
191 use std::collections::btree_map::Entry;
192
193 for (k, v) in &r.properties {
194 match result.properties.entry(k.clone()) {
195 Entry::Vacant(entry) => {
196 entry.insert(v.clone());
197 }
198 Entry::Occupied(mut entry) => merge_all_of(entry.get_mut(), v),
199 }
200 }
201
202 if let Some(ref ref_) = r.ref_ {
203 result.ref_ = Some(ref_.clone());
204 }
205
206 if let Some(ref description) = r.description {
207 result.description = Some(description.clone());
208 }
209
210 merge_option(&mut result.required, &r.required, |required, r_required| {
211 required.extend(r_required.iter().cloned());
212 });
213
214 result.type_.retain(|e| r.type_.contains(e));
215}
216
217const LINE_LENGTH: usize = 100;
218const INDENT_LENGTH: usize = 4;
219
220fn make_doc_comment(mut comment: &str, remaining_line: usize) -> TokenStream {
221 let mut out_comment = String::new();
222 out_comment.push_str("/// ");
223 let mut length = 4;
224 while let Some(word) = comment.split(char::is_whitespace).next() {
225 if comment.is_empty() {
226 break;
227 }
228 comment = &comment[word.len()..];
229 if length + word.len() >= remaining_line {
230 out_comment.push_str("\n/// ");
231 length = 4;
232 }
233 out_comment.push_str(word);
234 length += word.len();
235 let mut n = comment.chars();
236 match n.next() {
237 Some('\n') => {
238 out_comment.push('\n');
239 out_comment.push_str("/// ");
240 length = 4;
241 }
242 Some(_) => {
243 out_comment.push(' ');
244 length += 1;
245 }
246 None => (),
247 }
248 comment = n.as_str();
249 }
250 if out_comment.ends_with(' ') {
251 out_comment.pop();
252 }
253 out_comment.push('\n');
254 out_comment.parse().unwrap()
255}
256
257struct FieldExpander<'a, 'r: 'a> {
258 default: bool,
259 expander: &'a mut Expander<'r>,
260}
261
262impl<'a, 'r> FieldExpander<'a, 'r> {
263 fn expand_fields(&mut self, type_name: &str, schema: &Schema) -> Vec<TokenStream> {
264 let schema = self.expander.schema(schema);
265 schema
266 .properties
267 .iter()
268 .map(|(field_name, value)| {
269 self.expander.current_field.clone_from(field_name);
270 let key = field(field_name);
271 let required = schema
272 .required
273 .iter()
274 .flat_map(|a| a.iter())
275 .any(|req| req == field_name);
276 let field_type = self.expander.expand_type(type_name, required, value);
277 if !field_type.typ.starts_with("Option<") {
278 self.default = false;
279 }
280 let typ = field_type.typ.parse::<TokenStream>().unwrap();
281
282 let default = if field_type.default {
283 Some(quote! { #[serde(default)] })
284 } else {
285 None
286 };
287 let attributes = if field_type.attributes.is_empty() {
288 None
289 } else {
290 let attributes = field_type
291 .attributes
292 .iter()
293 .map(|attr| attr.parse::<TokenStream>().unwrap());
294 Some(quote! {
295 #[serde( #(#attributes),* )]
296 })
297 };
298 let comment = value
299 .description
300 .as_ref()
301 .map(|comment| make_doc_comment(comment, LINE_LENGTH - INDENT_LENGTH));
302 quote! {
303 #comment
304 #default
305 #attributes
306 #key : #typ
307 }
308 })
309 .collect()
310 }
311}
312
313pub struct Expander<'r> {
314 root_name: Option<&'r str>,
315 schemafy_path: &'r str,
316 root: &'r Schema,
317 current_type: String,
318 current_field: String,
319 types: Vec<(String, TokenStream)>,
320}
321
322struct FieldType {
323 typ: String,
324 attributes: Vec<String>,
325 default: bool,
326}
327
328impl<S> From<S> for FieldType
329where
330 S: Into<String>,
331{
332 fn from(s: S) -> FieldType {
333 FieldType {
334 typ: s.into(),
335 attributes: Vec::new(),
336 default: false,
337 }
338 }
339}
340
341impl<'r> Expander<'r> {
342 pub fn new(
343 root_name: Option<&'r str>,
344 schemafy_path: &'r str,
345 root: &'r Schema,
346 ) -> Expander<'r> {
347 Expander {
348 root_name,
349 root,
350 schemafy_path,
351 current_field: "".into(),
352 current_type: "".into(),
353 types: Vec::new(),
354 }
355 }
356
357 fn type_ref(&self, s: &str) -> String {
358 let fragment = URI::try_from(s)
360 .map(|uri| uri.fragment().map(Fragment::to_owned))
361 .ok()
362 .flatten()
363 .or({
364 let s = s.strip_prefix('#').unwrap_or(s);
365 Fragment::try_from(s).ok()
366 })
367 .map(|fragment| fragment.to_string())
368 .unwrap_or_else(|| s.to_owned());
369
370 let ref_ = if fragment.is_empty() {
371 self.root_name.expect("No root name specified for schema")
372 } else {
373 fragment.split('/').last().expect("Component")
374 };
375
376 let ref_ = ref_.to_pascal_case();
377 let ref_ = replace_invalid_identifier_chars(&ref_);
378 let ref_ = replace_numeric_start(&ref_);
379
380 format!("Box<{}>", ref_)
381 }
382
383 fn schema(&self, schema: &'r Schema) -> Cow<'r, Schema> {
384 let schema = match schema.ref_ {
385 Some(ref ref_) => self.schema_ref(ref_),
386 None => schema,
387 };
388 match schema.all_of {
389 Some(ref all_of) if !all_of.is_empty() => {
390 all_of
391 .iter()
392 .skip(1)
393 .fold(self.schema(&all_of[0]).clone(), |mut result, def| {
394 merge_all_of(result.to_mut(), &self.schema(def));
395 result
396 })
397 }
398 _ => Cow::Borrowed(schema),
399 }
400 }
401
402 fn schema_ref(&self, s: &str) -> &'r Schema {
403 s.split('/').fold(self.root, |schema, comp| {
404 if comp.ends_with('#') {
405 self.root
406 } else if comp == "definitions" {
407 schema
408 } else {
409 schema
410 .definitions
411 .get(comp)
412 .unwrap_or_else(|| panic!("Expected definition: `{}` {}", s, comp))
413 }
414 })
415 }
416
417 fn expand_type(&mut self, type_name: &str, required: bool, typ: &Schema) -> FieldType {
418 let saved_type = self.current_type.clone();
419 let mut result = self.expand_type_(typ);
420 self.current_type = saved_type;
421 if type_name.to_pascal_case() == result.typ.to_pascal_case() {
422 result.typ = format!("Box<{}>", result.typ)
423 }
424 if !required {
425 if !result.default {
426 result.typ = format!("Option<{}>", result.typ);
427 }
428 if result.typ.starts_with("Option<") {
429 result
430 .attributes
431 .push("skip_serializing_if=\"Option::is_none\"".into());
432 }
433 }
434 result
435 }
436
437 fn expand_type_(&mut self, typ: &Schema) -> FieldType {
438 if let Some(ref ref_) = typ.ref_ {
439 self.type_ref(ref_).into()
440 } else if typ.any_of.as_ref().map_or(false, |a| a.len() >= 2) {
441 let any_of = typ.any_of.as_ref().unwrap();
442 let simple = self.schema(&any_of[0]);
443 let array = self.schema(&any_of[1]);
444 if !array.type_.is_empty() {
445 if let SimpleTypes::Array = array.type_[0] {
446 if simple == self.schema(&array.items[0]) {
447 return FieldType {
448 typ: format!("Vec<{}>", self.expand_type_(&any_of[0]).typ),
449 attributes: vec![format!(
450 r#"with="{}one_or_many""#,
451 self.schemafy_path
452 )],
453 default: true,
454 };
455 }
456 }
457 }
458 "serde_json::Value".into()
459 } else if typ.one_of.as_ref().map_or(false, |a| a.len() >= 2) {
460 let schemas = typ.one_of.as_ref().unwrap();
461 let (type_name, type_def) = self.expand_one_of(schemas);
462 self.types.push((type_name.clone(), type_def));
463 type_name.into()
464 } else if typ.type_.len() == 2 {
465 if typ.type_[0] == SimpleTypes::Null || typ.type_[1] == SimpleTypes::Null {
466 let mut ty = typ.clone();
467 ty.type_.retain(|x| *x != SimpleTypes::Null);
468
469 FieldType {
470 typ: format!("Option<{}>", self.expand_type_(&ty).typ),
471 attributes: vec![],
472 default: true,
473 }
474 } else {
475 "serde_json::Value".into()
476 }
477 } else if typ.type_.len() == 1 {
478 match typ.type_[0] {
479 SimpleTypes::String => {
480 if typ.enum_.as_ref().map_or(false, |e| e.is_empty()) {
481 "serde_json::Value".into()
482 } else {
483 "String".into()
484 }
485 }
486 SimpleTypes::Integer => "i64".into(),
487 SimpleTypes::Boolean => "bool".into(),
488 SimpleTypes::Number => "f64".into(),
489 SimpleTypes::Object
491 if !typ.properties.is_empty()
492 || typ.additional_properties == Some(Value::Bool(false)) =>
493 {
494 let name = format!(
495 "{}{}",
496 self.current_type.to_pascal_case(),
497 self.current_field.to_pascal_case()
498 );
499 let tokens = self.expand_schema(&name, typ);
500 self.types.push((name.clone(), tokens));
501 name.into()
502 }
503 SimpleTypes::Object => {
504 let prop = match typ.additional_properties {
505 Some(ref props) if props.is_object() => {
506 let prop = serde_json::from_value(props.clone()).unwrap();
507 self.expand_type_(&prop).typ
508 }
509 _ => "serde_json::Value".into(),
510 };
511 let result = format!("::std::collections::BTreeMap<String, {}>", prop);
512 FieldType {
513 typ: result,
514 attributes: Vec::new(),
515 default: typ.default == Some(Value::Object(Default::default())),
516 }
517 }
518 SimpleTypes::Array => {
519 let item_type = typ.items.get(0).map_or("serde_json::Value".into(), |item| {
520 self.current_type = format!("{}Item", self.current_type);
521 self.expand_type_(item).typ
522 });
523 format!("Vec<{}>", item_type).into()
524 }
525 _ => "serde_json::Value".into(),
526 }
527 } else {
528 "serde_json::Value".into()
529 }
530 }
531
532 fn expand_one_of(&mut self, schemas: &[Schema]) -> (String, TokenStream) {
533 let current_field = if self.current_field.is_empty() {
534 "".to_owned()
535 } else {
536 str_to_ident(&self.current_field)
537 .to_string()
538 .to_pascal_case()
539 };
540 let saved_type = format!("{}{}", self.current_type, current_field);
541 if schemas.is_empty() {
542 return (saved_type, TokenStream::new());
543 }
544 let (variant_names, variant_types): (Vec<_>, Vec<_>) = schemas
545 .iter()
546 .enumerate()
547 .map(|(i, schema)| {
548 let name = schema.id.clone().unwrap_or_else(|| format!("Variant{}", i));
549 if let Some(ref_) = &schema.ref_ {
550 let type_ = self.type_ref(ref_);
551 (format_ident!("{}", &name), format_ident!("{}", &type_))
552 } else {
553 let type_name = format!("{}{}", saved_type, &name);
554 let field_type = self.expand_schema(&type_name, schema);
555 self.types.push((type_name.clone(), field_type));
556 (format_ident!("{}", &name), format_ident!("{}", &type_name))
557 }
558 })
559 .unzip();
560 let type_name_ident = syn::Ident::new(&saved_type, Span::call_site());
561 let type_def = quote! {
562 #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
563 #[serde(untagged)]
564 pub enum #type_name_ident {
565 #(#variant_names(#variant_types)),*
566 }
567 };
568 (saved_type, type_def)
569 }
570
571 fn expand_definitions(&mut self, schema: &Schema) {
572 for (name, def) in &schema.definitions {
573 let type_decl = self.expand_schema(name, def);
574 let definition_tokens = match def.description {
575 Some(ref comment) => {
576 let t = make_doc_comment(comment, LINE_LENGTH);
577 quote! {
578 #t
579 #type_decl
580 }
581 }
582 None => type_decl,
583 };
584 self.types.push((name.to_string(), definition_tokens));
585 }
586 }
587
588 fn expand_schema(&mut self, original_name: &str, schema: &Schema) -> TokenStream {
589 self.expand_definitions(schema);
590
591 let pascal_case_name = replace_invalid_identifier_chars(&original_name.to_pascal_case());
592 self.current_type.clone_from(&pascal_case_name);
593 let (fields, default) = {
594 let mut field_expander = FieldExpander {
595 default: true,
596 expander: self,
597 };
598 let fields = field_expander.expand_fields(original_name, schema);
599 (fields, field_expander.default)
600 };
601 let name = syn::Ident::new(&pascal_case_name, Span::call_site());
602 let is_struct =
603 !fields.is_empty() || schema.additional_properties == Some(Value::Bool(false));
604 let serde_rename = if name == original_name {
605 None
606 } else {
607 Some(quote! {
608 #[serde(rename = #original_name)]
609 })
610 };
611 let is_enum = schema.enum_.as_ref().map_or(false, |e| !e.is_empty());
612 let type_decl = if is_struct {
613 let serde_deny_unknown = if schema.additional_properties == Some(Value::Bool(false))
614 && schema.pattern_properties.is_empty()
615 {
616 Some(quote! { #[serde(deny_unknown_fields)] })
617 } else {
618 None
619 };
620 if default {
621 quote! {
622 #[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
623 #serde_rename
624 #serde_deny_unknown
625 pub struct #name {
626 #(#fields),*
627 }
628 }
629 } else {
630 quote! {
631 #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
632 #serde_rename
633 #serde_deny_unknown
634 pub struct #name {
635 #(#fields),*
636 }
637 }
638 }
639 } else if is_enum {
640 let mut optional = false;
641 let mut repr_i64 = false;
642 let variants = if schema.enum_names.as_ref().map_or(false, |e| !e.is_empty()) {
643 let values = schema.enum_.as_ref().map_or(&[][..], |v| v);
644 let names = schema.enum_names.as_ref().map_or(&[][..], |v| v);
645 if names.len() != values.len() {
646 panic!(
647 "enumNames(length {}) and enum(length {}) have different length",
648 names.len(),
649 values.len()
650 )
651 }
652 names
653 .iter()
654 .enumerate()
655 .map(|(idx, name)| (&values[idx], name))
656 .flat_map(|(value, name)| {
657 let pascal_case_variant = name.to_pascal_case();
658 let variant_name =
659 rename_keyword("", &pascal_case_variant).unwrap_or_else(|| {
660 let v = syn::Ident::new(&pascal_case_variant, Span::call_site());
661 quote!(#v)
662 });
663 match value {
664 Value::String(ref s) => Some(quote! {
665 #[serde(rename = #s)]
666 #variant_name
667 }),
668 Value::Number(ref n) => {
669 repr_i64 = true;
670 let num = syn::LitInt::new(&n.to_string(), Span::call_site());
671 Some(quote! {
672 #variant_name = #num
673 })
674 }
675 Value::Null => {
676 optional = true;
677 None
678 }
679 _ => panic!("Expected string,bool or number for enum got `{}`", value),
680 }
681 })
682 .collect::<Vec<_>>()
683 } else {
684 schema
685 .enum_
686 .as_ref()
687 .map_or(&[][..], |v| v)
688 .iter()
689 .flat_map(|v| match *v {
690 Value::String(ref v) => {
691 let pascal_case_variant = v.to_pascal_case();
692 let variant_name = rename_keyword("", &pascal_case_variant)
693 .unwrap_or_else(|| {
694 let v =
695 syn::Ident::new(&pascal_case_variant, Span::call_site());
696 quote!(#v)
697 });
698 Some(if pascal_case_variant == *v {
699 variant_name
700 } else {
701 quote! {
702 #[serde(rename = #v)]
703 #variant_name
704 }
705 })
706 }
707 Value::Null => {
708 optional = true;
709 None
710 }
711 _ => panic!("Expected string for enum got `{}`", v),
712 })
713 .collect::<Vec<_>>()
714 };
715 if optional {
716 let enum_name = syn::Ident::new(&format!("{}_", name), Span::call_site());
717 if repr_i64 {
718 quote! {
719 pub type #name = Option<#enum_name>;
720 #[derive(Clone, PartialEq, Debug, Serialize_repr, Deserialize_repr)]
721 #serde_rename
722 #[repr(i64)]
723 pub enum #enum_name {
724 #(#variants),*
725 }
726 }
727 } else {
728 quote! {
729 pub type #name = Option<#enum_name>;
730 #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
731 #serde_rename
732 pub enum #enum_name {
733 #(#variants),*
734 }
735 }
736 }
737 } else if repr_i64 {
738 quote! {
739 #[derive(Clone, PartialEq, Debug, Serialize_repr, Deserialize_repr)]
740 #serde_rename
741 #[repr(i64)]
742 pub enum #name {
743 #(#variants),*
744 }
745 }
746 } else {
747 quote! {
748 #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
749 #serde_rename
750 pub enum #name {
751 #(#variants),*
752 }
753 }
754 }
755 } else {
756 let typ = self
757 .expand_type("", true, schema)
758 .typ
759 .parse::<TokenStream>()
760 .unwrap();
761 if name == typ.to_string() {
763 return TokenStream::new();
764 }
765 return quote! {
766 pub type #name = #typ;
767 };
768 };
769 type_decl
770 }
771
772 pub fn expand(&mut self, schema: &Schema) -> TokenStream {
773 match self.root_name {
774 Some(name) => {
775 let schema = self.expand_schema(name, schema);
776 self.types.push((name.to_string(), schema));
777 }
778 None => self.expand_definitions(schema),
779 }
780
781 let types = self.types.iter().map(|t| &t.1);
782
783 quote! {
784 #( #types )*
785 }
786 }
787
788 pub fn expand_root(&mut self) -> TokenStream {
789 self.expand(self.root)
790 }
791}
792
793#[cfg(test)]
794mod tests {
795 use super::*;
796
797 #[test]
798 fn test_expander_type_ref() {
799 let json = std::fs::read_to_string("src/schema.json").expect("Read schema JSON file");
800 let schema = serde_json::from_str(&json).unwrap_or_else(|err| panic!("{}", err));
801 let expander = Expander::new(Some("SchemaName"), "::schemafy_core::", &schema);
802
803 assert_eq!(expander.type_ref("normalField"), "NormalField");
804 assert_eq!(expander.type_ref("#"), "SchemaName");
805 assert_eq!(expander.type_ref(""), "SchemaName");
806 assert_eq!(expander.type_ref("1"), "_1");
807 assert_eq!(
808 expander.type_ref("http://example.com/schema.json#"),
809 "SchemaName"
810 );
811 assert_eq!(
812 expander.type_ref("http://example.com/normalField#withFragment"),
813 "WithFragment"
814 );
815 assert_eq!(
816 expander.type_ref("http://example.com/normalField#withFragment/and/path"),
817 "Path"
818 );
819 assert_eq!(
820 expander.type_ref("http://example.com/normalField?with¶ms#andFragment/and/path"),
821 "Path"
822 );
823 assert_eq!(expander.type_ref("#/only/Fragment"), "Fragment");
824
825 assert_eq!(expander.type_ref("ref"), "Ref");
827 assert_eq!(expander.type_ref("_"), "");
828 assert_eq!(expander.type_ref("thieves' tools"), "ThievesTools");
829 assert_eq!(
830 expander.type_ref("http://example.com/normalField?with¶ms=1"),
831 "NormalFieldWithParams1"
832 );
833 }
834
835 #[test]
836 fn embedded_type_names() {
837 use std::collections::HashSet;
838
839 let json = std::fs::read_to_string("tests/multiple-property-types.json")
840 .expect("Read schema JSON file");
841 let schema = serde_json::from_str(&json).unwrap();
842 let mut expander = Expander::new(Some("Root"), "UNUSED", &schema);
843 expander.expand(&schema);
844
845 let types = expander
848 .types
849 .iter()
850 .map(|v| v.0.as_str())
851 .collect::<HashSet<&str>>();
852 assert!(types.contains("RootItemAC"));
853 assert!(types.contains("RootKM"));
854 assert!(types.contains("RootTV"));
855 }
856}