1
//! Deriving `NetdocEncodable`
2

            
3
use super::*;
4

            
5
use 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)]
12
pub 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)]
19
pub struct DisplayHelper<'t, T, F>(pub &'t T, pub F)
20
where
21
    F: Fn(&T, &mut fmt::Formatter) -> fmt::Result;
22

            
23
impl<'t, T, F> Display for DisplayHelper<'t, T, F>
24
where
25
    F: Fn(&T, &mut fmt::Formatter) -> fmt::Result,
26
{
27
12
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28
12
        self.1(self.0, f)
29
12
    }
30
}
31

            
32
define_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.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

            
132
define_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
56
        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
56
        }
250
    }
251
}
252

            
253
define_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
12
        fn encode_fields(&self, out: &mut $P::NetdocEncoder) -> $P::Result<(), $P::Bug> {
272
            use $P::*;
273

            
274
            $ENCODE_ITEMS_BODY
275
12
        }
276
    }
277
}
278

            
279
define_derive_deftly! {
280
    use NetdocDeriveAnyCommon;
281
    use NetdocItemDeriveCommon;
282

            
283
    /// Derive `ItemValueEncodable`
284
    ///
285
    // NB there is very similar wording in the ItemValuePareable derive docs.
286
    // If editing any of this derive's documentation, considering editing that too.
287
    //
288
    /// Fields in the struct are emitted as keyword line arguments,
289
    /// in the order they appear in the struct.
290
    ///
291
    /// ### Field type
292
    ///
293
    /// Each field should be:
294
    ///
295
    ///  * `impl `[`ItemArgument`] (one argument),
296
    ///  * `Option<impl ItemArgument>` (one optional argument), or
297
    ///  * `Vec<impl ItemArgument + EncodeOrd>` (zero or more arguments).
298
    ///  * `BTreeSet<impl ItemArgument>` (zero or more arguments).
299
    ///
300
    /// `ItemArgument` can be implemented via `impl Display`,
301
    /// by writing `impl NormalItemArgument`.
302
    ///
303
    /// (Multiplicity is implemented via types in the [`multiplicity`] module,
304
    /// specifically [`MultiplicitySelector`] and [`MultiplicityMethods`].)
305
    ///
306
    /// ### Top-level attributes:p
307
    ///
308
    ///  * **`#[deftly(netdoc(no_extra_args))]**:
309
    ///
310
    ///    Ignored.
311
    ///    (Obviously, the encoder never emits arguments that aren't in the document struct.)
312
    ///
313
    ///    Accepted for alignment with `ItemValueParseable`,
314
    ///    so that a struct which only conditionally derives `ItemValueParseable`
315
    ///    does not need to conditionally mark this attribute.
316
    ///
317
    ///    (May not be combined with `#[deftly(netdoc(rest))]`.)
318
    ///
319
    $DOC_DEBUG_PLACEHOLDER
320
    ///
321
    /// ### Field-level attributes:
322
    ///
323
    ///  * **`#[deftly(netdoc(rest))]**:
324
    ///
325
    ///    The field is the whole rest of the line.
326
    ///    Must come after any other normal argument fields.
327
    ///    Only allowed once.
328
    ///
329
    ///    The field type must implement `ToString` (normally, via `Display`).
330
    ///    (I.e. `Vec` , `Option` etc., are not allowed, and `ItemArgumen` is not used.)
331
    ///
332
    ///  * **`#[deftly(netdoc(object))]**:
333
    ///
334
    ///    The field is the Object.
335
    ///    It must implement [`ItemObjectEncodable`].
336
    ///    (or be `Option<impl ItemObjectEncodable>`).
337
    ///
338
    ///    Only allowed once.
339
    ///
340
    ///  * **`#[deftly(netdoc(object(label = "LABEL")))]**:
341
    ///
342
    ///    Sets the expected label for an Object.
343
    ///    If not supplied, uses [`ItemObjectEncodable::label`].
344
    ///
345
    ///  * **`#[deftly(netdoc(with = MODULE)]**:
346
    ///
347
    ///    Instead of `ItemArgument`, the argument is encoded with `MODULE::write_arg_onto`,
348
    ///    which must have the same signature as [`ItemArgument::write_arg_onto`].
349
    ///
350
    ///    With `#[deftly(netdoc(rest))]`, `MODULE::fmt_args_rest` replaces `Display::fmt`.
351
    ///
352
    ///    With `#[deftly(netdoc(object))]`, uses `MODULE::write_object_onto`
353
    ///    instead of `tor_netdoc::Writeable::write_onto`.
354
    ///    LABEL must also be specified unless the object also implements `ItemObjectEncodable`.
355
    ///
356
    ///  * **`#[deftly(netdoc(sig_hash = HASH_METHOD))]**:
357
    ///
358
    ///    TODO NETDOC ENCODE.  Encoding of signed documents is not yet implemented.
359
    export ItemValueEncodable beta_deftly, for struct, meta_quoted rigorous, expect items:
360

            
361
    ${define P { $crate::encode }}
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
38
        fn write_item_value_onto(
372
38
            &self,
373
38
            #[allow(unused)]
374
38
            mut out: $P::ItemEncoder,
375
38
        ) -> $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
38
                        let _: &str = ${tmeta(netdoc(doctype_for_error)) as expr, default {""}};
387

            
388
                        #[allow(unused)]
389
38
                        let rest_must_come_last_marker = RestMustComeLastMarker;
390

            
391
              ${for fields {
392
28
                        {
393
28
                ${select1
394
28
                  F_NORMAL {
395
28
                            let _ = &rest_must_come_last_marker;
396
28
                            let selector = MultiplicitySelector::<$ftype>::default();
397
28
                            let selector = selector.selector();
398
28
                      ${if not(fmeta(netdoc(with))) {
399
28
                            selector.${paste_spanned $fname check_item_argument_encodable}();
400
28
                      }}
401
28
                            for arg in selector.iter_ordered(&self.$fname) {
402
28
                      ${fmeta(netdoc(with)) as path, default {
403
28
                                ItemArgument
404
28
                      }}
405
28
                                    ::${paste_spanned $fname write_arg_onto}
406
28
                                    (arg, &mut out)
407
28
                                    .$BUG_CONTEXT?;
408
28
                            }
409
28
                  }
410
28
                  F_REST {
411
28
                            let _moved = rest_must_come_last_marker;
412
28
                            out.args_raw_string(&DisplayHelper(
413
9
                                &self.$fname,
414
9
                      ${if fmeta(netdoc(with)) {
415
9
                                ${fmeta(netdoc(with)) as path}
416
9
                                ::${paste_spanned $fname fmt_args_rest}
417
9
                      } else {
418
9
                                <$ftype as Display>::fmt
419
9
                      }}
420
9
                                ));
421
28
                  }
422
28
                  F_OBJECT {
423
28
                            // We do this one later, in case it's not last in the struct.
424
28
                            // It consumes `out`.
425
28
                  }
426
28
                }
427
28
                        } // per-field local variables scope
428
              }}
429

            
430
                // Look at some almost-entirely-ignored attributes.
431
                ${if tmeta(netdoc(no_extra_args)) {
432
16
                        let _consume = rest_must_come_last_marker;
433
                }}
434

            
435
              ${for fields {
436
                ${when F_OBJECT}
437

            
438
26
                        let selector = MultiplicitySelector::<$ftype>::default();
439
26
                        if let Some(object) = selector
440
26
                            .${paste_spanned $fname as_option}(&self.$fname)
441
                        {
442
                ${define CHECK_OBJECT_ENCODABLE {
443
8
                            selector.${paste_spanned $fname check_item_object_encodable}();
444
                }}
445
                            // This is, sort of, a recapitulation of `ItemEncoder::object`.
446
                            // We can't conveniently just call that because we want to support
447
                            // overriding the label, even when we're using ItemObjectEncodable.
448

            
449
                            // Bind to `label`
450
12
                            let label =
451
                ${fmeta(netdoc(object(label))) as str, default {
452
                                {
453
                                    $CHECK_OBJECT_ENCODABLE
454
8
                                    ItemObjectEncodable::label(object)
455
                                }
456
                }}
457
                                ;
458

            
459
                            // Obtain the `data`
460
12
                            let mut data: Vec<u8> = vec![];
461
                      ${fmeta(netdoc(with)) as path, default {
462
8
                                ItemObjectEncodable
463
                      }}
464
                                ::${paste_spanned $fname write_object_onto}
465
9
                                (object, &mut data)
466
9
                                .map_err(into_internal!("failed to encode byte array!"))
467
4
                                .$BUG_CONTEXT?;
468

            
469
12
                            out.object_bytes(label, data);
470

            
471
14
                        } // if let Some(object)
472
              }}
473

            
474
38
                        Ok(())
475
38
            }
476
    }
477
}