diff --git a/README.md b/README.md index 0c625123..bfb4cd21 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ Options: Generate exhaustively matchable enums and unions [default: false] [possible values: true, false] --serializeEmptyCollections[=] Include empty collection fields in serialized output [default: false] [possible values: true, false] + --useLegacyErrorSerialization[=] + Serialize service error parameters in their legacy stringified form [default: true] [possible values: true, false] + --publicFields[=] + Make struct fields public [default: false] [possible values: true, false] --stripPrefix Strip a prefix from types's package paths --productName @@ -39,14 +43,14 @@ Options: --extraManifestJson Extra manifest configuration as a JSON object. This JSON will be converted to TOML and merged into the generated Cargo.toml manifest. - + Example: --extraManifestJson '{ "package": { "publish": ["some-registry-name"], "license": "MIT" }, "dependencies": { "serde": { "version": "1.0", "features": ["default"] } }, "features": { "fancy-feature": ["foo", "bar"] } }' - + Use single quotes to avoid shell escaping issues. -h, --help Print help diff --git a/conjure-codegen/src/context.rs b/conjure-codegen/src/context.rs index 9665575b..3ba15296 100644 --- a/conjure-codegen/src/context.rs +++ b/conjure-codegen/src/context.rs @@ -61,6 +61,7 @@ pub struct Context { exhaustive: bool, serialize_empty_collections: bool, use_legacy_error_serialization: bool, + public_fields: bool, strip_prefix: Vec, version: Option, } @@ -71,6 +72,7 @@ impl Context { exhaustive: bool, serialize_empty_collections: bool, use_legacy_error_serialization: bool, + public_fields: bool, strip_prefix: Option<&str>, version: Option<&str>, ) -> Context { @@ -79,6 +81,7 @@ impl Context { exhaustive, serialize_empty_collections, use_legacy_error_serialization, + public_fields, strip_prefix: vec![], version: version.map(str::to_owned), }; @@ -133,6 +136,10 @@ impl Context { self.use_legacy_error_serialization } + pub fn public_fields(&self) -> bool { + self.public_fields + } + fn needs_box(&self, def: &Type) -> bool { match def { Type::Primitive(_) => false, diff --git a/conjure-codegen/src/lib.rs b/conjure-codegen/src/lib.rs index 1ed0f160..f2176bf5 100644 --- a/conjure-codegen/src/lib.rs +++ b/conjure-codegen/src/lib.rs @@ -111,6 +111,9 @@ //! assert_eq!(object.coin(), true); //! ``` //! +//! By default, struct fields are private and have accessor methods. The [`Config::public_fields`] option can be set to +//! make the fields public. +//! //! The generated structs implement `Debug`, `Clone`, `PartialEq`, Eq, `PartialOrd`, `Ord`, `Hash`, `Serialize`, and //! `Deserialize`. They implement `Copy` if they consist entirely of copyable primitive types. //! @@ -329,6 +332,7 @@ pub struct Config { exhaustive: bool, serialize_empty_collections: bool, use_legacy_error_serialization: bool, + public_fields: bool, strip_prefix: Option, version: Option, build_crate: Option, @@ -348,6 +352,7 @@ impl Config { exhaustive: false, serialize_empty_collections: false, use_legacy_error_serialization: true, + public_fields: false, strip_prefix: None, version: None, build_crate: None, @@ -395,6 +400,17 @@ impl Config { self } + /// Controls visibility of fields in objects. + /// + /// If enabled, fields will be public, but the struct will be annotated `#[non_exhaustive]`. If disabled, fields + /// will be private and accessor methods will be defined. + /// + /// Defaults to `false`. + pub fn public_fields(&mut self, public_fields: bool) -> &mut Config { + self.public_fields = public_fields; + self + } + /// Sets a prefix that will be stripped from package names. /// /// Defaults to `None`. @@ -488,6 +504,7 @@ impl Config { self.exhaustive, self.serialize_empty_collections, self.use_legacy_error_serialization, + self.public_fields, self.strip_prefix.as_deref(), self.version .as_deref() diff --git a/conjure-codegen/src/objects.rs b/conjure-codegen/src/objects.rs index 93d813bc..ef831591 100644 --- a/conjure-codegen/src/objects.rs +++ b/conjure-codegen/src/objects.rs @@ -1,3 +1,5 @@ +use std::iter; + // Copyright 2021 Palantir Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,6 +49,10 @@ pub fn generate(ctx: &Context, base_module: BaseModule, def: &ObjectDefinition) // The derive attr has to be before the educe attr, so insert rather than push type_attrs.insert(0, quote!(#[derive(#(#derives),*)])); + if ctx.public_fields() { + type_attrs.push(quote!(#[non_exhaustive])); + } + let field_attrs = def.fields().iter().map(|s| { let builder_attr = field_builder_attr(ctx, base_module, def, s); let serde_attr = serde_field_attr(ctx, def, s); @@ -61,13 +67,27 @@ pub fn generate(ctx: &Context, base_module: BaseModule, def: &ObjectDefinition) } else { quote!() }; + let (docs, deprecated) = if ctx.public_fields() { + (ctx.docs(s.docs()), ctx.deprecated(s.docs())) + } else { + (quote!(), quote!()) + }; quote! { #builder_attr #serde_attr #educe_attr + #docs + #deprecated } }); + + let pub_ = if ctx.public_fields() { + quote!(pub) + } else { + quote!() + }; + let pub_ = iter::repeat(pub_); let fields = def.fields().iter().map(|f| ctx.field_name(f.field_name())); let boxed_types = &def .fields() @@ -78,6 +98,10 @@ pub fn generate(ctx: &Context, base_module: BaseModule, def: &ObjectDefinition) let constructor = generate_constructor(ctx, base_module, def); let accessors = def.fields().iter().map(|s| { + if ctx.public_fields() { + return quote!(); + } + let docs = ctx.docs(s.docs()); let deprecated = ctx.deprecated(s.deprecated()); let name = ctx.field_name(s.field_name()); @@ -106,7 +130,7 @@ pub fn generate(ctx: &Context, base_module: BaseModule, def: &ObjectDefinition) pub struct #name { #( #field_attrs - #fields: #boxed_types, + #pub_ #fields: #boxed_types, )* } diff --git a/conjure-rust/src/main.rs b/conjure-rust/src/main.rs index 586b877e..ccd5b74d 100644 --- a/conjure-rust/src/main.rs +++ b/conjure-rust/src/main.rs @@ -58,6 +58,16 @@ struct Args { action = ArgAction::Set, )] use_legacy_error_serialization: bool, + /// Make struct fields public + #[clap( + long, + default_missing_value = "true", + default_value = "false", + num_args = 0..=1, + require_equals = true, + action = ArgAction::Set, + )] + public_fields: bool, /// Strip a prefix from types's package paths #[clap(long, value_name = "prefix")] strip_prefix: Option, @@ -107,7 +117,8 @@ fn main() { config .exhaustive(args.exhaustive) .serialize_empty_collections(args.serialize_empty_collections) - .use_legacy_error_serialization(args.use_legacy_error_serialization); + .use_legacy_error_serialization(args.use_legacy_error_serialization) + .public_fields(args.public_fields); if let Some(prefix) = args.strip_prefix { config.strip_prefix(prefix); } diff --git a/conjure-test/build.rs b/conjure-test/build.rs index ad3a1c1f..083015a5 100644 --- a/conjure-test/build.rs +++ b/conjure-test/build.rs @@ -15,6 +15,7 @@ fn main() { conjure_codegen::Config::new() .strip_prefix("com.palantir.conjure".to_string()) .exhaustive(true) + .public_fields(true) .generate_files(input, output) .unwrap(); }