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
6
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28
6
        self.1(self.0, f)
29
6
    }
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, expect items:
242

            
243
    impl<$tgens> $P::NetdocEncodable for $ttype {
244
46
        fn encode_unsigned(&self, out: &mut $P::NetdocEncoder) -> Result<(), $P::Bug> {
245
            use $P::*;
246

            
247
            $FIELD_ORDERING_CHECK
248
            $ENCODE_ITEMS_BODY
249
46
        }
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, expect items:
269

            
270
    impl<$tgens> $P::NetdocEncodableFields for $ttype {
271
12
        fn encode_fields(&self, out: &mut $P::NetdocEncoder) -> 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, expect items:
360

            
361
    ${define P { $crate::encode }}
362

            
363
    ${define LET_SELECTOR {
364
                    let selector = MultiplicitySelector::<$ftype>::default();
365
                    let selector = selector.selector();
366
    }}
367

            
368
    ${define BUG_CONTEXT {
369
        // We use .map_err() rather than .bug_context() so that we nail down the error type
370
        map_err(|bug: Bug| bug.bug_context(
371
            ${concat "in item " $ttype ", in field " $fname}
372
        ))
373
    }}
374

            
375
    impl<$tgens> $P::ItemValueEncodable for $ttype {
376
28
        fn write_item_value_onto(
377
28
            &self,
378
28
            #[allow(unused)]
379
28
            mut out: $P::ItemEncoder,
380
28
        ) -> $P::Result<(), $P::Bug> {
381
          //  | <- macro conditions and loops are aligned starting here
382
          //  |         | <- we line up the normal Rust statements starting here
383
                        #[allow(unused_imports)]
384
                        use $P::*;
385
                        #[allow(unused_imports)]
386
                        use tor_error::BugContext as _;
387

            
388
                        $EMIT_DEBUG_PLACEHOLDER
389

            
390
                        // ignore #[deftly(netdoc(doctype_for_error = EXPR))]
391
28
                        let _: &str = ${tmeta(netdoc(doctype_for_error)) as expr, default {""}};
392

            
393
                        #[allow(unused)]
394
28
                        let rest_must_come_last_marker = RestMustComeLastMarker;
395

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

            
434
                // Look at some almost-entirely-ignored attributes.
435
                ${if tmeta(netdoc(no_extra_args)) {
436
12
                        let _consume = rest_must_come_last_marker;
437
                }}
438

            
439
              ${for fields {
440
                ${when F_OBJECT}
441

            
442
                        $LET_SELECTOR
443
22
                        if let Some(object) = selector.as_option(&self.$fname) {
444
                ${define CHECK_OBJECT_ENCODABLE {
445
6
                            selector.${paste_spanned $fname check_item_object_encodable}();
446
                }}
447
                            // Bind to `label`
448
8
                            let label =
449
                ${fmeta(netdoc(object(label))) as str, default {
450
                                {
451
                                    $CHECK_OBJECT_ENCODABLE
452
6
                                    ItemObjectEncodable::label(object)
453
                                }
454
                }}
455
                                ;
456

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

            
467
8
                            out.object(label, data);
468

            
469
14
                        } // if let Some(field)
470
              }}
471

            
472
28
                        Ok(())
473
28
            }
474
    }
475
}