Allow deriving MakeSchema on simple structs!
This commit is contained in:
		
							parent
							
								
									fcb9d5d1c0
								
							
						
					
					
						commit
						5228ec65e5
					
				
					 9 changed files with 90 additions and 38 deletions
				
			
		|  | @ -9,3 +9,4 @@ edition = "2018" | ||||||
| [dependencies] | [dependencies] | ||||||
| serde = { version = "1.0", features = ["derive"] } | serde = { version = "1.0", features = ["derive"] } | ||||||
| serde_json = "1.0" | serde_json = "1.0" | ||||||
|  | schemars_derive = { version = "0.1.0", path = "../schemars_derive" } | ||||||
|  |  | ||||||
|  | @ -76,7 +76,6 @@ impl SchemaGenerator { | ||||||
| 
 | 
 | ||||||
|     fn make_unique_name<T: ?Sized + MakeSchema>(&mut self) -> String { |     fn make_unique_name<T: ?Sized + MakeSchema>(&mut self) -> String { | ||||||
|         let base_name = T::schema_name(); |         let base_name = T::schema_name(); | ||||||
|         // TODO remove namespace, remove special chars
 |  | ||||||
|         if self.names.contains(&base_name) { |         if self.names.contains(&base_name) { | ||||||
|             for i in 2.. { |             for i in 2.. { | ||||||
|                 let name = format!("{}{}", base_name, i); |                 let name = format!("{}{}", base_name, i); | ||||||
|  |  | ||||||
|  | @ -1,11 +1,8 @@ | ||||||
|  | pub mod generator; | ||||||
| pub mod make_schema; | pub mod make_schema; | ||||||
| pub mod schema; | pub mod schema; | ||||||
| pub mod generator; |  | ||||||
| 
 | 
 | ||||||
| #[cfg(test)] | pub use generator::SchemaGenerator; | ||||||
| mod tests { | pub use schema::{Schema, SchemaObject, SchemaRef}; | ||||||
|     #[test] | 
 | ||||||
|     fn it_works() { | pub use schemars_derive::*; | ||||||
|         assert_eq!(2 + 2, 4); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,9 +1,4 @@ | ||||||
| pub mod generator; | use schemars::MakeSchema; | ||||||
| pub mod make_schema; |  | ||||||
| pub mod schema; |  | ||||||
| 
 |  | ||||||
| use make_schema::MakeSchema; |  | ||||||
| use schema::*; |  | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::Result; | use serde_json::Result; | ||||||
| 
 | 
 | ||||||
|  | @ -16,43 +11,26 @@ enum TodoStatus { | ||||||
|     Archived, |     Archived, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, Debug)] | #[derive(Serialize, Deserialize, Debug, MakeSchema)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| struct Todo { | struct Todo { | ||||||
|     id: u64, |     id: u64, | ||||||
|     title: String, |     title: String, | ||||||
|     description: Option<String>, |     description: Option<String>, | ||||||
|     status: TodoStatus, |     // status: TodoStatus,
 | ||||||
|     assigned_to: Vec<User>, |     assigned_to: Vec<User>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, Debug)] | #[derive(Serialize, Deserialize, Debug, MakeSchema)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| struct User { | struct User { | ||||||
|     id: u64, |     id: u64, | ||||||
|     username: String, |     username: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl MakeSchema for User { |  | ||||||
|     fn generates_ref_schema() -> bool { |  | ||||||
|         true |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn make_schema(gen: &mut generator::SchemaGenerator) -> Schema { |  | ||||||
|         let mut o = SchemaObject { |  | ||||||
|             ..Default::default() |  | ||||||
|         }; |  | ||||||
|         o.properties |  | ||||||
|             .insert("id".to_owned(), gen.subschema_for::<u64>()); |  | ||||||
|         o.properties |  | ||||||
|             .insert("username".to_owned(), gen.subschema_for::<String>()); |  | ||||||
|         o.into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn main() -> Result<()> { | fn main() -> Result<()> { | ||||||
|     let gen = generator::SchemaGenerator::new(); |     let gen = schemars::SchemaGenerator::new(); | ||||||
|     let schema = gen.into_root_schema_for::<User>(); |     let schema = gen.into_root_schema_for::<Todo>(); | ||||||
|     let json = serde_json::to_string_pretty(&schema)?; |     let json = serde_json::to_string_pretty(&schema)?; | ||||||
|     println!("{}", json); |     println!("{}", json); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,7 +17,7 @@ pub trait MakeSchema { | ||||||
|         // It's probably worth removing the default implemenation,
 |         // It's probably worth removing the default implemenation,
 | ||||||
|         //  then make every impl in this file set an explicit name
 |         //  then make every impl in this file set an explicit name
 | ||||||
|         // Or maybe hide it under feature flag?
 |         // Or maybe hide it under feature flag?
 | ||||||
|         core::any::type_name::<Self>().to_owned() |         core::any::type_name::<Self>().replace(|c: char| !c.is_ascii_alphanumeric(), "_") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn generates_ref_schema() -> bool { |     fn generates_ref_schema() -> bool { | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::Value; | use serde_json::Value; | ||||||
| use std::collections::BTreeMap as Map; | use std::collections::BTreeMap as Map; | ||||||
| 
 | 
 | ||||||
|  | // TODO use serde_json::Map (or some other wrapper) instead of BTreeMap to ensure preserve_order is possible
 | ||||||
|  | 
 | ||||||
| #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | ||||||
| #[serde(untagged)] | #[serde(untagged)] | ||||||
| pub enum Schema { | pub enum Schema { | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								schemars_derive/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								schemars_derive/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | /target | ||||||
|  | **/*.rs.bk | ||||||
|  | Cargo.lock | ||||||
							
								
								
									
										16
									
								
								schemars_derive/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								schemars_derive/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | [package] | ||||||
|  | name = "schemars_derive" | ||||||
|  | version = "0.1.0" | ||||||
|  | authors = ["Graham Esau <gesau@hotmail.co.uk>"] | ||||||
|  | edition = "2018" | ||||||
|  | 
 | ||||||
|  | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  | 
 | ||||||
|  | [lib] | ||||||
|  | proc-macro = true | ||||||
|  | 
 | ||||||
|  | [dependencies] | ||||||
|  | proc-macro2 = "0.4" | ||||||
|  | quote = "0.6.13" | ||||||
|  | syn = { version = "0.15.22", features = ["visit"] } # do we need visit? | ||||||
|  | serde_derive_internals = "0.24.1" | ||||||
							
								
								
									
										56
									
								
								schemars_derive/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								schemars_derive/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | ||||||
|  | #[macro_use] | ||||||
|  | extern crate quote; | ||||||
|  | #[macro_use] | ||||||
|  | extern crate syn; | ||||||
|  | 
 | ||||||
|  | extern crate proc_macro; | ||||||
|  | extern crate proc_macro2; | ||||||
|  | 
 | ||||||
|  | use proc_macro2::TokenStream; | ||||||
|  | use syn::spanned::Spanned; | ||||||
|  | use syn::{Data, DeriveInput, Fields}; | ||||||
|  | 
 | ||||||
|  | #[proc_macro_derive(MakeSchema, attributes(schemars, serde))] | ||||||
|  | pub fn derive_make_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream { | ||||||
|  |     let input = parse_macro_input!(input as DeriveInput); | ||||||
|  | 
 | ||||||
|  |     let name = input.ident; | ||||||
|  |     let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); | ||||||
|  | 
 | ||||||
|  |     let fn_contents = match input.data { | ||||||
|  |         Data::Struct(data) => struct_implementation(&data.fields), | ||||||
|  |         _ => unimplemented!("Only structs work so far!"), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let impl_block = quote! { | ||||||
|  |         #[automatically_derived] | ||||||
|  |         impl #impl_generics schemars::make_schema::MakeSchema for #name #ty_generics #where_clause { | ||||||
|  |             fn make_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { | ||||||
|  |                 let mut o = schemars::SchemaObject { | ||||||
|  |                     ..Default::default() | ||||||
|  |                 }; | ||||||
|  |                 #fn_contents | ||||||
|  |                 o.into() | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     }; | ||||||
|  |     proc_macro::TokenStream::from(impl_block) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn struct_implementation(fields: &Fields) -> TokenStream { | ||||||
|  |     match fields { | ||||||
|  |         Fields::Named(ref fields) => { | ||||||
|  |             let recurse = fields.named.iter().map(|f| { | ||||||
|  |                 let name = (&f.ident).as_ref().map(ToString::to_string); | ||||||
|  |                 let ty = &f.ty; | ||||||
|  |                 quote_spanned! {f.span()=> | ||||||
|  |                     o.properties.insert(#name.to_owned(), gen.subschema_for::<#ty>()); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |             quote! { | ||||||
|  |                 #(#recurse)* | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         _ => unimplemented!("Only named fields work so far!"), | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Graham Esau
						Graham Esau