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