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