1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! EN 18219:2026 clause 5 identifier syntax, for the two tiers that check it,
//! and the GS1 AI 10 batch and AI 21 serial a passport's data carrier prints.
//!
//! 🚨 **One home on purpose.** These predicates lived only in
//! `dpp_domain::identifier::ProductIdentifier`, and the plugin SDK grew its own
//! copy when the product-group payloads moved to `productIdentifier`. The copies
//! disagreed: the SDK accepted `https:///p/1` and `did:web: ` because it tested
//! a prefix and a non-empty remainder, while the domain tested the authority and
//! the W3C grammar. A plugin is the *first* thing to see product group data, so
//! the weaker of the two copies was the one on the outside.
//!
//! Kept dependency-free and `no_std` so the Wasm guest SDK can call the same
//! code the host does, rather than a second reading of the same clause.
//!
// LAYOUT-DEVIATION: rule 15 counts users among a bucket's siblings, inside one
// crate. This module's two users are `dpp-domain` and `dpp-plugin-sdk`, so the
// count it can see is zero and the sharing it is testing for is real but
// cross-crate. `dpp-rules` exists precisely to be depended on by both without
// either depending on the other, so a type shared that way has no in-crate
// sibling to count and cannot satisfy the rule as written.
/// The DID methods EN 18219 scheme 3 names.
///
/// The standard describes scheme 3 as Decentralized Identifiers and names these
/// three as the admissible methods, `did:web` being the lightweight non-DLT
/// option. Closed rather than open because an identifier exists to be followed:
/// a method no reader can resolve identifies nothing, and accepting one would
/// let a passport be created that is unreachable by design.
pub const DID_METHODS: = ;
/// Why a candidate scheme 3 value is not an admissible DID.
///
/// Three variants rather than a `bool` because the callers report differently:
/// the domain has an error type per case, and a plugin turns them into one
/// field message. Neither should have to re-derive which case it hit.
/// An absolute `http`/`https` URL with a host, as EN 18219 scheme 2 requires.
///
/// 🚨 The authority ends at the first `/`, `?` or `#` — it is not simply
/// "whatever follows the scheme". `https:///p/1` and `https://?q` each leave a
/// non-empty remainder and no host whatsoever, so testing that remainder for
/// emptiness accepted two values nothing can resolve.
///
/// This is a shape check, not a conformance claim: scheme 2's format is
/// specified by EN IEC 61406-1/-2 and those rules are **not** applied here.
/// A DID under one of the methods [`DID_METHODS`] names.
///
/// # Errors
///
/// [`DidRejection`], naming which of the three ways the value failed.
/// The most characters a GS1 AI 21 serial number may carry.
///
/// GS1's Barcode Syntax Dictionary specifies AI 21 as `X..20`: one to twenty
/// characters of CSET 82. That dictionary is vendored by `dpp-digital-link`,
/// which this crate cannot depend on, so the number is restated here and a
/// cross-crate test holds it against the dictionary's own entry.
pub const MAX_GS1_SERIAL_CHARS: usize = 20;
/// The most characters a GS1 AI 10 batch or lot number may carry.
///
/// AI 10 is `X..20` in the same dictionary — the same rule as AI 21, restated
/// under its own name because the two are separate entries that GS1 could
/// change apart. The same cross-crate test holds it against the dictionary.
pub const MAX_GS1_LOT_CHARS: usize = 20;
/// Why a candidate AI 10 or AI 21 value cannot be printed.
/// Whether `c` belongs to GS1 CSET 82, the character set of every `X`-typed
/// Application Identifier component — AI 10 and AI 21 among them.
///
/// 🚨 **The table below is ours, and it is not checked against a text.** The
/// syntax dictionary names the set (`"X": CSET 82`) without enumerating it; the
/// enumeration is in the GS1 General Specifications, which this repository does
/// not hold. What checks it instead is GS1's own Barcode Syntax Engine: the
/// Digital Link oracle corpus carries every printable ASCII character in an
/// AI 21 value together with this function's verdict, and GS1's engine has to
/// agree in both directions.
pub const
/// A value GS1 admits in AI 21: one to [`MAX_GS1_SERIAL_CHARS`] characters, all
/// in CSET 82.
///
/// # Errors
///
/// [`Gs1ValueRejection`], naming the first rule the value breaks.
/// A value GS1 admits in AI 10: one to [`MAX_GS1_LOT_CHARS`] characters, all in
/// CSET 82.
///
/// # Errors
///
/// [`Gs1ValueRejection`], naming the first rule the value breaks.
/// `X..max`: one to `max` characters, all in CSET 82.
/// W3C DID v1.0 clause 3.1: `method-specific-id = *( *idchar ":" ) 1*idchar`.
///
/// Colon-separated segments, of which only the last must be non-empty. Checked
/// because the shape is the whole claim the value makes — one carrying a raw
/// space or a truncated `%` escape is not a DID with a formatting blemish, it is
/// a string no resolver will accept, pointing at no passport.
/// `idchar = ALPHA / DIGIT / "." / "-" / "_" / pct-encoded`, where
/// `pct-encoded = "%" HEXDIG HEXDIG`.