From 200b1dad07ac7f1b1d026547cf28f2d45d457e99 Mon Sep 17 00:00:00 2001 From: Kurt Wolf Date: Mon, 18 Dec 2023 14:29:37 -0500 Subject: [PATCH] add more docs --- core/src/extractor.rs | 4 +-- core/src/extractor/record.rs | 16 ++++++++--- core/src/options.rs | 2 ++ hir/src/lib.rs | 4 +++ libninja/src/command/generate.rs | 3 ++ libninja/src/rust.rs | 2 +- libninja/src/rust/cargo_toml.rs | 3 ++ libninja/src/rust/codegen.rs | 17 +++++------ libninja/src/rust/codegen/typ.rs | 21 ++++++++------ libninja/src/rust/lower_mir.rs | 43 +++++++++++++++++++--------- libninja/tests/all_of/main.rs | 14 +++++---- libninja/tests/all_of/transaction.rs | 2 +- libninja/tests/basic/main.rs | 7 +++-- libninja/tests/regression/main.rs | 4 +-- 14 files changed, 91 insertions(+), 51 deletions(-) diff --git a/core/src/extractor.rs b/core/src/extractor.rs index a353e02..27e447b 100644 --- a/core/src/extractor.rs +++ b/core/src/extractor.rs @@ -198,12 +198,12 @@ pub fn extract_operation_doc(operation: &oa::Operation, format: DocFormat) -> Op } } -pub fn extract_schema_docs(schema: &oa::Schema) -> Option { +pub fn extract_schema_docs(schema: &Schema) -> Option { schema .schema_data .description .as_ref() - .map(|d| Doc(d.clone())) + .map(|d| Doc(d.trim().to_string())) } pub fn make_name_from_method_and_url(method: &str, url: &str) -> String { diff --git a/core/src/extractor/record.rs b/core/src/extractor/record.rs index cfc7adb..e7dd0e8 100644 --- a/core/src/extractor/record.rs +++ b/core/src/extractor/record.rs @@ -40,8 +40,8 @@ pub fn effective_length(all_of: &[ReferenceOr]) -> usize { for schema_ref in all_of { length += schema_ref.as_ref_str().map(|_s| 1).unwrap_or_default(); length += schema_ref.as_item() - .and_then(|s| s.properties() ) - .map(|s| s.iter().len() ) + .and_then(|s| s.properties()) + .map(|s| s.iter().len()) .unwrap_or_default(); } length @@ -53,7 +53,12 @@ pub fn create_record(name: &str, schema: &Schema, spec: &OpenAPI) -> Record { // The base case, a regular object SchemaKind::Type(Type::Object(ObjectType { properties, .. })) => { let fields = properties_to_fields(properties, schema, spec); - Record::Struct(Struct { name, fields, nullable: schema.schema_data.nullable }) + Record::Struct(Struct { + name, + fields, + nullable: schema.schema_data.nullable, + docs: schema.schema_data.description.as_ref().map(|d| Doc(d.trim().to_string())), + }) } // An enum SchemaKind::Type(Type::String(StringType { enumeration, .. })) @@ -65,6 +70,7 @@ pub fn create_record(name: &str, schema: &Schema, spec: &OpenAPI) -> Record { .iter() .map(|s| s.to_string()) .collect(), + docs: schema.schema_data.description.as_ref().map(|d| Doc(d.clone())), }) } // A newtype with multiple fields @@ -89,6 +95,7 @@ pub fn create_record(name: &str, schema: &Schema, spec: &OpenAPI) -> Record { example: None, flatten: false, }], + docs: schema.schema_data.description.as_ref().map(|d| Doc(d.clone())), }), } } @@ -140,6 +147,7 @@ fn create_record_from_all_of(name: &str, all_of: &[ReferenceOr], schema_ nullable: schema_data.nullable, name: name.to_string(), fields, + docs: schema_data.description.as_ref().map(|d| Doc(d.clone())), }) } @@ -154,7 +162,7 @@ pub fn extract_records(spec: &OpenAPI, result: &mut HirSpec) -> Result<()> { result.schemas.insert(name, rec); } - for (name, schema_ref) in spec.schemas() { + for (name, schema_ref) in spec.schemas() { let Some(reference) = schema_ref.as_ref_str() else { continue; }; result.schemas.insert(name.clone(), Record::TypeAlias(name.clone(), create_field(&schema_ref, spec))); } diff --git a/core/src/options.rs b/core/src/options.rs index af43c79..7f5fcf4 100644 --- a/core/src/options.rs +++ b/core/src/options.rs @@ -8,6 +8,8 @@ use hir::Language; pub struct ConfigFlags { /// Only for Rust. Adds ormlite::TableMeta flags to the code. pub ormlite: bool, + /// Only for Rust (for now). Adds fake::Dummy flags to the code. + pub fake: bool } #[derive(Debug, Clone)] diff --git a/hir/src/lib.rs b/hir/src/lib.rs index 76a79a6..9cbf963 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -222,12 +222,14 @@ pub struct Struct { pub name: String, pub nullable: bool, pub fields: BTreeMap, + pub docs: Option, } #[derive(Debug, Clone)] pub struct NewType { pub name: String, pub fields: Vec, + pub docs: Option, } #[derive(Debug, Clone)] @@ -241,6 +243,7 @@ pub struct TypeAlias { pub struct StrEnum { pub name: String, pub variants: Vec, + pub docs: Option, } /// an object type in the HIR @@ -498,6 +501,7 @@ impl Operation { nullable: false, name: self.required_struct_name(), fields, + docs: None, } } } diff --git a/libninja/src/command/generate.rs b/libninja/src/command/generate.rs index e7f7943..ef9011f 100644 --- a/libninja/src/command/generate.rs +++ b/libninja/src/command/generate.rs @@ -11,6 +11,8 @@ use ln_core::{ConfigFlags}; pub enum Config { /// Only used by Rust. Adds ormlite::TableMeta flags to the code. Ormlite, + /// Only used by Rust (for now). Adds fake::Dummy flags to the code. + Fake, } fn build_config(configs: &[Config]) -> ConfigFlags { @@ -18,6 +20,7 @@ fn build_config(configs: &[Config]) -> ConfigFlags { for c in configs { match c { Config::Ormlite => config.ormlite = true, + Config::Fake => config.fake = true, } } config diff --git a/libninja/src/rust.rs b/libninja/src/rust.rs index fbbb363..b2f3c82 100644 --- a/libninja/src/rust.rs +++ b/libninja/src/rust.rs @@ -44,7 +44,7 @@ pub struct Extras { date_serialization: bool, currency: bool, integer_date_serialization: bool, - basic_auth: bool + basic_auth: bool, } impl Extras { diff --git a/libninja/src/rust/cargo_toml.rs b/libninja/src/rust/cargo_toml.rs index 4ee1b4e..50d5ab3 100644 --- a/libninja/src/rust/cargo_toml.rs +++ b/libninja/src/rust/cargo_toml.rs @@ -61,6 +61,9 @@ pub fn update_cargo_toml(extras: &Extras, opts: &OutputConfig, context: &HashMap if opts.config.ormlite { ensure_dependency(&mut m.dependencies, "ormlite", "0.16.0", &["decimal"]); } + if opts.config.fake { + ensure_dependency(&mut m.dependencies, "fake", "2.9", &["derive", "chrono", "rust_decimal", "http", "uuid"]); + } if extras.basic_auth { ensure_dependency(&mut m.dependencies, "base64", "0.21.0", &[]); } diff --git a/libninja/src/rust/codegen.rs b/libninja/src/rust/codegen.rs index 7da8c16..c66a2a4 100644 --- a/libninja/src/rust/codegen.rs +++ b/libninja/src/rust/codegen.rs @@ -277,7 +277,10 @@ impl ToRustCode for Option { fn to_rust_code(self) -> TokenStream { match self { None => TokenStream::new(), - Some(Doc(doc)) => quote!(#[doc = #doc]), + Some(Doc(doc)) => { + let doc = doc.trim(); + quote!(#[doc = #doc]) + }, } } } @@ -312,7 +315,7 @@ pub fn to_rust_example_value(ty: &Ty, name: &str, spec: &HirSpec, use_ref_value: let record = spec.get_record(model)?; let force_ref = model.ends_with("Required"); match record { - Record::Struct(Struct { name: _name, fields, nullable }) => { + Record::Struct(Struct { name: _name, fields, nullable, docs: _docs }) => { let fields = fields.iter().map(|(name, field)| { let not_ref = !force_ref || field.optional; let mut value = to_rust_example_value(&field.ty, name, spec, !not_ref)?; @@ -325,14 +328,14 @@ pub fn to_rust_example_value(ty: &Ty, name: &str, spec: &HirSpec, use_ref_value: let model = model.to_rust_struct(); quote!(#model{#(#fields),*}) } - Record::NewType(NewType { name, fields }) => { + Record::NewType(NewType { name, fields, docs: _docs }) => { let fields = fields.iter().map(|f| { to_rust_example_value(&f.ty, name, spec, false) }).collect::, _>>()?; let name = name.to_rust_struct(); quote!(#name(#(#fields),*)) } - Record::Enum(StrEnum { name, variants }) => { + Record::Enum(StrEnum { name, variants, docs: _docs }) => { let variant = variants.first().unwrap(); let variant = variant.to_rust_struct(); let model = model.to_rust_struct(); @@ -452,12 +455,6 @@ fn assert_valid_ident(s: &str, original: &str) { } } -/// This is for testing more than anything else -pub fn formatted_code(code: impl ToRustCode) -> String { - let code = code.to_rust_code(); - format::format_code(code).unwrap() -} - #[cfg(test)] mod tests { use mir::{Ident, import, Import}; diff --git a/libninja/src/rust/codegen/typ.rs b/libninja/src/rust/codegen/typ.rs index 30f695f..4cce37d 100644 --- a/libninja/src/rust/codegen/typ.rs +++ b/libninja/src/rust/codegen/typ.rs @@ -1,14 +1,15 @@ use proc_macro2::TokenStream; use quote::quote; -use hir::Ty; +use hir::{HirSpec, Ty}; use crate::rust::codegen::ToRustIdent; +use crate::rust::lower_mir::HirFieldExt; /// Use this to generate Rust code types. pub trait ToRustType { fn to_rust_type(&self) -> TokenStream; fn to_reference_type(&self, specifier: TokenStream) -> TokenStream; fn is_reference_type(&self) -> bool; - fn implements_default(&self) -> bool; + fn implements_default(&self, spec: &HirSpec) -> bool; } impl ToRustType for Ty { @@ -67,20 +68,22 @@ impl ToRustType for Ty { } } - - fn implements_default(&self) -> bool { + fn implements_default(&self, spec: &HirSpec) -> bool { match self { Ty::String => true, Ty::Integer { .. } => true, Ty::Float => true, Ty::Boolean => true, Ty::Array(_) => true, - Ty::Model(..) => false, + Ty::Model(name) => { + let model = spec.get_record(name.as_str()).expect("Model not found"); + model.fields().all(|f| f.implements_default(spec)) + } Ty::Unit => true, - Ty::Any => false, - Ty::Date { .. } => false, - Ty::DateTime => false, - Ty::Currency { .. } => false, + Ty::Any => true, + Ty::Date { .. } => true, + Ty::DateTime => true, + Ty::Currency { .. } => true, } } } diff --git a/libninja/src/rust/lower_mir.rs b/libninja/src/rust/lower_mir.rs index 3f4ad25..a447721 100644 --- a/libninja/src/rust/lower_mir.rs +++ b/libninja/src/rust/lower_mir.rs @@ -92,20 +92,20 @@ impl FieldExt for HirField { } pub trait StructExt { - fn implements_default(&self) -> bool; - fn derive_default(&self) -> TokenStream; + fn implements_default(&self, spec: &HirSpec) -> bool; + fn derive_default(&self, spec: &HirSpec) -> TokenStream; fn model_fields<'a>(&'a self, config: &'a ConfigFlags) -> Box> + 'a>; fn ref_target(&self) -> Option; } impl StructExt for Struct { - fn implements_default(&self) -> bool { - self.fields.iter().all(|(_, f)| f.optional || f.ty.implements_default()) + fn implements_default(&self, spec: &HirSpec) -> bool { + self.fields.values().all(|f| f.implements_default(spec)) } - fn derive_default(&self) -> TokenStream { - if self.implements_default() { + fn derive_default(&self, spec: &HirSpec) -> TokenStream { + if self.implements_default(spec) { quote! { , Default } } else { TokenStream::new() @@ -132,6 +132,7 @@ impl StructExt for Struct { visibility: Visibility::Public, decorators, optional, + doc: field.doc.clone(), ..Field::default() } })) @@ -165,6 +166,16 @@ impl RecordExt for Record { } } +pub trait HirFieldExt { + fn implements_default(&self, spec: &HirSpec) -> bool; +} + +impl HirFieldExt for HirField { + fn implements_default(&self, spec: &HirSpec) -> bool { + self.optional || self.ty.implements_default(spec) + } +} + /// Generate a model.rs file that just imports from dependents. pub fn generate_model_rs(spec: &HirSpec, config: &ConfigFlags) -> File { let imports = spec.schemas.keys().map(|name: &String| { @@ -195,8 +206,11 @@ pub fn generate_single_model_file(name: &str, record: &Record, spec: &HirSpec, c if config.ormlite { imports.push(import!("ormlite", TableMeta, IntoArguments)); } + if config.fake { + imports.push(import!("fake", Dummy)); + } File { - code: Some(create_struct(record, config)), + code: Some(create_struct(record, config, spec)), imports, ..File::default() } @@ -207,9 +221,11 @@ pub struct RefTarget { ty: Ty, } -pub fn create_sumtype_struct(schema: &Struct, config: &ConfigFlags) -> TokenStream { - let default = schema.derive_default(); - let ormlite = if config.ormlite { quote! { , TableMeta, IntoArguments } } else { TokenStream::new() }; +pub fn create_sumtype_struct(schema: &Struct, config: &ConfigFlags, spec: &HirSpec) -> TokenStream { + let default = schema.derive_default(spec); + let ormlite = config.ormlite.then(|| { quote! { , TableMeta, IntoArguments } }).unwrap_or_default(); + let dummy = config.fake.then(|| { quote! { , Dummy } }).unwrap_or_default(); + let docs = schema.docs.clone().to_rust_code(); let name = schema.name.to_rust_struct(); let fields = schema.model_fields(config).map(ToRustCode::to_rust_code); @@ -232,7 +248,8 @@ pub fn create_sumtype_struct(schema: &Struct, config: &ConfigFlags) -> TokenStre }).unwrap_or_default(); quote! { - #[derive(Debug, Clone, Serialize, Deserialize #default #ormlite)] + #docs + #[derive(Debug, Clone, Serialize, Deserialize #default #ormlite #dummy)] pub struct #name { #(#fields)* } @@ -292,9 +309,9 @@ pub fn create_typealias(name: &str, schema: &HirField) -> TokenStream { } } -pub fn create_struct(record: &Record, config: &ConfigFlags) -> TokenStream { +pub fn create_struct(record: &Record, config: &ConfigFlags, spec: &HirSpec) -> TokenStream { match record { - Record::Struct(s) => create_sumtype_struct(s, config), + Record::Struct(s) => create_sumtype_struct(s, config, spec), Record::NewType(nt) => create_newtype_struct(nt), Record::Enum(en) => create_enum_struct(en), Record::TypeAlias(name, field) => create_typealias(name, field), diff --git a/libninja/tests/all_of/main.rs b/libninja/tests/all_of/main.rs index fea9c2d..10031b8 100644 --- a/libninja/tests/all_of/main.rs +++ b/libninja/tests/all_of/main.rs @@ -3,7 +3,8 @@ use pretty_assertions::assert_eq; /// Tests that the `allOf` keyword is handled correctly. use ln_core::{ConfigFlags}; -use hir::Record; +use hir::{HirSpec, Record}; +use ln_core::extractor::{extract_api_operations, extract_records}; const TRANSACTION: &str = include_str!("transaction.yaml"); const TRANSACTION_RS: &str = include_str!("transaction.rs"); @@ -19,9 +20,9 @@ fn record_for_schema(name: &str, schema: &str, spec: &OpenAPI) -> Record { record } -fn formatted_code(record: Record) -> String { +fn formatted_code(record: Record, spec: &HirSpec) -> String { let config = ConfigFlags::default(); - let code = libninja::rust::lower_mir::create_struct(&record, &config); + let code = libninja::rust::lower_mir::create_struct(&record, &config, spec); libninja::rust::format::format_code(code).unwrap() } @@ -33,9 +34,10 @@ fn test_transaction() { spec.add_schema("PersonalFinanceCategory", Schema::new_string()); spec.add_schema("TransactionCounterparty", Schema::new_string()); + let mut result = HirSpec::default(); + extract_records(&spec, &mut result).unwrap(); let record = record_for_schema("Transaction", TRANSACTION, &spec); - let code = formatted_code(record); - println!("{}", code); + let code = formatted_code(record, &result); assert_eq!(code, TRANSACTION_RS); } @@ -45,6 +47,6 @@ fn test_nullable_doesnt_deref() { spec.add_schema("RecipientBACS", Schema::new_object()); let record = record_for_schema("PaymentInitiationOptionalRestrictionBacs", RESTRICTION_BACS, &spec); - let code = formatted_code(record); + let code = formatted_code(record, &HirSpec::default()); assert_eq!(code, RESTRICTION_BACS_RS); } \ No newline at end of file diff --git a/libninja/tests/all_of/transaction.rs b/libninja/tests/all_of/transaction.rs index 24f35c1..871556d 100644 --- a/libninja/tests/all_of/transaction.rs +++ b/libninja/tests/all_of/transaction.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Transaction { #[serde(flatten)] pub transaction_base: TransactionBase, diff --git a/libninja/tests/basic/main.rs b/libninja/tests/basic/main.rs index 2840026..39c35d8 100644 --- a/libninja/tests/basic/main.rs +++ b/libninja/tests/basic/main.rs @@ -2,7 +2,7 @@ use std::fs::File; use std::str::FromStr; use anyhow::Result; -use hir::Language; +use hir::{HirSpec, Language}; use libninja::{generate_library, rust}; use ln_core::extractor::{extract_api_operations, extract_inputs, extract_spec}; use ln_core::{PackageConfig, OutputConfig}; @@ -39,8 +39,9 @@ fn test_generate_example() -> Result<()> { config: Default::default(), dest: PathBuf::from_str("..").unwrap(), }; - let operations = extract_api_operations(&spec).unwrap(); - let operation = operations + let mut result = HirSpec::default(); + extract_api_operations(&spec, &mut result).unwrap(); + let operation = result.operations .iter() .find(|o| o.name == "linkTokenCreate") .unwrap(); diff --git a/libninja/tests/regression/main.rs b/libninja/tests/regression/main.rs index 8e11718..5624bc4 100644 --- a/libninja/tests/regression/main.rs +++ b/libninja/tests/regression/main.rs @@ -1,6 +1,6 @@ use openapiv3::{OpenAPI, Schema}; -use hir::Record; +use hir::{HirSpec, Record}; use libninja::rust::lower_mir::StructExt; const LINK_TOKEN_CREATE: &str = include_str!("link_token_create.yaml"); @@ -23,5 +23,5 @@ fn test_link_token_create() { let Record::Struct(struc) = record else { panic!("expected struct"); }; - assert!(struc.implements_default()); + assert!(struc.implements_default(&HirSpec::default())); } \ No newline at end of file