diff --git a/conformance/Cargo.toml b/conformance/Cargo.toml index d2c3f6f5e..b2b933b76 100644 --- a/conformance/Cargo.toml +++ b/conformance/Cargo.toml @@ -8,7 +8,7 @@ authors.workspace = true [dependencies] bytes = "1" env_logger = { version = "0.11", default-features = false } -prost = { path = "../prost" } -prost-types = { path = "../prost-types", features = ["any-v2"] } +prost = { path = "../prost", features = ["serde", "serde-json"] } +prost-types = { path = "../prost-types", features = ["serde", "any-v2"] } protobuf = { path = "../protobuf" } tests = { path = "../tests" } diff --git a/prost-build/src/code_generator.rs b/prost-build/src/code_generator.rs index 315f1350b..980f7603e 100644 --- a/prost-build/src/code_generator.rs +++ b/prost-build/src/code_generator.rs @@ -384,9 +384,11 @@ impl<'a> CodeGenerator<'a> { } fn append_serde(&mut self) { - push_indent(self.buf, self.depth); - self.buf.push_str("#[prost(serde)]"); - self.buf.push('\n'); + if self.config.enable_serde { + push_indent(self.buf, self.depth); + self.buf.push_str("#[prost(serde)]"); + self.buf.push('\n'); + } } fn append_enum_attributes(&mut self, fq_message_name: &str) { @@ -512,12 +514,14 @@ impl<'a> CodeGenerator<'a> { let rust_field_name = &field.rust_name(); let proto_field_name = field.descriptor.name(); - if !ident::is_stable_ident_for_json( + let field_name_is_stable_for_json = ident::is_stable_ident_for_json( proto_field_name, IdentKind::MessageField { field: rust_field_name, }, - ) { + ); + + if self.config.enable_serde && !field_name_is_stable_for_json { self.buf .push_str(&format!(", json(proto_name = \"{}\")", proto_field_name)); } @@ -583,12 +587,14 @@ impl<'a> CodeGenerator<'a> { let rust_field_name = &field.rust_name(); let proto_field_name = field.descriptor.name(); - let json_attr = if !ident::is_stable_ident_for_json( + let field_name_is_stable_for_json = ident::is_stable_ident_for_json( proto_field_name, IdentKind::MessageField { field: rust_field_name, }, - ) { + ); + + let json_attr = if self.config.enable_serde && !field_name_is_stable_for_json { format!(", json(proto_name = \"{}\")", proto_field_name) } else { Default::default() @@ -685,12 +691,14 @@ impl<'a> CodeGenerator<'a> { let ty_tag = self.field_type_tag(&field.descriptor); let ty = self.resolve_type(&field.descriptor, fq_message_name); - let json_attr = if !ident::is_stable_ident_for_json( + let variant_name_is_stable_for_json = ident::is_stable_ident_for_json( proto_field_name, IdentKind::OneOfVariant { variant: rust_variant_name, }, - ) { + ); + + let json_attr = if self.config.enable_serde && !variant_name_is_stable_for_json { format!(", json(proto_name = \"{}\")", proto_field_name) } else { Default::default() @@ -809,15 +817,18 @@ impl<'a> CodeGenerator<'a> { self.append_doc(&fq_proto_enum_name, Some(variant.proto_name)); - let emit_proto_names = !variant.proto_aliases.is_empty() - || !ident::is_stable_ident_for_json( - variant.proto_name, - IdentKind::EnumVariant { - ty: &enum_name, - variant: &variant.generated_variant_name, - }, - ); - if emit_proto_names { + let variant_name_is_stable_for_json = ident::is_stable_ident_for_json( + variant.proto_name, + IdentKind::EnumVariant { + ty: &enum_name, + variant: &variant.generated_variant_name, + }, + ); + + let emit_proto_names = + !variant.proto_aliases.is_empty() || !variant_name_is_stable_for_json; + + if self.config.enable_serde && emit_proto_names { self.push_indent(); let names = iter::once(variant.proto_name) diff --git a/prost-build/src/config.rs b/prost-build/src/config.rs index 5087cd418..e7875e4f6 100644 --- a/prost-build/src/config.rs +++ b/prost-build/src/config.rs @@ -52,6 +52,7 @@ pub struct Config { pub(crate) prost_path: Option, #[cfg(feature = "format")] pub(crate) fmt: bool, + pub(crate) enable_serde: bool, } impl Config { @@ -1126,6 +1127,14 @@ impl Config { *buf = with_generated; } } + + /// Configures the code generator to also emit serde compatible serialization impls. + /// + /// Defaults to `false`. + pub fn enable_serde(&mut self) -> &mut Self { + self.enable_serde = true; + self + } } /// Write a slice as the entire contents of a file. @@ -1176,6 +1185,7 @@ impl default::Default for Config { prost_path: None, #[cfg(feature = "format")] fmt: true, + enable_serde: false, } } } diff --git a/prost-build/src/lib.rs b/prost-build/src/lib.rs index 1e74d0c96..bf6396832 100644 --- a/prost-build/src/lib.rs +++ b/prost-build/src/lib.rs @@ -359,6 +359,7 @@ mod tests { let tempdir = tempfile::tempdir().unwrap(); Config::new() + .enable_serde() .service_generator(Box::new(ServiceTraitGenerator)) .out_dir(tempdir.path()) .compile_protos(&["src/fixtures/smoke_test/smoke_test.proto"], &["src"]) @@ -374,6 +375,7 @@ mod tests { let gen = MockServiceGenerator::new(Rc::clone(&state)); Config::new() + .enable_serde() .service_generator(Box::new(gen)) .include_file("_protos.rs") .out_dir(tempdir.path()) @@ -398,6 +400,8 @@ mod tests { let tempdir = tempfile::tempdir().unwrap(); let mut config = Config::new(); + config.enable_serde(); + config .out_dir(tempdir.path()) // Add attributes to all messages and enums @@ -453,6 +457,7 @@ mod tests { let previously_empty_proto_path = tempdir.path().join(Path::new("google.protobuf.rs")); Config::new() + .enable_serde() .service_generator(Box::new(gen)) .include_file(include_file) .out_dir(tempdir.path()) @@ -486,6 +491,7 @@ mod tests { let tempdir = tempfile::tempdir().unwrap(); Config::new() + .enable_serde() .out_dir(tempdir.path()) .boxed("Container.data.foo") .boxed("Bar.qux") @@ -527,6 +533,7 @@ mod tests { let tempdir = tempfile::tempdir().unwrap(); Config::new() + .enable_serde() .service_generator(Box::new(gen)) .include_file(include_file) .out_dir(tempdir.path()) @@ -579,6 +586,7 @@ mod tests { let mut buf = Vec::new(); Config::new() + .enable_serde() .default_package_filename("_.default") .write_includes(modules.iter().collect(), &mut buf, None, &file_names) .unwrap(); diff --git a/prost-types/Cargo.toml b/prost-types/Cargo.toml index 32fe6d1ec..be81ceac8 100644 --- a/prost-types/Cargo.toml +++ b/prost-types/Cargo.toml @@ -13,7 +13,7 @@ rust-version.workspace = true doctest = false [features] -default = ["std", "serde", "any-v2"] +default = ["std"] std = ["prost/std"] serde = ["prost/serde"] any-v2 = ["std", "serde", "prost/serde-json", "dep:serde", "dep:erased-serde"] diff --git a/prost/Cargo.toml b/prost/Cargo.toml index 70abe5f6e..ef3045c05 100644 --- a/prost/Cargo.toml +++ b/prost/Cargo.toml @@ -16,7 +16,7 @@ rust-version.workspace = true bench = false [features] -default = ["derive", "std", "serde", "serde-json"] +default = ["derive", "std"] derive = ["dep:prost-derive"] prost-derive = ["derive"] # deprecated, please use derive feature instead no-recursion-limit = [] diff --git a/protobuf/build.rs b/protobuf/build.rs index 4b831b1ce..8fac63cab 100644 --- a/protobuf/build.rs +++ b/protobuf/build.rs @@ -37,11 +37,13 @@ fn main() -> Result<()> { } let conformance_proto_dir = src_dir.join("conformance"); - prost_build::compile_protos( - &[conformance_proto_dir.join("conformance.proto")], - &[conformance_proto_dir], - ) - .unwrap(); + prost_build::Config::new() + .enable_serde() + .compile_protos( + &[conformance_proto_dir.join("conformance.proto")], + &[conformance_proto_dir], + ) + .unwrap(); let proto_dir = src_dir.join("src"); @@ -51,6 +53,7 @@ fn main() -> Result<()> { // values. prost_build::Config::new() .btree_map(["."]) + .enable_serde() .compile_protos( &[ proto_dir.join("google/protobuf/test_messages_proto2.proto"), diff --git a/tests/single-include/Cargo.toml b/tests/single-include/Cargo.toml index 54408a0f1..665dbeb59 100644 --- a/tests/single-include/Cargo.toml +++ b/tests/single-include/Cargo.toml @@ -7,7 +7,7 @@ publish = false license = "MIT" [dependencies] -prost = { path = "../../prost" } +prost = { path = "../../prost", features = ["serde"] } [build-dependencies] prost-build = { path = "../../prost-build" } diff --git a/tests/single-include/build.rs b/tests/single-include/build.rs index 5432d8b5c..ad6b335ac 100644 --- a/tests/single-include/build.rs +++ b/tests/single-include/build.rs @@ -2,11 +2,13 @@ use prost_build::Config; fn main() { Config::new() + .enable_serde() .include_file("lib.rs") .compile_protos(&["protos/search.proto"], &["protos"]) .unwrap(); Config::new() + .enable_serde() .out_dir("src/outdir") .include_file("mod.rs") .compile_protos(&["protos/outdir.proto"], &["protos"]) diff --git a/tests/src/build.rs b/tests/src/build.rs index 796157980..e41d58030 100644 --- a/tests/src/build.rs +++ b/tests/src/build.rs @@ -25,6 +25,7 @@ fn main() { // compare based on the Rust PartialEq implementations is difficult, due to presence of NaN // values. let mut config = prost_build::Config::new(); + config.enable_serde(); config.btree_map(["."]); // Tests for custom attributes config.type_attribute("Foo.Bar_Baz.Foo_barBaz", "#[derive(Eq, PartialOrd, Ord)]"); @@ -128,12 +129,14 @@ fn main() { .unwrap(); prost_build::Config::new() + .enable_serde() .protoc_arg("--experimental_allow_proto3_optional") .compile_protos(&[src.join("proto3_presence.proto")], includes) .unwrap(); { let mut config = prost_build::Config::new(); + config.enable_serde(); config.disable_comments(["."]); config @@ -152,6 +155,7 @@ fn main() { std::fs::create_dir_all(&out_path).unwrap(); prost_build::Config::new() + .enable_serde() .bytes(["."]) .out_dir(out_path) .include_file("wellknown_include.rs") @@ -166,6 +170,7 @@ fn main() { .unwrap(); prost_build::Config::new() + .enable_serde() .enable_type_names() .type_name_domain([".type_names.Foo"], "tests") .compile_protos(&[src.join("type_names.proto")], includes) @@ -184,6 +189,7 @@ fn main() { fs::create_dir_all(&no_root_packages).expect("failed to create prefix directory"); let mut no_root_packages_config = prost_build::Config::new(); no_root_packages_config + .enable_serde() .out_dir(&no_root_packages) .default_package_filename("__.default") .include_file("__.include.rs") @@ -199,6 +205,7 @@ fn main() { fs::create_dir_all(&no_root_packages_with_default).expect("failed to create prefix directory"); let mut no_root_packages_config = prost_build::Config::new(); no_root_packages_config + .enable_serde() .out_dir(&no_root_packages_with_default) .compile_protos( &[src.join("no_root_packages/widget_factory.proto")],