Skip to main content

tor_netdoc/encode/
derive.rs

1//! Deriving `NetdocEncodable`
2
3use super::*;
4
5use derive_deftly::{define_derive_deftly, define_derive_deftly_module};
6
7/// Not `Copy`, used to detect when other arguments come after a `rest` field
8///
9/// Implementation detail of the encoding derives.
10#[doc(hidden)]
11#[allow(clippy::exhaustive_structs)]
12pub struct RestMustComeLastMarker;
13
14/// Displays `T` using the `fmt` function `F`
15///
16/// Implementation detail of the encoding derives.
17#[doc(hidden)]
18#[allow(clippy::exhaustive_structs)]
19pub struct DisplayHelper<'t, T, F>(pub &'t T, pub F)
20where
21    F: Fn(&T, &mut fmt::Formatter) -> fmt::Result;
22
23impl<'t, T, F> Display for DisplayHelper<'t, T, F>
24where
25    F: Fn(&T, &mut fmt::Formatter) -> fmt::Result,
26{
27    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28        self.1(self.0, f)
29    }
30}
31
32define_derive_deftly_module! {
33    /// Common definitions for `NetdocEncodable` and `NetdocEncodableFields`
34    ///
35    /// Importer must also import `NetdocSomeItemsDeriveCommon` and `NetdocDeriveAnyCommon`.
36    NetdocSomeItemsEncodableCommon beta_deftly:
37
38    ${define P { $crate::encode }}
39
40    // Suffix for error handling - specifically to add field information.
41    //
42    // Usage:
43    //    some_function().$BUG_CONTEXT?;
44    ${define BUG_CONTEXT {
45        // We use .map_err() rather than .bug_context() so that we nail down the error type
46        map_err(|bug: Bug| bug.bug_context(
47            ${concat "in netdoc " $ttype ", in field " $F_KEYWORD_REPORT}
48        ))
49    }}
50
51    // Body of an encoding function.
52    //
53    //    | <- macro conditions and loops are aligned starting here
54    //    |         | <- we line up the normal Rust statements starting here
55    ${define ENCODE_ITEMS_BODY {
56                    $EMIT_DEBUG_PLACEHOLDER
57
58          // Add an item with keyword $F_KEYWORD_STR and value `item`
59          ${define ENCODE_ITEM_VALUE {
60                    #[allow(unused_mut)]
61                    let mut item_out = out.item($F_KEYWORD_STR);
62            ${if fmeta(netdoc(with)) {
63                    ${fmeta(netdoc(with)) as path}
64                        ::${paste_spanned $fname write_item_value_onto}
65                        (item, item_out)
66                        .$BUG_CONTEXT?;
67            } else if fmeta(netdoc(single_arg)) {
68                    selector.${paste_spanned $fname check_item_argument_encodable}();
69                    ItemArgument::${paste_spanned $fname write_arg_onto}
70                        (item, &mut item_out)
71                        .$BUG_CONTEXT?;
72                    item_out.finish();
73            } else {
74                    selector.${paste_spanned $fname check_item_value_encodable}();
75                    ItemValueEncodable
76                        ::${paste_spanned $fname write_item_value_onto}
77                        (item, item_out)
78                        .$BUG_CONTEXT?;
79            }}
80          }}
81
82          // Bind `selector` to an appropriate selector ZST.
83          ${define LET_SELECTOR {
84                         let selector = MultiplicitySelector::<$ftype>::default();
85                         let selector = selector.selector();
86          }}
87
88          ${for fields {
89                    { // Rust block for bindings for this field (notably `selector`, `item`
90
91            // ignore #[deftly(netdoc(default))] precisely like NetdocSomeItemsParseableCommon
92            ${if not(F_INTRO) {
93                ${if fmeta(netdoc(default)) {}}
94            }}
95
96            ${select1
97              F_INTRO {
98                        #[allow(unused)] // `with` can make this unused
99                        let selector = SingletonMultiplicitySelector::<$ftype>::default();
100                        let item = &self.$fname;
101                        $ENCODE_ITEM_VALUE
102              }
103              F_NORMAL {
104                        $LET_SELECTOR
105                        for item in selector.${paste_spanned $fname iter_ordered}(&self.$fname) {
106                            $ENCODE_ITEM_VALUE
107                        }
108              }
109              F_FLATTEN {
110                        <$ftype as NetdocEncodableFields>::encode_fields
111                            (&self.$fname, out)
112                            .$BUG_CONTEXT?;
113              }
114              F_SUBDOC {
115                        $LET_SELECTOR
116                        selector.${paste_spanned $fname check_netdoc_encodable}();
117                        for subdoc in selector.iter_ordered(&self.$fname) {
118                            NetdocEncodable::encode_unsigned(subdoc, out)
119                                .$BUG_CONTEXT?;
120                        }
121              }
122              F_SKIP {
123              }
124            }
125                    } // field block.
126          }} // ${for fields ..}
127
128                    Ok(())
129    }}
130}
131
132define_derive_deftly! {
133    use NetdocDeriveAnyCommon;
134    use NetdocEntireDeriveCommon;
135    use NetdocSomeItemsDeriveCommon;
136    use NetdocSomeItemsEncodableCommon;
137
138    /// Derive [`NetdocEncodable`] for a document (or sub-document)
139    ///
140    // NB there is very similar wording in the NetdocParseable derive docs.
141    // If editing any of this derive's documentation, considering editing that too.
142    //
143    // We could conceivably template this, but without a `$///` string templater in derive-deftly
144    // that would be very tiresome, and it might be a bad idea anyway.
145    //
146    /// ### Expected input structure
147    ///
148    /// Should be applied named-field struct, where each field is
149    /// an Item which may appear in the document,
150    /// or a sub-document.
151    ///
152    /// The first field will be the document's intro Item.
153    /// The output Keyword for each Item will be kebab-case of the field name.
154    ///
155    /// ### Field type
156    ///
157    /// Each field must be
158    ///  * `impl `[`ItemValueEncodable`] for an "exactly once" field,
159    ///  * `Vec<T: ItemValueEncodable>` for "zero or more", or
160    ///  * `BTreeSet<T: ItemValueEncodable + Ord>`, or
161    ///  * `Option<T: ItemValueEncodable>` for "zero or one".
162    ///
163    /// We don't directly support "at least once"; if the value is empty,
164    /// the encoder will produce a structurally correct but semantically invalid document.
165    ///
166    /// (Multiplicity is implemented via types in the [`multiplicity`] module,
167    /// specifically [`MultiplicitySelector`] and [`MultiplicityMethods`].)
168    ///
169    /// ### Signed documents
170    ///
171    /// TODO NETDOC ENCODE this is not yet supported.
172    ///
173    /// ### Top-level attributes:
174    ///
175    /// * **`#[deftly(netdoc(signatures))]`**:
176    ///
177    ///   This type is the signatures section of another document.
178    ///   TODO NETDOC ENCODE This is not yet supported, and will fail to compile.
179    ///
180    $DOC_DEBUG_PLACEHOLDER
181    ///
182    /// # **`#[deftly(netdoc(doctype_for_error = EXPRESSION))]`**:
183    ///
184    ///   Ignored.  (The encoder does not report errors this way.)
185    ///
186    ///   Accepted for alignment with `NetdocParseable`,
187    ///   so that a struct which only conditionally derives `NetdocParseable`
188    ///   does not need to conditionally mark this attribute.
189    ///
190    /// ### Field-level attributes:
191    ///
192    /// * **`#[deftly(netdoc(keyword = STR))]`**:
193    ///
194    ///   Use `STR` as the Keyword for this Item.
195    ///
196    /// * **`#[deftly(netdoc(single_arg))]`**:
197    ///
198    ///   The field type implements `ItemArgument`,
199    ///   instead of `ItemValueEncodable`,
200    ///   and is encoded as if `(FIELD_TYPE,)` had been written.
201    ///
202    /// * **`#[deftly(netdoc(with = MODULE))]`**:
203    ///
204    ///   Instead of `ItemValueEncodable`, the item is parsed with
205    ///   `MODULE::write_item_value_onto`,
206    ///   which must have the same signature as [`ItemValueEncodable::write_item_value_onto`].
207    ///
208    ///   (Not supported for sub-documents, signature items, or field collections.)
209    ///
210    /// * **`#[deftly(netdoc(flatten))]`**:
211    ///
212    ///   This field is a struct containing further individual normal fields.
213    ///   The Items for those individual fields appear in this
214    ///   outer document here, so interspersed with other normal fields.
215    ///
216    ///   The field type must implement [`NetdocEncodableFields`].
217    ///
218    /// * **`#[deftly(netdoc(skip))]`**:
219    ///
220    ///   This field doesn't really appear in the network document.
221    ///   It will be ignored during encoding.
222    ///
223    /// * **`#[deftly(netdoc(subdoc))]`**:
224    ///
225    ///   This field is a sub-document.
226    ///   The value type `T` must implement [`NetdocEncodable`]
227    ///   *instead of* `ItemValueEncodable`.
228    ///
229    ///   The field name is not used for parsging;
230    ///   the sub-document's intro keyword is used instead.
231    ///
232    /// # **`#[deftly(netdoc(default))]`**:
233    ///
234    ///   Ignored.  (The encoder always encodes the field, regardless of the value.)
235    ///
236    ///   Accepted for alignment with `NetdocParseable`.
237    ///
238    /// # Example
239    ///
240    /// TODO NETDOC ENCODE provide an example when signatures are implemented.
241    export NetdocEncodable beta_deftly, for struct, meta_quoted rigorous, expect items:
242
243    impl<$tgens> $P::NetdocEncodable for $ttype {
244        fn encode_unsigned(&self, out: &mut $P::NetdocEncoder) -> $P::Result<(), $P::Bug> {
245            use $P::*;
246
247            $FIELD_ORDERING_CHECK
248            $ENCODE_ITEMS_BODY
249        }
250    }
251}
252
253define_derive_deftly! {
254    use NetdocDeriveAnyCommon;
255    use NetdocFieldsDeriveCommon;
256    use NetdocSomeItemsDeriveCommon;
257    use NetdocSomeItemsEncodableCommon;
258
259    /// Derive [`NetdocEncodableFields`] for a struct with individual items
260    ///
261    /// Similar to
262    /// [`#[derive_deftly(NetdocEncodable)]`](derive_deftly_template_NetdocEncodable),
263    /// but:
264    ///
265    ///  * Derives [`NetdocEncodableFields`]
266    $DOC_NETDOC_FIELDS_DERIVE_SUPPORTED
267    ///
268    export NetdocEncodableFields beta_deftly, for struct, meta_quoted rigorous, expect items:
269
270    impl<$tgens> $P::NetdocEncodableFields for $ttype {
271        fn encode_fields(
272            &self,
273            #[allow(unused)] // Not used if there are no fields.
274            out: &mut $P::NetdocEncoder,
275        ) -> $P::Result<(), $P::Bug> {
276            #[allow(unused)] // Not used if there are no fields.
277            use $P::*;
278
279            $ENCODE_ITEMS_BODY
280        }
281    }
282}
283
284define_derive_deftly! {
285    use NetdocDeriveAnyCommon;
286    use NetdocItemDeriveCommon;
287
288    /// Derive `ItemValueEncodable`
289    ///
290    // NB there is very similar wording in the ItemValuePareable derive docs.
291    // If editing any of this derive's documentation, considering editing that too.
292    //
293    /// Fields in the struct are emitted as keyword line arguments,
294    /// in the order they appear in the struct.
295    ///
296    /// ### Field type
297    ///
298    /// Each field should be:
299    ///
300    ///  * `impl `[`ItemArgument`] (one argument),
301    ///  * `Option<impl ItemArgument>` (one optional argument), or
302    ///  * `Vec<impl ItemArgument + EncodeOrd>` (zero or more arguments).
303    ///  * `BTreeSet<impl ItemArgument>` (zero or more arguments).
304    ///
305    /// `ItemArgument` can be implemented via `impl Display`,
306    /// by writing `impl NormalItemArgument`.
307    ///
308    /// (Multiplicity is implemented via types in the [`multiplicity`] module,
309    /// specifically [`MultiplicitySelector`] and [`MultiplicityMethods`].)
310    ///
311    /// ### Top-level attributes:p
312    ///
313    ///  * **`#[deftly(netdoc(no_extra_args))]**:
314    ///
315    ///    Ignored.
316    ///    (Obviously, the encoder never emits arguments that aren't in the document struct.)
317    ///
318    ///    Accepted for alignment with `ItemValueParseable`,
319    ///    so that a struct which only conditionally derives `ItemValueParseable`
320    ///    does not need to conditionally mark this attribute.
321    ///
322    ///    (May not be combined with `#[deftly(netdoc(rest))]`.)
323    ///
324    $DOC_DEBUG_PLACEHOLDER
325    ///
326    /// ### Field-level attributes:
327    ///
328    ///  * **`#[deftly(netdoc(rest))]**:
329    ///
330    ///    The field is the whole rest of the line.
331    ///    Must come after any other normal argument fields.
332    ///    Only allowed once.
333    ///
334    ///    The field type must implement `ToString` (normally, via `Display`).
335    ///    (I.e. `Vec` , `Option` etc., are not allowed, and `ItemArgumen` is not used.)
336    ///
337    ///  * **`#[deftly(netdoc(object))]**:
338    ///
339    ///    The field is the Object.
340    ///    It must implement [`ItemObjectEncodable`].
341    ///    (or be `Option<impl ItemObjectEncodable>`).
342    ///
343    ///    Only allowed once.
344    ///
345    ///  * **`#[deftly(netdoc(object(label = "LABEL")))]**:
346    ///
347    ///    Sets the expected label for an Object.
348    ///    If not supplied, uses [`ItemObjectEncodable::label`].
349    ///
350    ///  * **`#[deftly(netdoc(with = MODULE)]**:
351    ///
352    ///    Instead of `ItemArgument`, the argument is encoded with `MODULE::write_arg_onto`,
353    ///    which must have the same signature as [`ItemArgument::write_arg_onto`].
354    ///
355    ///    With `#[deftly(netdoc(rest))]`, `MODULE::fmt_args_rest` replaces `Display::fmt`.
356    ///
357    ///    With `#[deftly(netdoc(object))]`, uses `MODULE::write_object_onto`
358    ///    instead of `tor_netdoc::Writeable::write_onto`.
359    ///    LABEL must also be specified unless the object also implements `ItemObjectEncodable`.
360    ///
361    ///  * **`#[deftly(netdoc(sig_hash = HASH_METHOD))]**:
362    ///
363    ///    TODO NETDOC ENCODE.  Encoding of signed documents is not yet implemented.
364    ///
365    ///  * **`#[deftly(netdoc(skip))]**:
366    ///
367    ///    Do not encode this field.
368    export ItemValueEncodable beta_deftly, for struct, meta_quoted rigorous, expect items:
369
370    ${define P { $crate::encode }}
371
372    ${define BUG_CONTEXT {
373        // We use .map_err() rather than .bug_context() so that we nail down the error type
374        map_err(|bug: Bug| bug.bug_context(
375            ${concat "in item " $ttype ", in field " $fname}
376        ))
377    }}
378
379    impl<$tgens> $P::ItemValueEncodable for $ttype {
380        fn write_item_value_onto(
381            &self,
382            #[allow(unused)]
383            mut out: $P::ItemEncoder,
384        ) -> $P::Result<(), $P::Bug> {
385          //  | <- macro conditions and loops are aligned starting here
386          //  |         | <- we line up the normal Rust statements starting here
387                        #[allow(unused_imports)]
388                        use $P::*;
389                        #[allow(unused_imports)]
390                        use tor_error::BugContext as _;
391
392                        $EMIT_DEBUG_PLACEHOLDER
393
394                        // ignore #[deftly(netdoc(doctype_for_error = EXPR))]
395                        let _: &str = ${tmeta(netdoc(doctype_for_error)) as expr, default {""}};
396
397                        #[allow(unused)]
398                        let rest_must_come_last_marker = RestMustComeLastMarker;
399
400              ${for fields {
401                        {
402                ${select1
403                  F_NORMAL {
404                            let _ = &rest_must_come_last_marker;
405                            let selector = MultiplicitySelector::<$ftype>::default();
406                            let selector = selector.selector();
407                      ${if not(fmeta(netdoc(with))) {
408                            selector.${paste_spanned $fname check_item_argument_encodable}();
409                      }}
410                            for arg in selector.iter_ordered(&self.$fname) {
411                      ${fmeta(netdoc(with)) as path, default {
412                                ItemArgument
413                      }}
414                                    ::${paste_spanned $fname write_arg_onto}
415                                    (arg, &mut out)
416                                    .$BUG_CONTEXT?;
417                            }
418                  }
419                  F_REST {
420                            let _moved = rest_must_come_last_marker;
421                            out.args_raw_string(&DisplayHelper(
422                                &self.$fname,
423                      ${if fmeta(netdoc(with)) {
424                                ${fmeta(netdoc(with)) as path}
425                                ::${paste_spanned $fname fmt_args_rest}
426                      } else {
427                                <$ftype as Display>::fmt
428                      }}
429                                ));
430                  }
431                  F_OBJECT {
432                            // We do this one later, in case it's not last in the struct.
433                            // It consumes `out`.
434                  }
435                  F_SKIP {
436                  }
437                }
438                        } // per-field local variables scope
439              }}
440
441                // Look at some almost-entirely-ignored attributes.
442                ${if tmeta(netdoc(no_extra_args)) {
443                        let _consume = rest_must_come_last_marker;
444                }}
445
446              ${for fields {
447                ${when F_OBJECT}
448
449                        let selector = MultiplicitySelector::<$ftype>::default();
450                        if let Some(object) = selector
451                            .${paste_spanned $fname as_option}(&self.$fname)
452                        {
453                ${define CHECK_OBJECT_ENCODABLE {
454                            selector.${paste_spanned $fname check_item_object_encodable}();
455                }}
456                            // This is, sort of, a recapitulation of `ItemEncoder::object`.
457                            // We can't conveniently just call that because we want to support
458                            // overriding the label, even when we're using ItemObjectEncodable.
459
460                            // Bind to `label`
461                            let label =
462                ${fmeta(netdoc(object(label))) as str, default {
463                                {
464                                    $CHECK_OBJECT_ENCODABLE
465                                    ItemObjectEncodable::label(object)
466                                }
467                }}
468                                ;
469
470                            // Obtain the `data`
471                            let mut data: Vec<u8> = vec![];
472                      ${fmeta(netdoc(with)) as path, default {
473                                ItemObjectEncodable
474                      }}
475                                ::${paste_spanned $fname write_object_onto}
476                                (object, &mut data)
477                                .map_err(into_internal!("failed to encode byte array!"))
478                                .$BUG_CONTEXT?;
479
480                            out.object_bytes(label, data);
481
482                        } // if let Some(object)
483              }}
484
485                        Ok(())
486            }
487    }
488}