diff --git a/Cargo.lock b/Cargo.lock index 1f02a62..d5062f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,7 @@ dependencies = [ "proc-macro2", "progenitor-impl", "quote", + "schemars", "serde", "serde_json", "serde_tokenstream", @@ -1928,7 +1929,7 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "typify" version = "0.0.11-dev" -source = "git+https://github.com/oxidecomputer/typify#1aefa41396d0aefa1a9dbb32320445ab02a5cbc3" +source = "git+https://github.com/oxidecomputer/typify#da0505b0b43180ee85d8df2a7c2504acb7ce01e0" dependencies = [ "typify-impl", "typify-macro", @@ -1937,7 +1938,7 @@ dependencies = [ [[package]] name = "typify-impl" version = "0.0.11-dev" -source = "git+https://github.com/oxidecomputer/typify#1aefa41396d0aefa1a9dbb32320445ab02a5cbc3" +source = "git+https://github.com/oxidecomputer/typify#da0505b0b43180ee85d8df2a7c2504acb7ce01e0" dependencies = [ "heck", "log", @@ -1955,7 +1956,7 @@ dependencies = [ [[package]] name = "typify-macro" version = "0.0.11-dev" -source = "git+https://github.com/oxidecomputer/typify#1aefa41396d0aefa1a9dbb32320445ab02a5cbc3" +source = "git+https://github.com/oxidecomputer/typify#da0505b0b43180ee85d8df2a7c2504acb7ce01e0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 93bbf21..5539eed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,6 @@ members = [ #typify = { path = "../typify/typify" } #[patch.crates-io] +#serde_tokenstream = { path = "../serde_tokenstream" } #typify = { path = "../typify/typify" } #rustfmt-wrapper = { path = "../rustfmt-wrapper" } diff --git a/progenitor-impl/Cargo.toml b/progenitor-impl/Cargo.toml index 5512031..5e3aee8 100644 --- a/progenitor-impl/Cargo.toml +++ b/progenitor-impl/Cargo.toml @@ -3,8 +3,9 @@ name = "progenitor-impl" version = "0.2.1-dev" edition = "2021" license = "MPL-2.0" -repository = "https://github.com/oxidecomputer/progenitor.git" description = "An OpenAPI client generator - core implementation" +repository = "https://github.com/oxidecomputer/progenitor.git" +readme = "../README.md" [dependencies] heck = "0.4.0" diff --git a/progenitor-impl/release.toml b/progenitor-impl/release.toml new file mode 100644 index 0000000..9ede551 --- /dev/null +++ b/progenitor-impl/release.toml @@ -0,0 +1 @@ +pre-release-replacements = [] diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index b3af7f4..8068f94 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -52,6 +52,7 @@ pub struct GenerationSettings { post_hook: Option, extra_derives: Vec, patch: HashMap, + convert: Vec<(schemars::schema::SchemaObject, String, Vec)>, } #[derive(Clone, Deserialize, PartialEq, Eq)] @@ -122,6 +123,20 @@ impl GenerationSettings { .insert(type_name.as_ref().to_string(), patch.clone()); self } + + pub fn with_conversion>( + &mut self, + schema: schemars::schema::SchemaObject, + type_name: S, + impls: I, + ) -> &mut Self { + self.convert.push(( + schema, + type_name.to_string(), + impls.map(|x| x.to_string()).collect(), + )); + self + } } impl Default for Generator { @@ -149,6 +164,16 @@ impl Generator { settings.patch.iter().for_each(|(type_name, patch)| { type_settings.with_patch(type_name, patch); }); + settings + .convert + .iter() + .for_each(|(schema, type_name, impls)| { + type_settings.with_conversion( + schema.clone(), + type_name, + impls.iter(), + ); + }); Self { type_space: TypeSpace::new(&type_settings), settings: settings.clone(), diff --git a/progenitor-impl/tests/output/keeper-builder.out b/progenitor-impl/tests/output/keeper-builder.out index 43dbe4f..1edb94f 100644 --- a/progenitor-impl/tests/output/keeper-builder.out +++ b/progenitor-impl/tests/output/keeper-builder.out @@ -55,9 +55,9 @@ pub mod types { #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct ReportFinishBody { - pub duration_millis: i32, + pub duration_millis: usize, pub end_time: chrono::DateTime, - pub exit_status: i32, + pub exit_status: usize, pub id: ReportId, } @@ -120,11 +120,11 @@ pub mod types { #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct ReportSummary { - pub age_seconds: i32, - pub duration_seconds: i32, + pub age_seconds: usize, + pub duration_seconds: usize, pub host: String, pub job: String, - pub status: i32, + pub status: usize, pub when: chrono::DateTime, } @@ -324,9 +324,9 @@ pub mod types { } pub struct ReportFinishBody { - duration_millis: Result, + duration_millis: Result, end_time: Result, String>, - exit_status: Result, + exit_status: Result, id: Result, } @@ -344,7 +344,7 @@ pub mod types { impl ReportFinishBody { pub fn duration_millis(mut self, value: T) -> Self where - T: std::convert::TryInto, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.duration_millis = value.try_into().map_err(|e| { @@ -364,7 +364,7 @@ pub mod types { } pub fn exit_status(mut self, value: T) -> Self where - T: std::convert::TryInto, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.exit_status = value @@ -624,11 +624,11 @@ pub mod types { } pub struct ReportSummary { - age_seconds: Result, - duration_seconds: Result, + age_seconds: Result, + duration_seconds: Result, host: Result, job: Result, - status: Result, + status: Result, when: Result, String>, } @@ -648,7 +648,7 @@ pub mod types { impl ReportSummary { pub fn age_seconds(mut self, value: T) -> Self where - T: std::convert::TryInto, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.age_seconds = value @@ -658,7 +658,7 @@ pub mod types { } pub fn duration_seconds(mut self, value: T) -> Self where - T: std::convert::TryInto, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.duration_seconds = value.try_into().map_err(|e| { @@ -691,7 +691,7 @@ pub mod types { } pub fn status(mut self, value: T) -> Self where - T: std::convert::TryInto, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.status = value diff --git a/progenitor-impl/tests/test_output.rs b/progenitor-impl/tests/test_output.rs index 7abb38f..4aa9bc5 100644 --- a/progenitor-impl/tests/test_output.rs +++ b/progenitor-impl/tests/test_output.rs @@ -26,7 +26,18 @@ fn verify_apis(openapi_file: &str) { .with_interface(InterfaceStyle::Builder) .with_tag(TagStyle::Merged) .with_derive("JsonSchema") - .with_patch("Name", TypePatch::default().with_derive("Hash")), + .with_patch("Name", TypePatch::default().with_derive("Hash")) + .with_conversion( + schemars::schema::SchemaObject { + instance_type: Some( + schemars::schema::InstanceType::Integer.into(), + ), + format: Some("int32".to_string()), + ..Default::default() + }, + "usize", + ["Display"].into_iter(), + ), ); let output = generator.generate_text_normalize_comments(&spec).unwrap(); expectorate::assert_contents( diff --git a/progenitor-macro/Cargo.toml b/progenitor-macro/Cargo.toml index 2bf178d..6a08414 100644 --- a/progenitor-macro/Cargo.toml +++ b/progenitor-macro/Cargo.toml @@ -3,18 +3,20 @@ name = "progenitor-macro" version = "0.2.1-dev" edition = "2021" license = "MPL-2.0" -repository = "https://github.com/oxidecomputer/progenitor.git" description = "An OpenAPI client generator - macros" +repository = "https://github.com/oxidecomputer/progenitor.git" +readme = "../README.md" + +[lib] +proc-macro = true [dependencies] openapiv3 = "1.0.0" proc-macro2 = "1.0" progenitor-impl = { version = "0.2.1-dev", path = "../progenitor-impl" } quote = "1.0" +schemars = "0.8.11" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_tokenstream = "0.1.6" syn = "1.0" - -[lib] -proc-macro = true diff --git a/progenitor-macro/release.toml b/progenitor-macro/release.toml new file mode 100644 index 0000000..9ede551 --- /dev/null +++ b/progenitor-macro/release.toml @@ -0,0 +1 @@ +pre-release-replacements = [] diff --git a/progenitor-macro/src/lib.rs b/progenitor-macro/src/lib.rs index 6693c10..3310fd1 100644 --- a/progenitor-macro/src/lib.rs +++ b/progenitor-macro/src/lib.rs @@ -1,6 +1,6 @@ // Copyright 2022 Oxide Computer Company -use std::{collections::HashMap, path::Path}; +use std::{collections::HashMap, fmt::Display, path::Path}; use openapiv3::OpenAPI; use proc_macro::TokenStream; @@ -8,8 +8,9 @@ use progenitor_impl::{ GenerationSettings, Generator, InterfaceStyle, TagStyle, TypePatch, }; use quote::{quote, ToTokens}; +use schemars::schema::SchemaObject; use serde::Deserialize; -use serde_tokenstream::ParseWrapper; +use serde_tokenstream::{OrderedMap, ParseWrapper}; use syn::LitStr; /// Generates a client from the given OpenAPI document @@ -82,6 +83,24 @@ struct MacroSettings { derives: Vec>, #[serde(default)] patch: HashMap, MacroPatch>, + #[serde(default)] + convert: OrderedMap< + SchemaObject, + (ParseWrapper, Vec), + >, +} + +#[derive(Deserialize)] +enum MacroSettingsImpl { + Display, +} + +impl Display for MacroSettingsImpl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MacroSettingsImpl::Display => f.write_str("Display"), + } + } } #[derive(Deserialize)] @@ -153,6 +172,7 @@ fn do_generate_api(item: TokenStream) -> Result { post_hook, derives, patch, + convert, } = serde_tokenstream::from_tokenstream(&item.into())?; let mut settings = GenerationSettings::default(); settings.with_interface(interface); @@ -174,6 +194,15 @@ fn do_generate_api(item: TokenStream) -> Result { &patch.into(), ); }); + convert + .into_iter() + .for_each(|(schema, (type_name, impls))| { + settings.with_conversion( + schema, + type_name.to_token_stream(), + impls.into_iter(), + ); + }); (spec.into_inner(), settings) }; diff --git a/progenitor/Cargo.toml b/progenitor/Cargo.toml index 3acbe8d..a4345ce 100644 --- a/progenitor/Cargo.toml +++ b/progenitor/Cargo.toml @@ -3,8 +3,11 @@ name = "progenitor" version = "0.2.1-dev" edition = "2021" license = "MPL-2.0" -repository = "https://github.com/oxidecomputer/progenitor.git" description = "An OpenAPI client generator" +repository = "https://github.com/oxidecomputer/progenitor.git" +readme = "../README.md" +keywords = ["openapi", "openapiv3", "sdk", "generator", "proc_macro"] +categories = ["api-bindings", "compilers"] [dependencies] progenitor-client = { version = "0.2.1-dev", path = "../progenitor-client" }