diff --git a/rocketmq-protocol/tests/request_header_codec_v1_ui.rs b/rocketmq-protocol/tests/request_header_codec_v1_ui.rs new file mode 100644 index 000000000..86ec36df0 --- /dev/null +++ b/rocketmq-protocol/tests/request_header_codec_v1_ui.rs @@ -0,0 +1,20 @@ +// Copyright 2026 The RocketMQ Rust Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[test] +fn request_header_codec_v1_legacy_contract_remains_compilable_and_deprecated() { + let tests = trybuild::TestCases::new(); + tests.pass("tests/ui/request_header_codec_v1/pass/*.rs"); + tests.compile_fail("tests/ui/request_header_codec_v1/fail/*.rs"); +} diff --git a/rocketmq-protocol/tests/request_header_codec_v1_wire_snapshot.rs b/rocketmq-protocol/tests/request_header_codec_v1_wire_snapshot.rs new file mode 100644 index 000000000..0c79347d6 --- /dev/null +++ b/rocketmq-protocol/tests/request_header_codec_v1_wire_snapshot.rs @@ -0,0 +1,164 @@ +// Copyright 2026 The RocketMQ Rust Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::BTreeMap; + +use cheetah_string::CheetahString; +use rocketmq_macros::RequestHeaderCodec; +use rocketmq_protocol::{CommandCustomHeader, FromMap, HeaderMap}; +use serde::{Deserialize, Serialize}; + +// RequestHeaderCodec V1 expands these paths relative to its consuming crate. +pub mod protocol { + pub mod command_custom_header { + pub use rocketmq_protocol::{CommandCustomHeader, FromMap}; + } +} + +#[allow( + deprecated, + reason = "freezes the legacy RequestHeaderCodec V1 nested-header expansion" +)] +mod legacy_nested_header { + use super::*; + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, RequestHeaderCodec)] + pub struct LegacyNestedHeader { + pub nested_flag: bool, + } +} + +use legacy_nested_header::LegacyNestedHeader; + +#[allow( + deprecated, + reason = "freezes the legacy RequestHeaderCodec V1 wire and decode contract" +)] +mod legacy_header { + use super::*; + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, RequestHeaderCodec)] + pub struct LegacyHeader { + #[required] + pub request_name: String, + #[required] + pub request_token: CheetahString, + pub attempt_count: Option, + pub optional_label: Option, + pub retry_count: i32, + #[serde(flatten)] + pub nested: LegacyNestedHeader, + } +} + +use legacy_header::LegacyHeader; + +fn sorted(map: &HeaderMap) -> BTreeMap { + map.iter() + .map(|(key, value)| (key.to_string(), value.to_string())) + .collect() +} + +#[test] +fn v1_named_struct_wire_map_and_decode_quirks_are_frozen() { + let header = LegacyHeader { + request_name: "request-name".to_owned(), + request_token: CheetahString::from_static_str("token-7"), + attempt_count: Some(7), + optional_label: Some(CheetahString::from_static_str("label")), + retry_count: 3, + nested: LegacyNestedHeader { nested_flag: true }, + }; + + assert_eq!( + sorted(&header.to_map().expect("legacy header map")), + BTreeMap::from([ + ("attemptCount".to_owned(), "7".to_owned()), + ("nestedFlag".to_owned(), "true".to_owned()), + ("optionalLabel".to_owned(), "label".to_owned()), + ("requestName".to_owned(), "request-name".to_owned()), + ("requestToken".to_owned(), "token-7".to_owned()), + ("retryCount".to_owned(), "3".to_owned()), + ]) + ); + + let decoded = ::from(&HeaderMap::from([ + (CheetahString::from_static_str("requestName"), "request-name".into()), + (CheetahString::from_static_str("requestToken"), "token-7".into()), + (CheetahString::from_static_str("attemptCount"), "invalid".into()), + (CheetahString::from_static_str("optionalLabel"), "label".into()), + (CheetahString::from_static_str("retryCount"), "invalid".into()), + (CheetahString::from_static_str("nestedFlag"), "true".into()), + ])) + .expect("legacy decode"); + + assert_eq!(decoded.request_name, "request-name"); + assert_eq!(decoded.request_token.as_str(), "token-7"); + assert_eq!(decoded.attempt_count, None); + assert_eq!(decoded.optional_label.as_deref(), Some("label")); + assert_eq!(decoded.retry_count, 0); + assert!(decoded.nested.nested_flag); + + let missing_required = ::from(&HeaderMap::new()).expect_err("missing requestName"); + assert!(missing_required.to_string().contains("Missing requestName field")); +} + +#[test] +fn v1_required_decode_order_and_absent_optional_defaults_are_frozen() { + let missing_request_token = ::from(&HeaderMap::from([ + (CheetahString::from_static_str("requestName"), "request-name".into()), + (CheetahString::from_static_str("nestedFlag"), "true".into()), + ])) + .expect_err("requestToken remains required after requestName and nested fields are present"); + assert_eq!( + missing_request_token.to_string(), + "Request header error: Missing requestToken field" + ); + + let decoded = ::from(&HeaderMap::from([ + (CheetahString::from_static_str("requestName"), "request-name".into()), + (CheetahString::from_static_str("requestToken"), "token-7".into()), + (CheetahString::from_static_str("nestedFlag"), "true".into()), + ])) + .expect("optional fields and non-required scalar may be absent"); + + assert_eq!(decoded.attempt_count, None); + assert_eq!(decoded.optional_label, None); + assert_eq!(decoded.retry_count, 0); +} + +#[test] +fn v1_none_optionals_omit_their_wire_keys_with_a_literal_oracle() { + let header = LegacyHeader { + request_name: "request-name".to_owned(), + request_token: CheetahString::from_static_str("token-7"), + attempt_count: None, + optional_label: None, + retry_count: 0, + nested: LegacyNestedHeader { nested_flag: true }, + }; + + let map = header.to_map().expect("legacy header map"); + assert!(!map.contains_key("attemptCount")); + assert!(!map.contains_key("optionalLabel")); + assert_eq!( + sorted(&map), + BTreeMap::from([ + ("nestedFlag".to_owned(), "true".to_owned()), + ("requestName".to_owned(), "request-name".to_owned()), + ("requestToken".to_owned(), "token-7".to_owned()), + ("retryCount".to_owned(), "0".to_owned()), + ]) + ); +} diff --git a/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/deprecated.rs b/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/deprecated.rs new file mode 100644 index 000000000..c5eb51edc --- /dev/null +++ b/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/deprecated.rs @@ -0,0 +1,31 @@ +// Copyright 2026 The RocketMQ Rust Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![deny(deprecated)] + +use cheetah_string::CheetahString; +use rocketmq_macros::RequestHeaderCodec; + +pub mod protocol { + pub mod command_custom_header { + pub use rocketmq_protocol::{CommandCustomHeader, FromMap}; + } +} + +#[derive(RequestHeaderCodec)] +struct DeprecatedHeader { + value: CheetahString, +} + +fn main() {} diff --git a/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/deprecated.stderr b/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/deprecated.stderr new file mode 100644 index 000000000..7d798ae3d --- /dev/null +++ b/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/deprecated.stderr @@ -0,0 +1,11 @@ +error: use of deprecated macro `RequestHeaderCodec`: use RequestHeaderCodecV3 with dedicated #[header(...)] wire metadata + --> tests/ui/request_header_codec_v1/fail/deprecated.rs:26:10 + | +26 | #[derive(RequestHeaderCodec)] + | ^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/request_header_codec_v1/fail/deprecated.rs:15:9 + | +15 | #![deny(deprecated)] + | ^^^^^^^^^^ diff --git a/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs b/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs new file mode 100644 index 000000000..3c05d388f --- /dev/null +++ b/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs @@ -0,0 +1,142 @@ +// Copyright 2026 The RocketMQ Rust Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use rocketmq_macros::{RequestHeaderCodec, RequestHeaderCodecV2}; +use rocketmq_protocol::protocol::header_codec::{HeaderCodec, HeaderFieldSpec}; + +pub mod protocol { + pub mod command_custom_header { + pub use rocketmq_protocol::{CommandCustomHeader, FromMap}; + } +} + +#[allow( + deprecated, + reason = "proves RequestHeaderCodec V1 does not gain a V3 descriptor contract" +)] +mod legacy_v1_header { + use super::*; + + #[derive(RequestHeaderCodec)] + pub struct LegacyV1Header { + value: i32, + } +} + +use legacy_v1_header::LegacyV1Header; + +#[allow( + deprecated, + reason = "proves RequestHeaderCodecV2 does not gain a V3 descriptor contract" +)] +mod legacy_v2_header { + use super::*; + + #[derive(RequestHeaderCodecV2)] + #[request_header_codec_v2(crate = "rocketmq_protocol")] + pub struct LegacyV2Header { + value: i32, + } +} + +use legacy_v2_header::LegacyV2Header; + +#[allow( + deprecated, + reason = "proves silent RequestHeaderCodec V1 tuple expansion produces no legacy codec traits" +)] +mod silent_tuple_header { + use super::*; + + #[derive(RequestHeaderCodec)] + pub struct SilentTupleHeader(i32); +} + +use silent_tuple_header::SilentTupleHeader; + +#[allow( + deprecated, + reason = "proves silent RequestHeaderCodec V1 unit expansion produces no legacy codec traits" +)] +mod silent_unit_header { + use super::*; + + #[derive(RequestHeaderCodec)] + pub struct SilentUnitHeader; +} + +use silent_unit_header::SilentUnitHeader; + +#[allow( + deprecated, + reason = "proves silent RequestHeaderCodec V1 enum expansion produces no legacy codec traits" +)] +mod silent_enum_header { + use super::*; + + #[derive(RequestHeaderCodec)] + pub enum SilentEnumHeader { + V1, + } +} + +use silent_enum_header::SilentEnumHeader; + +trait LocalV3Descriptor { + const TYPE_ID: &'static str; + const LOCAL_FIELD_SPECS: &'static [HeaderFieldSpec]; + + fn visit_field_specs(visitor: &mut dyn FnMut(&HeaderFieldSpec)); +} + +#[diagnostic::do_not_recommend] +impl LocalV3Descriptor for T { + const TYPE_ID: &'static str = T::TYPE_ID; + const LOCAL_FIELD_SPECS: &'static [HeaderFieldSpec] = T::LOCAL_FIELD_SPECS; + + fn visit_field_specs(visitor: &mut dyn FnMut(&HeaderFieldSpec)) { + T::visit_field_specs(visitor); + } +} + +fn requires_v3_descriptor() { + let _ = T::TYPE_ID; + let _ = T::LOCAL_FIELD_SPECS; + T::visit_field_specs(&mut |_| {}); +} + +trait LocalLegacyCommandCustomHeader {} + +#[diagnostic::do_not_recommend] +impl LocalLegacyCommandCustomHeader for T {} + +trait LocalLegacyFromMap {} + +#[diagnostic::do_not_recommend] +impl LocalLegacyFromMap for T {} + +fn requires_legacy_command_custom_header() {} + +fn requires_legacy_from_map() {} + +fn main() { + requires_v3_descriptor::(); + requires_v3_descriptor::(); + requires_legacy_command_custom_header::(); + requires_legacy_command_custom_header::(); + requires_legacy_command_custom_header::(); + requires_legacy_from_map::(); + requires_legacy_from_map::(); + requires_legacy_from_map::(); +} diff --git a/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.stderr b/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.stderr new file mode 100644 index 000000000..6e6ab95ba --- /dev/null +++ b/rocketmq-protocol/tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.stderr @@ -0,0 +1,135 @@ +error[E0277]: the trait bound `LegacyV1Header: LocalV3Descriptor` is not satisfied + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:134:30 + | +134 | requires_v3_descriptor::(); + | ^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `LocalV3Descriptor` is not implemented for `LegacyV1Header` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:32:5 + | + 32 | pub struct LegacyV1Header { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `requires_v3_descriptor` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:113:30 + | +113 | fn requires_v3_descriptor() { + | ^^^^^^^^^^^^^^^^^ required by this bound in `requires_v3_descriptor` + +error[E0277]: the trait bound `LegacyV2Header: LocalV3Descriptor` is not satisfied + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:135:30 + | +135 | requires_v3_descriptor::(); + | ^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `LocalV3Descriptor` is not implemented for `LegacyV2Header` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:48:5 + | + 48 | pub struct LegacyV2Header { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `requires_v3_descriptor` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:113:30 + | +113 | fn requires_v3_descriptor() { + | ^^^^^^^^^^^^^^^^^ required by this bound in `requires_v3_descriptor` + +error[E0277]: the trait bound `SilentTupleHeader: LocalLegacyCommandCustomHeader` is not satisfied + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:136:45 + | +136 | requires_legacy_command_custom_header::(); + | ^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `LocalLegacyCommandCustomHeader` is not implemented for `SilentTupleHeader` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:63:5 + | + 63 | pub struct SilentTupleHeader(i32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `requires_legacy_command_custom_header` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:129:45 + | +129 | fn requires_legacy_command_custom_header() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `requires_legacy_command_custom_header` + +error[E0277]: the trait bound `SilentUnitHeader: LocalLegacyCommandCustomHeader` is not satisfied + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:137:45 + | +137 | requires_legacy_command_custom_header::(); + | ^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `LocalLegacyCommandCustomHeader` is not implemented for `SilentUnitHeader` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:76:5 + | + 76 | pub struct SilentUnitHeader; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `requires_legacy_command_custom_header` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:129:45 + | +129 | fn requires_legacy_command_custom_header() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `requires_legacy_command_custom_header` + +error[E0277]: the trait bound `SilentEnumHeader: LocalLegacyCommandCustomHeader` is not satisfied + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:138:45 + | +138 | requires_legacy_command_custom_header::(); + | ^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `LocalLegacyCommandCustomHeader` is not implemented for `SilentEnumHeader` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:89:5 + | + 89 | pub enum SilentEnumHeader { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `requires_legacy_command_custom_header` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:129:45 + | +129 | fn requires_legacy_command_custom_header() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `requires_legacy_command_custom_header` + +error[E0277]: the trait bound `SilentTupleHeader: LocalLegacyFromMap` is not satisfied + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:139:32 + | +139 | requires_legacy_from_map::(); + | ^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `LocalLegacyFromMap` is not implemented for `SilentTupleHeader` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:63:5 + | + 63 | pub struct SilentTupleHeader(i32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `requires_legacy_from_map` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:131:32 + | +131 | fn requires_legacy_from_map() {} + | ^^^^^^^^^^^^^^^^^^ required by this bound in `requires_legacy_from_map` + +error[E0277]: the trait bound `SilentUnitHeader: LocalLegacyFromMap` is not satisfied + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:140:32 + | +140 | requires_legacy_from_map::(); + | ^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `LocalLegacyFromMap` is not implemented for `SilentUnitHeader` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:76:5 + | + 76 | pub struct SilentUnitHeader; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `requires_legacy_from_map` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:131:32 + | +131 | fn requires_legacy_from_map() {} + | ^^^^^^^^^^^^^^^^^^ required by this bound in `requires_legacy_from_map` + +error[E0277]: the trait bound `SilentEnumHeader: LocalLegacyFromMap` is not satisfied + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:141:32 + | +141 | requires_legacy_from_map::(); + | ^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `LocalLegacyFromMap` is not implemented for `SilentEnumHeader` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:89:5 + | + 89 | pub enum SilentEnumHeader { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `requires_legacy_from_map` + --> tests/ui/request_header_codec_v1/fail/legacy_codecs_do_not_implement_v3_descriptor.rs:131:32 + | +131 | fn requires_legacy_from_map() {} + | ^^^^^^^^^^^^^^^^^^ required by this bound in `requires_legacy_from_map` diff --git a/rocketmq-protocol/tests/ui/request_header_codec_v1/pass/legacy_named_struct_and_silent_non_named.rs b/rocketmq-protocol/tests/ui/request_header_codec_v1/pass/legacy_named_struct_and_silent_non_named.rs new file mode 100644 index 000000000..dd097d58e --- /dev/null +++ b/rocketmq-protocol/tests/ui/request_header_codec_v1/pass/legacy_named_struct_and_silent_non_named.rs @@ -0,0 +1,95 @@ +// Copyright 2026 The RocketMQ Rust Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use cheetah_string::CheetahString; +use rocketmq_macros::RequestHeaderCodec; +use serde::{Deserialize, Serialize}; + +// RequestHeaderCodec V1 expands these paths relative to its consuming crate. +pub mod protocol { + pub mod command_custom_header { + pub use rocketmq_protocol::{CommandCustomHeader, FromMap}; + } +} + +#[allow( + deprecated, + reason = "proves the supported named RequestHeaderCodec V1 expansion remains source-compatible" +)] +mod supported_named_header { + use super::*; + + #[derive(Serialize, Deserialize, RequestHeaderCodec)] + pub struct SupportedNamedHeader { + #[required] + request_id: CheetahString, + attempt_count: Option, + } +} + +use supported_named_header::SupportedNamedHeader; + +#[allow( + deprecated, + reason = "freezes the historical silent RequestHeaderCodec V1 tuple expansion" +)] +mod silent_tuple_header { + use super::*; + + #[derive(RequestHeaderCodec)] + pub struct SilentTupleHeader(pub String); +} + +use silent_tuple_header::SilentTupleHeader; + +#[allow( + deprecated, + reason = "freezes the historical silent RequestHeaderCodec V1 unit expansion" +)] +mod silent_unit_header { + use super::*; + + #[derive(RequestHeaderCodec)] + pub struct SilentUnitHeader; +} + +use silent_unit_header::SilentUnitHeader; + +#[allow( + deprecated, + reason = "freezes the historical silent RequestHeaderCodec V1 enum expansion" +)] +mod silent_enum_header { + use super::*; + + #[derive(RequestHeaderCodec)] + pub enum SilentEnumHeader { + V1, + } +} + +use silent_enum_header::SilentEnumHeader; + +fn requires_legacy_header() +where + T: crate::protocol::command_custom_header::CommandCustomHeader + crate::protocol::command_custom_header::FromMap, +{ +} + +fn main() { + requires_legacy_header::(); + let _ = SilentTupleHeader("V1 silently expands unsupported non-named shapes to nothing".to_owned()); + let _ = SilentUnitHeader; + let _ = SilentEnumHeader::V1; +}