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}