diff --git a/core/src/extractor.rs b/core/src/extractor.rs index 66e7d87..2534247 100644 --- a/core/src/extractor.rs +++ b/core/src/extractor.rs @@ -35,7 +35,13 @@ pub fn extract_spec(spec: &OpenAPI) -> Result { } pub fn is_optional(name: &str, param: &Schema, parent: &Schema) -> bool { - param.nullable || !parent.required().iter().any(|s| s == name) + if param.nullable { + return true; + } + let Some(req) = parent.get_required() else { + return false; + }; + !req.iter().any(|s| s == name) } pub fn extract_request_schema<'a>( diff --git a/libninja/src/rust.rs b/libninja/src/rust.rs index a485677..ea5f001 100644 --- a/libninja/src/rust.rs +++ b/libninja/src/rust.rs @@ -23,7 +23,6 @@ use mir::{DateSerialization, IntegerSerialization}; use mir::Ident; use mir_rust::{sanitize_filename, ToRustCode}; use mir_rust::ToRustIdent; -use mir_rust::codegen_function; use crate::{add_operation_models, extract_spec, OutputConfig, PackageConfig}; use crate::rust::client::{build_Client_authenticate, server_url}; @@ -397,10 +396,8 @@ fn write_request_module(spec: &HirSpec, opts: &PackageConfig) -> Result<()> { let mut import = Import::new(&fname, struct_names); import.vis = Visibility::Public; imports.push(import); - let builder_methods = build_request_struct_builder_methods(&operation); - let builder_methods = builder_methods - .into_iter() - .map(|s| codegen_function(s, quote! { mut self , })); + let builder_methods = build_request_struct_builder_methods(&operation) + .into_iter().map(|s| s.to_rust_code()); let assign_inputs = assign_inputs_to_request(&operation.parameters); diff --git a/libninja/src/rust/lower_hir.rs b/libninja/src/rust/lower_hir.rs index c47f2c8..02c0129 100644 --- a/libninja/src/rust/lower_hir.rs +++ b/libninja/src/rust/lower_hir.rs @@ -38,7 +38,6 @@ impl FieldExt for HirField { }); } } - dbg!(self.optional); if self.optional { decorators.push(quote! { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -262,7 +261,7 @@ pub fn create_sumtype_struct(schema: &Struct, config: &ConfigFlags, spec: &HirSp #dummy #[derive(Debug, Clone, Serialize, Deserialize #default)] pub struct #name { - #(#fields)* + #(#fields,)* } impl std::fmt::Display for #name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { diff --git a/libninja/src/rust/request.rs b/libninja/src/rust/request.rs index 5ec1d5d..872bb94 100644 --- a/libninja/src/rust/request.rs +++ b/libninja/src/rust/request.rs @@ -217,11 +217,12 @@ pub fn build_request_struct_builder_methods( doc: Some(Doc(format!("Set the value of the {} field.", name.0))), name, args: vec![ + FnArg2::SelfArg { mutable: true, reference: false }, FnArg2::Basic { name: a.name.to_rust_ident(), ty: arg_type, default: None, - } + }, ], ret: quote! {Self}, body, @@ -250,9 +251,6 @@ pub fn build_request_struct( // }, // ); - // let mut instance_methods = vec![build_send_function(operation, spec)]; - // let mut_self_instance_methods = build_request_struct_builder_methods(operation); - let fn_name = operation.name.to_rust_ident().0; let response = operation.ret.to_rust_type().to_string().replace(" ", ""); let client = opt.client_name().to_rust_struct().to_string().replace(" ", ""); @@ -263,10 +261,6 @@ On request success, this will return a [`{response}`]."#, ))); name: operation.request_struct_name().to_rust_struct(), doc, instance_fields, - instance_methods: Vec::new(), - mut_self_instance_methods: Vec::new(), - // We need this lifetime because we hold a ref to the client. - // lifetimes: vec!["'a".to_string()], lifetimes: vec![], public: true, decorators: vec![quote! {#[derive(Debug, Clone, Serialize, Deserialize)]}], diff --git a/libninja/tests/basic/main.rs b/libninja/tests/basic/main.rs index 39c35d8..a5f5e65 100644 --- a/libninja/tests/basic/main.rs +++ b/libninja/tests/basic/main.rs @@ -1,14 +1,15 @@ use std::fs::File; +use std::path::PathBuf; use std::str::FromStr; use anyhow::Result; +use openapiv3::OpenAPI; +use pretty_assertions::assert_eq; + use hir::{HirSpec, Language}; use libninja::{generate_library, rust}; +use ln_core::{OutputConfig, PackageConfig}; use ln_core::extractor::{extract_api_operations, extract_inputs, extract_spec}; -use ln_core::{PackageConfig, OutputConfig}; -use openapiv3::OpenAPI; -use pretty_assertions::assert_eq; -use std::path::PathBuf; const BASIC: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/spec/basic.yaml"); const RECURLY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/spec/recurly.yaml"); diff --git a/mir/src/lib.rs b/mir/src/lib.rs index 518f422..a830439 100644 --- a/mir/src/lib.rs +++ b/mir/src/lib.rs @@ -79,13 +79,11 @@ pub struct Class { pub code: Option, pub instance_fields: Vec>, pub static_fields: Vec>, - pub instance_methods: Vec>, pub constructors: Vec>, pub class_methods: Vec>, pub static_methods: Vec>, pub public: bool, - pub mut_self_instance_methods: Vec>, pub lifetimes: Vec, pub decorators: Vec, pub superclasses: Vec, @@ -158,12 +156,10 @@ impl Default for Class { doc: None, instance_fields: vec![], static_fields: vec![], - instance_methods: vec![], constructors: vec![], class_methods: vec![], static_methods: vec![], public: false, - mut_self_instance_methods: vec![], lifetimes: vec![], decorators: vec![], superclasses: vec![], @@ -178,12 +174,10 @@ impl Debug for Class { doc: {doc:?}, \ instance_fields: todo!, \ static_fields: todo!, \ - instance_methods: todo!, \ constructors: todo!, \ class_methods: todo!, \ static_methods: todo!, \ public: {public}, \ - mut_self_instance_methods: todo!, \ lifetimes: todo!, \ superclasses: todo! }}", name = self.name, diff --git a/mir_rust/src/class.rs b/mir_rust/src/class.rs index 55312a5..0b2f10b 100644 --- a/mir_rust/src/class.rs +++ b/mir_rust/src/class.rs @@ -1,26 +1,15 @@ -use mir::{Class, Function}; -use proc_macro2::{TokenStream, Span}; +use proc_macro2::{Span, TokenStream}; use quote::quote; -use crate::{ToRustCode, ToRustIdent}; + +use mir::{Class, Field}; + +use crate::{FluentBool, ToRustCode, ToRustIdent}; impl ToRustCode for Class { fn to_rust_code(self) -> TokenStream { - let vis = self.public.then(|| quote!(pub)).unwrap_or_default(); - let fields = self.instance_fields.iter().map(|f| { - let name = &f.name.to_rust_ident(); - let ty = &f.ty; - let public = f.vis.to_rust_code(); - quote! { #public #name: #ty } - }); - let instance_methods = self.instance_methods.into_iter().map(|m| - codegen_function(m, quote! { self , }) - ); - let mut_self_instance_methods = self.mut_self_instance_methods.into_iter().map(|m| { - codegen_function(m, quote! { mut self , }) - }); - let class_methods = self.class_methods.into_iter().map(|m| { - codegen_function(m, TokenStream::new()) - }); + let vis = self.public.to_value(|| quote!(pub)); + let fields = self.instance_fields.into_iter().map(|f| f.to_rust_code()); + let class_methods = self.class_methods.into_iter().map(|m| m.to_rust_code()); let doc = self.doc.to_rust_code(); let lifetimes = if self.lifetimes.is_empty() { @@ -42,26 +31,31 @@ impl ToRustCode for Class { #vis struct #name #lifetimes { #(#fields,)* } - impl #lifetimes #name #lifetimes{ - #(#instance_methods)* - #(#mut_self_instance_methods)* + impl #lifetimes #name #lifetimes { #(#class_methods)* } } } } -pub fn codegen_function(func: Function, self_arg: TokenStream) -> TokenStream { - let name = func.name; - let args = func.args.into_iter().map(|a| a.to_rust_code()); - let ret = func.ret; - let async_ = func.async_.then(|| quote!(async)).unwrap_or_default(); - let vis = func.public.then(|| quote!(pub)).unwrap_or_default(); - let body = &func.body; - quote! { - #vis #async_ fn #name(#self_arg #(#args),*) -> #ret { - #body +impl ToRustCode for Field { + fn to_rust_code(self) -> TokenStream { + let name = self.name.to_rust_ident(); + let ty = if self.optional { + let ty = self.ty; + quote! { Option<#ty> } + } else { + self.ty + }; + let vis = self.vis.to_rust_code(); + let doc = self.doc.to_rust_code(); + let decorators = self.decorators; + quote! { + #doc + #( + #decorators + )* + #vis #name: #ty } } -} - +} \ No newline at end of file diff --git a/mir_rust/src/function.rs b/mir_rust/src/function.rs new file mode 100644 index 0000000..dc4473c --- /dev/null +++ b/mir_rust/src/function.rs @@ -0,0 +1,56 @@ +use proc_macro2::TokenStream; +use quote::quote; +use mir::{FnArg2, Function}; +use crate::{FluentBool, ToRustCode}; + +impl ToRustCode for Function { + fn to_rust_code(self) -> TokenStream { + let Function { + name, + args, + body, + doc, + async_, + annotations, + ret, + public, + .. + } = self; + let annotations = annotations + .into_iter() + .map(|a| syn::parse_str::(&a).unwrap()); + let doc = doc.to_rust_code(); + + let vis = public.to_value(|| quote!(pub)); + let async_ = async_.to_value(|| quote!(async)); + let args = args.into_iter().map(|a| a.to_rust_code()); + let ret = (!ret.is_empty()).to_value(|| quote!( -> #ret)); + quote! { + #(#[ #annotations ])* + #doc + #vis #async_ fn #name(#(#args),*) #ret { + #body + } + } + } +} + +impl ToRustCode for FnArg2 { + fn to_rust_code(self) -> TokenStream { + match self { + FnArg2::Basic { name, ty, default } => { + if default.is_some() { + panic!("No default args in Rust") + } + quote!(#name: #ty) + } + FnArg2::Unpack { .. } => panic!("unpack args not yet supported in Rust"), + FnArg2::SelfArg { reference, mutable } => { + let mutability = mutable.then(|| quote!(mut)).unwrap_or_default(); + let reference = reference.then(|| quote!(&)).unwrap_or_default(); + quote!(#reference #mutability self) + } + FnArg2::Variadic { .. } | FnArg2::Kwargs { .. } => panic!("No variadic or kwargs args in Rust"), + } + } +} diff --git a/mir_rust/src/lib.rs b/mir_rust/src/lib.rs index 882a0e4..2d9b693 100644 --- a/mir_rust/src/lib.rs +++ b/mir_rust/src/lib.rs @@ -1,19 +1,34 @@ +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream; +use quote::quote; +use regex::{Captures, Regex}; + +use mir::{Doc, Ident, Literal, ParamKey, Visibility}; + mod file; mod class; mod import; - -use mir::{Function, Visibility, FnArg2, Doc, Field, Literal, ParamKey, Ident}; -use proc_macro2::{TokenStream}; -use quote::quote; -use regex::{Captures, Regex}; -use convert_case::{Casing, Case}; -pub use class::codegen_function; +mod function; /// Use this for codegen structs: Function, Class, etc. pub trait ToRustCode { fn to_rust_code(self) -> TokenStream; } +pub trait FluentBool { + fn to_value(self, f: impl FnOnce() -> T) -> T; +} + +impl FluentBool for bool { + fn to_value(self, f: impl FnOnce() -> T) -> T { + if self { + f() + } else { + Default::default() + } + } +} + impl ToRustCode for Visibility { fn to_rust_code(self) -> TokenStream { match self { @@ -24,42 +39,6 @@ impl ToRustCode for Visibility { } } -impl ToRustCode for Function { - fn to_rust_code(self) -> TokenStream { - let Function { - name, - args, - body, - doc, - async_, - annotations, - ret, - public, - .. - } = self; - let annotations = annotations - .into_iter() - .map(|a| syn::parse_str::(&a).unwrap()); - let doc = doc.to_rust_code(); - let vis = public.then(|| quote!(pub)).unwrap_or_default(); - let async_ = async_.then(|| quote!(async)).unwrap_or_default(); - let args = args.into_iter().map(|a| a.to_rust_code()); - let ret = ret.is_empty().then(|| quote!(-> #ret)).unwrap_or_default(); - quote! { - #(#[ #annotations ])* - #doc - #vis #async_ fn #name(#(#args),*) #ret { - #body - } - } - } -} - -impl ToRustCode for FnArg2 { - fn to_rust_code(self) -> TokenStream { - todo!() - } -} impl ToRustCode for Option { fn to_rust_code(self) -> TokenStream { @@ -68,28 +47,7 @@ impl ToRustCode for Option { Some(Doc(doc)) => { let doc = doc.trim(); quote!(#[doc = #doc]) - }, - } - } -} -impl ToRustCode for Field { - fn to_rust_code(self) -> TokenStream { - let name = self.name.to_rust_ident(); - let ty = if self.optional { - let ty = self.ty; - quote! { Option<#ty> } - } else { - self.ty - }; - let vis = self.vis.to_rust_code(); - let doc = self.doc.to_rust_code(); - let decorators = self.decorators; - quote! { - #doc - #( - #decorators - )* - #vis #name: #ty, + } } } }