rocketmq_protocol/protocol/command_custom_header.rs
1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::Any;
16use std::collections::HashMap;
17
18use cheetah_string::CheetahString;
19
20use crate::protocol::header_codec::HeaderFieldSource;
21use crate::protocol::header_codec::ResolvedHeaderKey;
22use crate::rocketmq_serializable::RocketMQSerializable;
23use crate::ProtocolContractViolation;
24
25/// Wire-format extension fields carried by a RocketMQ remoting command.
26pub type HeaderMap = HashMap<CheetahString, CheetahString>;
27
28/// Encoding paths available for a custom header.
29///
30/// This capability is intentionally limited to encoding. Legacy fast decode
31/// remains a separate compatibility concern until all handwritten decoders
32/// have migrated to the typed [`crate::HeaderCodec`] path.
33#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
34#[non_exhaustive]
35pub enum HeaderEncodeCapability {
36 /// Materialize the header into extension fields before serialization.
37 #[default]
38 MapOnly,
39 /// Encode the header directly into the ROCKETMQ extension-field payload.
40 DirectBinary,
41}
42
43pub trait CommandCustomHeader: AsAny {
44 /// Checks the fields of the implementing type.
45 ///
46 /// Returns a typed result indicating whether the fields are valid.
47 ///
48 /// If the fields are valid, the `Ok` variant is returned with an empty `()` value.
49 /// If the fields are invalid, an error governed by a stable
50 /// [`rocketmq_error::ErrorDescriptor`] is returned.
51 fn check_fields(&self) -> rocketmq_error::Result<()> {
52 Ok(())
53 }
54
55 /// Converts the implementing type to a map.
56 ///
57 /// Returns an `Option` that contains a `HashMap` of string keys and string values,
58 /// representing the implementing type's fields.
59 /// If the conversion is successful, a map is returned; explicitly audited
60 /// fieldless headers return an empty map. If conversion fails, `None` is
61 /// returned.
62 fn to_map(&self) -> Option<HashMap<CheetahString, CheetahString>>;
63
64 /// Appends this header's extension fields to an existing map.
65 ///
66 /// Generated implementations write directly into `out`. The default preserves compatibility
67 /// for handwritten implementations.
68 fn encode_into_map(&self, out: &mut HeaderMap) {
69 if let Some(fields) = self.to_map() {
70 out.extend(fields);
71 }
72 }
73
74 /// Validates and appends this header's fields to an existing map.
75 ///
76 /// Typed codecs override this method to preserve classified failures. The
77 /// default adapts handwritten implementations without changing their object-safe
78 /// trait surface or silently treating `None` as a successful conversion.
79 ///
80 /// # Errors
81 ///
82 /// Returns [`ProtocolContractViolation::LegacyValidation`] when `check_fields`
83 /// fails, or [`ProtocolContractViolation::LegacyMapConversionFailed`] when a legacy
84 /// `to_map` implementation returns `None`.
85 fn try_encode_into_map(&self, out: &mut HeaderMap) -> Result<(), ProtocolContractViolation> {
86 self.check_fields()
87 .map_err(|_| ProtocolContractViolation::LegacyValidation {
88 header: std::any::type_name::<Self>(),
89 })?;
90 let fields = self
91 .to_map()
92 .ok_or(ProtocolContractViolation::LegacyMapConversionFailed {
93 header: std::any::type_name::<Self>(),
94 })?;
95 out.extend(fields);
96 Ok(())
97 }
98
99 /// Resolves a canonical key or decode alias for typed headers.
100 fn resolve_wire_key(&self, _key: &str) -> Option<ResolvedHeaderKey> {
101 None
102 }
103
104 /// Resolves a key and returns its canonical wire key.
105 fn canonical_wire_key(&self, key: &str) -> Option<&'static str> {
106 self.resolve_wire_key(key).map(|resolved| resolved.canonical)
107 }
108
109 /// Returns whether a canonical key or alias belongs to this header schema.
110 fn contains_wire_key(&self, key: &str) -> bool {
111 self.resolve_wire_key(key).is_some()
112 }
113
114 /// Returns a saturating extension-field payload capacity hint.
115 fn encoded_len_hint(&self) -> usize {
116 0
117 }
118
119 /// Returns the authoritative encoding path for this header.
120 fn encode_capability(&self) -> HeaderEncodeCapability {
121 HeaderEncodeCapability::MapOnly
122 }
123
124 /// Appends this header directly to a ROCKETMQ extension-field payload.
125 ///
126 /// Implementations must require only shared access so cloned remoting
127 /// commands can encode the same immutable header. Callers must check
128 /// [`Self::encode_capability`] before invoking this method. Implementations
129 /// are responsible for validating the header before the first write; the
130 /// frame entrypoint does not pre-validate because map and binary encoders
131 /// already own the authoritative validation boundary.
132 ///
133 /// # Errors
134 ///
135 /// Returns a classified header codec error when validation or binary
136 /// representation fails. The default reports that direct encoding is
137 /// unavailable.
138 fn encode_direct_binary(&self, _out: &mut bytes::BytesMut) -> Result<(), ProtocolContractViolation> {
139 Err(ProtocolContractViolation::FastCodecUnavailable {
140 header: std::any::type_name::<Self>(),
141 })
142 }
143
144 /// Returns whether this header can write its complete JSON extension-field
145 /// object without first materializing a [`HeaderMap`].
146 fn supports_direct_json_fields(&self) -> bool {
147 false
148 }
149
150 /// Writes this header as one complete JSON extension-field object.
151 ///
152 /// Callers must check [`Self::supports_direct_json_fields`] before invoking
153 /// this method. Implementations must validate before writing and must leave
154 /// rollback to the enclosing frame encoder.
155 ///
156 /// # Errors
157 ///
158 /// Returns a classified header codec error when direct JSON encoding is
159 /// unavailable or validation fails.
160 fn encode_direct_json_fields(&self, _out: &mut bytes::BytesMut) -> Result<(), ProtocolContractViolation> {
161 Err(ProtocolContractViolation::FastCodecUnavailable {
162 header: std::any::type_name::<Self>(),
163 })
164 }
165
166 /// Writes the provided `key` to the `out` buffer if the `value` is not empty.
167 ///
168 /// # Arguments
169 ///
170 /// * `out` - A mutable reference to a `BytesMut` buffer where the `key` will be written.
171 /// * `key` - A string slice that represents the key to be written.
172 /// * `value` - A string slice that represents the value associated with the key.
173 ///
174 /// # Behavior
175 ///
176 /// If `value` is not empty, the function will write the `key` to the `out` buffer twice,
177 /// first with a short length prefix and then with a long length prefix.
178 fn write_if_not_null(&self, out: &mut bytes::BytesMut, key: &str, value: &str) {
179 if !value.is_empty() {
180 RocketMQSerializable::write_str(out, true, key);
181 RocketMQSerializable::write_str(out, false, value);
182 }
183 }
184
185 /// Compatibility adapter for the legacy mutable fast-encoding API.
186 ///
187 /// Production serialization uses [`Self::encode_capability`] and
188 /// [`Self::encode_direct_binary`] directly. This adapter cannot propagate
189 /// errors and remains only for existing callers during migration.
190 fn encode_fast(&mut self, out: &mut bytes::BytesMut) {
191 let _ = self.encode_direct_binary(out);
192 }
193
194 /// A placeholder function for fast decoding.
195 ///
196 /// This function currently does nothing and can be overridden by implementing types.
197 ///
198 /// # Arguments
199 ///
200 /// * `_fields` - A reference to a `HashMap` that contains the fields to be decoded.
201 fn decode_fast(&mut self, _fields: &HashMap<CheetahString, CheetahString>) -> rocketmq_error::Result<()> {
202 Ok(())
203 }
204
205 /// Indicates whether the implementing type supports fast codec.
206 ///
207 /// # Returns
208 ///
209 /// This legacy capability is limited to hand-written fast decoding. Direct
210 /// binary encoding is reported independently by [`Self::encode_capability`].
211 /// The default remains `false` so enabling generated direct encoding cannot
212 /// route decoding through an unimplemented legacy hook.
213 fn support_fast_codec(&self) -> bool {
214 false
215 }
216
217 /// Retrieves the value associated with the specified field from the provided map.
218 ///
219 /// # Arguments
220 ///
221 /// * `map` - A reference to a `HashMap` containing `CheetahString` keys and values.
222 /// * `field` - A reference to a `CheetahString` representing the field to retrieve.
223 ///
224 /// # Returns
225 ///
226 /// * `Ok(CheetahString)` - If the field is found in the map, returns the associated value.
227 /// * `Err(Error)` - If the field is not found in the map, returns an error indicating the
228 /// field is required.
229 ///
230 /// # Errors
231 ///
232 /// This function returns a serialization error if the specified field is not found in the
233 /// map.
234 #[inline(always)]
235 fn get_and_check_not_none(
236 &self,
237 map: &HashMap<CheetahString, CheetahString>,
238 field: &CheetahString,
239 ) -> rocketmq_error::Result<CheetahString> {
240 match map.get(field) {
241 Some(value) => Ok(value.clone()),
242 None => Err(crate::error::serialization_decode_failed(
243 "header",
244 format!("The field {field} is required."),
245 )),
246 }
247 }
248
249 /// Retrieves a borrowed value associated with the specified field from the provided map.
250 #[inline(always)]
251 fn get_and_check_not_none_ref<'a>(
252 &self,
253 map: &'a HashMap<CheetahString, CheetahString>,
254 field: &CheetahString,
255 ) -> rocketmq_error::Result<&'a CheetahString> {
256 match map.get(field) {
257 Some(value) => Ok(value),
258 None => Err(crate::error::serialization_decode_failed(
259 "header",
260 format!("The field {field} is required."),
261 )),
262 }
263 }
264}
265
266pub trait AsAny: Any {
267 fn as_any_mut(&mut self) -> &mut dyn Any;
268
269 fn as_any(&self) -> &dyn Any;
270}
271
272impl<T: CommandCustomHeader> AsAny for T {
273 fn as_any_mut(&mut self) -> &mut dyn Any {
274 self
275 }
276
277 fn as_any(&self) -> &dyn Any {
278 self
279 }
280}
281
282pub trait FromMap {
283 type Error: From<rocketmq_error::Error>;
284
285 type Target;
286
287 /// Whether production decode may call [`Self::from_field_source`] without
288 /// first materializing the compatibility map.
289 const SUPPORTS_HEADER_FIELD_SOURCE: bool = false;
290
291 /// Converts the implementing type from a map.
292 ///
293 /// Returns an instance of `Self::Target` that is created from the provided map.
294 fn from(map: &HashMap<CheetahString, CheetahString>) -> Result<Self::Target, Self::Error>;
295
296 /// Converts the implementing type from a borrowed field source.
297 ///
298 /// Generated request-header implementations override this method. The default is an
299 /// additive compatibility fallback for existing implementations and is not
300 /// selected by production dispatch while the capability remains `false`.
301 fn from_field_source(source: &dyn HeaderFieldSource) -> Result<Self::Target, Self::Error> {
302 let map = source.to_header_map();
303 Self::from(&map)
304 }
305}