Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_jdat/dat_map/TODO.md

7.0 KiB, 1 run

created by r1870400018:9510, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1# TODO for fe2o3_jdat/dat_map
2
3Improvements to the `FromDatMap` / `ToDatMap` procedural macros that would
4lift the derive from "just sufficient" toward a first-class
5Dat/JSON-through-Dat (de)serialisation facility for Hematite. Captured after
6writing `fe2o3_net::acme::rfc8555`, where every item below was felt as
7friction during development.
8
9The derive is finicky and should be improved carefully in a dedicated
10session, with `fe2o3_net/src/acme/rfc8555.rs` (a non-trivial real-world
11user) used as a stress test for each change. None of the items below block
12the ACME migration currently under way.
13
14## Ranked from biggest usability win down
15
16### 1. Nested-struct field support (biggest payoff)
17
18Today a field typed `Vec<Challenge>` or `Option<Order>` or
19`BTreeMap<String, Foo>` panics at derive expansion with
20`"from_datmap: Cannot find an equivalent Dat for type ..."`. The supported
21type list in `fe2o3_jdat/dat_map/src/lib.rs` is a fixed whitelist of
22stringified type names: primitives, `String`, `Dat`, `Box<Dat>`,
23`Vec<u8>`, `Vec<Dat>`, `Vec<String>`, `DaticleMap`.
24
25Consequences:
26
27- Every compound field in a protocol struct has to fall back to `Dat` or
28 `Vec<Dat>` at the field site and the enclosing type has to expose a
29 `typed_*()` helper that iterates and calls `T::from_datmap(...)`
30 explicitly.
31- `fe2o3_net::acme::rfc8555::Authorization::typed_challenges()` is one such
32 hand-rolled unwrap helper; `fe2o3_steel::srv::cfg::ServerConfig::get_vhosts`
33 and `::get_acme` are two more. Every new protocol type in the workspace
34 will need another.
35
36Desired behaviour:
37
38- For any field of a type `T` where `T: FromDatMap`, emit
39 `res!(T::from_datmap(...))` in place of the current `get_*` lookup.
40- For `Vec<T>`, `Option<T>` and `BTreeMap<String, T>` where `T: FromDatMap`,
41 unwrap the generic wrapper and recurse.
42- Keep the existing primitive/`Dat`/`Vec<Dat>` fast paths as they are.
43
44Implementation sketch:
45
46- When the derive encounters a type it does not recognise, stop panicking.
47 Instead, generate code that assumes `<T as FromDatMap>::from_datmap(...)`
48 and lets rustc emit a trait-bound error at the call site if the user's
49 type does not implement `FromDatMap`. This gives the user a clear,
50 compilable-code error message instead of a derive panic.
51- For `Vec<T>`, parse the generic argument, emit a loop that pulls each
52 element out of a `Dat::List(v)`, calls `T::from_datmap` on each element's
53 `Dat::Map`, and collects.
54- For `Option<T>`, emit presence check plus `T::from_datmap`.
55
56### 2. Native `Option<T>` support
57
58Separate from (1) but related. Today there is no way to express "may be
59absent **and** distinguish between absent and a default value". The
60`#[optional]` attribute makes an absent field default to `Default::default()`
61for the field type, which for `String` is `""` -- indistinguishable from a
62present `""` in the wire payload.
63
64Desired behaviour: a `pub x: Option<T>` field **with no attribute** means
65"absent ⇒ `None`, present ⇒ `Some(T)`", no confusion with a default value.
66`#[optional]` stays as a separate escape hatch for the value-with-default
67case.
68
69ACME example: `Order.certificate` defaults to `""` today but semantically
70means "not yet issued". An `Option<String>` field would make that explicit
71and survive typo-style bugs where the caller checks `if order.certificate.is_empty()`
72when they meant `.is_some()`.
73
74### 3. Error messages should carry field and struct names
75
76Currently the derive-generated code calls `res!(m.get_string(...))` etc. and
77the resulting error is something like `"expected Str, got X"`. Nothing in
78that message tells you **which field** of **which struct** blew up, so
79debugging a malformed CA response turns into a println/eprintln binary
80search.
81
82Desired behaviour: the derive wraps every field extraction with an
83`err!` context carrying the field name and the enclosing struct name.
84Something like `"while decoding field `new_nonce` of struct `Directory`"`.
85
86Cheap to implement (quote!-interpolate the literals), huge debugging win.
87
88### 4. JSON `null` versus missing key
89
90RFC 8555 permits CAs to emit either `"error": null` or to omit the `error`
91key entirely. Both paths currently end up indistinguishable: the field
92defaults to `Dat::Empty`. This is the same shape as issue (2); resolving
93both at once by adopting `Option<T>` semantics with explicit null handling
94covers this case too.
95
96### 5. `#[rename_all = "camelCase"]` at the struct level
97
98The current attribute is `#[rename(name = "newNonce")]` -- wordy, and
99easily repeated across every field of a camelCase wire format. Serde's
100`#[serde(rename_all = "camelCase")]` at the struct level would let us
101delete every per-field `#[rename(...)]` line in `rfc8555.rs` and remove a
102whole class of typos.
103
104Relatedly: the simpler per-field form `#[rename = "newNonce"]` (without the
105`name =` wrapper) would match serde's de facto standard and be nicer to
106type.
107
108### 6. Support for simple enums mapped to `Dat::Str`
109
110ACME status is a closed string set (`"pending"`, `"ready"`, `"valid"`,
111`"invalid"`, `"processing"`, `"revoked"`, `"deactivated"`, `"expired"`).
112Today we store it as `String` and compare with `==`, losing compile-time
113safety.
114
115Desired: `#[derive(FromDatMap, ToDatMap)]` works on unit-only enums whose
116variants map to `Dat::Str` values. Serde supports this via
117`#[serde(rename_all = "lowercase")]` at the enum level. The derive would
118need to accept a second input kind (enum) and emit a match.
119
120### 7. Compile-time errors should be actionable
121
122Unsupported types currently hit
123`unimplemented!("from_datmap: Cannot find an equivalent Dat for type '{}'.")`
124at derive expansion time. That manifests as a derive panic, which is one
125of the harder errors in Rust to interpret.
126
127Desired: emit `compile_error!` with a message like
128`"field `challenges` of struct `Authorization` has type `Vec<Challenge>`, which is not currently supported by `FromDatMap`. Either use `#[skip]` on the field, or change the field type to `Vec<Dat>` and write a typed getter, or add nested-struct support (see TODO #1)."`
129
130### 8. Attribute hygiene
131
132Attributes currently live in the global attribute namespace: `#[optional]`,
133`#[skip]`, `#[rename(...)]`. A shared namespace such as `#[datmap(optional)]`,
134`#[datmap(rename = "x")]`, `#[datmap(skip)]` would:
135
136- make them easier to discover and grep for;
137- reduce the risk of name collisions with other attribute macros;
138- leave room to add new attribute options like
139 `#[datmap(default = "expression")]` or
140 `#[datmap(skip_serializing_if = "Vec::is_empty")]` without polluting the
141 global namespace further.
142
143## Practical takeaway
144
145Items (1) and (5) would individually eliminate most of the boilerplate in
146any fe2o3 module that parses a wire protocol. (2) closes a real semantic
147gap that will bite us in future protocol parsers. (3) is cheap and makes
148debugging bearable. The rest are quality-of-life polish.
149
150None of these are prerequisites for finishing the ACME migration. This
151list exists so the work is not lost and so whoever picks up the derive
152improvements next starts with context for why each item matters, from a
153real, non-trivial user of the derive (`fe2o3_net::acme::rfc8555`).