Skip to main content

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}