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 // 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.${paste_spanned $fname 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
132define_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 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 }
250 }
251}
252
253define_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 fn encode_fields(
272 &self,
273 #[allow(unused)] // Not used if there are no fields.
274 out: &mut $P::NetdocEncoder,
275 ) -> $P::Result<(), $P::Bug> {
276 #[allow(unused)] // Not used if there are no fields.
277 use $P::*;
278
279 $ENCODE_ITEMS_BODY
280 }
281 }
282}
283
284define_derive_deftly! {
285 use NetdocDeriveAnyCommon;
286 use NetdocItemDeriveCommon;
287
288 /// Derive `ItemValueEncodable`
289 ///
290 // NB there is very similar wording in the ItemValuePareable derive docs.
291 // If editing any of this derive's documentation, considering editing that too.
292 //
293 /// Fields in the struct are emitted as keyword line arguments,
294 /// in the order they appear in the struct.
295 ///
296 /// ### Field type
297 ///
298 /// Each field should be:
299 ///
300 /// * `impl `[`ItemArgument`] (one argument),
301 /// * `Option<impl ItemArgument>` (one optional argument), or
302 /// * `Vec<impl ItemArgument + EncodeOrd>` (zero or more arguments).
303 /// * `BTreeSet<impl ItemArgument>` (zero or more arguments).
304 ///
305 /// `ItemArgument` can be implemented via `impl Display`,
306 /// by writing `impl NormalItemArgument`.
307 ///
308 /// (Multiplicity is implemented via types in the [`multiplicity`] module,
309 /// specifically [`MultiplicitySelector`] and [`MultiplicityMethods`].)
310 ///
311 /// ### Top-level attributes:p
312 ///
313 /// * **`#[deftly(netdoc(no_extra_args))]**:
314 ///
315 /// Ignored.
316 /// (Obviously, the encoder never emits arguments that aren't in the document struct.)
317 ///
318 /// Accepted for alignment with `ItemValueParseable`,
319 /// so that a struct which only conditionally derives `ItemValueParseable`
320 /// does not need to conditionally mark this attribute.
321 ///
322 /// (May not be combined with `#[deftly(netdoc(rest))]`.)
323 ///
324 $DOC_DEBUG_PLACEHOLDER
325 ///
326 /// ### Field-level attributes:
327 ///
328 /// * **`#[deftly(netdoc(rest))]**:
329 ///
330 /// The field is the whole rest of the line.
331 /// Must come after any other normal argument fields.
332 /// Only allowed once.
333 ///
334 /// The field type must implement `ToString` (normally, via `Display`).
335 /// (I.e. `Vec` , `Option` etc., are not allowed, and `ItemArgumen` is not used.)
336 ///
337 /// * **`#[deftly(netdoc(object))]**:
338 ///
339 /// The field is the Object.
340 /// It must implement [`ItemObjectEncodable`].
341 /// (or be `Option<impl ItemObjectEncodable>`).
342 ///
343 /// Only allowed once.
344 ///
345 /// * **`#[deftly(netdoc(object(label = "LABEL")))]**:
346 ///
347 /// Sets the expected label for an Object.
348 /// If not supplied, uses [`ItemObjectEncodable::label`].
349 ///
350 /// * **`#[deftly(netdoc(with = MODULE)]**:
351 ///
352 /// Instead of `ItemArgument`, the argument is encoded with `MODULE::write_arg_onto`,
353 /// which must have the same signature as [`ItemArgument::write_arg_onto`].
354 ///
355 /// With `#[deftly(netdoc(rest))]`, `MODULE::fmt_args_rest` replaces `Display::fmt`.
356 ///
357 /// With `#[deftly(netdoc(object))]`, uses `MODULE::write_object_onto`
358 /// instead of `tor_netdoc::Writeable::write_onto`.
359 /// LABEL must also be specified unless the object also implements `ItemObjectEncodable`.
360 ///
361 /// * **`#[deftly(netdoc(sig_hash = HASH_METHOD))]**:
362 ///
363 /// TODO NETDOC ENCODE. Encoding of signed documents is not yet implemented.
364 ///
365 /// * **`#[deftly(netdoc(skip))]**:
366 ///
367 /// Do not encode this field.
368 export ItemValueEncodable beta_deftly, for struct, meta_quoted rigorous, expect items:
369
370 ${define P { $crate::encode }}
371
372 ${define BUG_CONTEXT {
373 // We use .map_err() rather than .bug_context() so that we nail down the error type
374 map_err(|bug: Bug| bug.bug_context(
375 ${concat "in item " $ttype ", in field " $fname}
376 ))
377 }}
378
379 impl<$tgens> $P::ItemValueEncodable for $ttype {
380 fn write_item_value_onto(
381 &self,
382 #[allow(unused)]
383 mut out: $P::ItemEncoder,
384 ) -> $P::Result<(), $P::Bug> {
385 // | <- macro conditions and loops are aligned starting here
386 // | | <- we line up the normal Rust statements starting here
387 #[allow(unused_imports)]
388 use $P::*;
389 #[allow(unused_imports)]
390 use tor_error::BugContext as _;
391
392 $EMIT_DEBUG_PLACEHOLDER
393
394 // ignore #[deftly(netdoc(doctype_for_error = EXPR))]
395 let _: &str = ${tmeta(netdoc(doctype_for_error)) as expr, default {""}};
396
397 #[allow(unused)]
398 let rest_must_come_last_marker = RestMustComeLastMarker;
399
400 ${for fields {
401 {
402 ${select1
403 F_NORMAL {
404 let _ = &rest_must_come_last_marker;
405 let selector = MultiplicitySelector::<$ftype>::default();
406 let selector = selector.selector();
407 ${if not(fmeta(netdoc(with))) {
408 selector.${paste_spanned $fname check_item_argument_encodable}();
409 }}
410 for arg in selector.iter_ordered(&self.$fname) {
411 ${fmeta(netdoc(with)) as path, default {
412 ItemArgument
413 }}
414 ::${paste_spanned $fname write_arg_onto}
415 (arg, &mut out)
416 .$BUG_CONTEXT?;
417 }
418 }
419 F_REST {
420 let _moved = rest_must_come_last_marker;
421 out.args_raw_string(&DisplayHelper(
422 &self.$fname,
423 ${if fmeta(netdoc(with)) {
424 ${fmeta(netdoc(with)) as path}
425 ::${paste_spanned $fname fmt_args_rest}
426 } else {
427 <$ftype as Display>::fmt
428 }}
429 ));
430 }
431 F_OBJECT {
432 // We do this one later, in case it's not last in the struct.
433 // It consumes `out`.
434 }
435 F_SKIP {
436 }
437 }
438 } // per-field local variables scope
439 }}
440
441 // Look at some almost-entirely-ignored attributes.
442 ${if tmeta(netdoc(no_extra_args)) {
443 let _consume = rest_must_come_last_marker;
444 }}
445
446 ${for fields {
447 ${when F_OBJECT}
448
449 let selector = MultiplicitySelector::<$ftype>::default();
450 if let Some(object) = selector
451 .${paste_spanned $fname as_option}(&self.$fname)
452 {
453 ${define CHECK_OBJECT_ENCODABLE {
454 selector.${paste_spanned $fname check_item_object_encodable}();
455 }}
456 // This is, sort of, a recapitulation of `ItemEncoder::object`.
457 // We can't conveniently just call that because we want to support
458 // overriding the label, even when we're using ItemObjectEncodable.
459
460 // Bind to `label`
461 let label =
462 ${fmeta(netdoc(object(label))) as str, default {
463 {
464 $CHECK_OBJECT_ENCODABLE
465 ItemObjectEncodable::label(object)
466 }
467 }}
468 ;
469
470 // Obtain the `data`
471 let mut data: Vec<u8> = vec![];
472 ${fmeta(netdoc(with)) as path, default {
473 ItemObjectEncodable
474 }}
475 ::${paste_spanned $fname write_object_onto}
476 (object, &mut data)
477 .map_err(into_internal!("failed to encode byte array!"))
478 .$BUG_CONTEXT?;
479
480 out.object_bytes(label, data);
481
482 } // if let Some(object)
483 }}
484
485 Ok(())
486 }
487 }
488}