1extern crate alloc;
45
46use alloc::vec::Vec;
47
48use uor_foundation::enforcement::ShapeViolation;
49use uor_foundation::pipeline::{ConstrainedTypeShape, ConstraintRef, IntoBindingValue};
50use uor_foundation::{DefaultHostTypes, ViolationKind};
51use uor_foundation_sdk::{output_shape, prism_model};
52
53use crate::resolvers::AddressResolverTuple;
54use crate::shapes::bounds::AddrBounds;
55use crate::shapes::hasher::Sha256Hasher;
56
57#[allow(unused_imports)]
59use crate::verbs::{address_inference, VERB_TERMS_ADDRESS_INFERENCE};
60
61pub const ADDRESS_LABEL_BYTES: usize = 71;
63
64pub const JSON_INPUT_MAX_BYTES: usize = 3968;
70
71#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct JsonInput {
83 pub bytes: Vec<u8>,
86}
87
88impl JsonInput {
89 const LENGTH_VIOLATION: ShapeViolation = ShapeViolation {
90 shape_iri: "https://uor.foundation/addr/JsonInput",
91 constraint_iri: "https://uor.foundation/addr/JsonInput/maxBytes",
92 property_iri: "https://uor.foundation/addr/JsonInput/byteCount",
93 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
94 min_count: 0,
95 max_count: JSON_INPUT_MAX_BYTES as u32,
96 kind: ViolationKind::ValueCheck,
97 };
98
99 pub fn new(bytes: Vec<u8>) -> Result<Self, ShapeViolation> {
106 if bytes.len() > JSON_INPUT_MAX_BYTES {
107 return Err(Self::LENGTH_VIOLATION);
108 }
109 Ok(Self { bytes })
110 }
111}
112
113impl ConstrainedTypeShape for JsonInput {
114 const IRI: &'static str = "https://uor.foundation/addr/JsonInput";
115 const SITE_COUNT: usize = JSON_INPUT_MAX_BYTES;
119 const CONSTRAINTS: &'static [ConstraintRef] = &[];
120 const CYCLE_SIZE: u64 = u64::MAX;
121}
122
123impl uor_foundation::pipeline::__sdk_seal::Sealed for JsonInput {}
124
125impl IntoBindingValue for JsonInput {
126 const MAX_BYTES: usize = JSON_INPUT_MAX_BYTES;
127 fn into_binding_bytes(&self, out: &mut [u8]) -> Result<usize, ShapeViolation> {
128 if self.bytes.len() > out.len() {
129 return Err(Self::LENGTH_VIOLATION);
130 }
131 out[..self.bytes.len()].copy_from_slice(&self.bytes);
132 Ok(self.bytes.len())
133 }
134}
135
136output_shape! {
163 pub struct AddressLabel;
164 impl ConstrainedTypeShape for AddressLabel {
165 const IRI: &'static str = "https://uor.foundation/addr/AddressLabel";
166 const SITE_COUNT: usize = 71;
167 const CONSTRAINTS: &'static [ConstraintRef] = &[
168 ConstraintRef::Site { position: 0 }, ConstraintRef::Site { position: 1 },
171 ConstraintRef::Site { position: 2 }, ConstraintRef::Site { position: 3 },
172 ConstraintRef::Site { position: 4 }, ConstraintRef::Site { position: 5 },
173 ConstraintRef::Site { position: 6 }, ConstraintRef::Site { position: 7 },
174 ConstraintRef::Site { position: 8 }, ConstraintRef::Site { position: 9 },
175 ConstraintRef::Site { position: 10 }, ConstraintRef::Site { position: 11 },
176 ConstraintRef::Site { position: 12 }, ConstraintRef::Site { position: 13 },
177 ConstraintRef::Site { position: 14 }, ConstraintRef::Site { position: 15 },
178 ConstraintRef::Site { position: 16 }, ConstraintRef::Site { position: 17 },
179 ConstraintRef::Site { position: 18 }, ConstraintRef::Site { position: 19 },
180 ConstraintRef::Site { position: 20 }, ConstraintRef::Site { position: 21 },
181 ConstraintRef::Site { position: 22 }, ConstraintRef::Site { position: 23 },
182 ConstraintRef::Site { position: 24 }, ConstraintRef::Site { position: 25 },
183 ConstraintRef::Site { position: 26 }, ConstraintRef::Site { position: 27 },
184 ConstraintRef::Site { position: 28 }, ConstraintRef::Site { position: 29 },
185 ConstraintRef::Site { position: 30 }, ConstraintRef::Site { position: 31 },
186 ConstraintRef::Site { position: 32 }, ConstraintRef::Site { position: 33 },
187 ConstraintRef::Site { position: 34 }, ConstraintRef::Site { position: 35 },
188 ConstraintRef::Site { position: 36 }, ConstraintRef::Site { position: 37 },
189 ConstraintRef::Site { position: 38 }, ConstraintRef::Site { position: 39 },
190 ConstraintRef::Site { position: 40 }, ConstraintRef::Site { position: 41 },
191 ConstraintRef::Site { position: 42 }, ConstraintRef::Site { position: 43 },
192 ConstraintRef::Site { position: 44 }, ConstraintRef::Site { position: 45 },
193 ConstraintRef::Site { position: 46 }, ConstraintRef::Site { position: 47 },
194 ConstraintRef::Site { position: 48 }, ConstraintRef::Site { position: 49 },
195 ConstraintRef::Site { position: 50 }, ConstraintRef::Site { position: 51 },
196 ConstraintRef::Site { position: 52 }, ConstraintRef::Site { position: 53 },
197 ConstraintRef::Site { position: 54 }, ConstraintRef::Site { position: 55 },
198 ConstraintRef::Site { position: 56 }, ConstraintRef::Site { position: 57 },
199 ConstraintRef::Site { position: 58 }, ConstraintRef::Site { position: 59 },
200 ConstraintRef::Site { position: 60 }, ConstraintRef::Site { position: 61 },
201 ConstraintRef::Site { position: 62 }, ConstraintRef::Site { position: 63 },
202 ConstraintRef::Site { position: 64 }, ConstraintRef::Site { position: 65 },
203 ConstraintRef::Site { position: 66 }, ConstraintRef::Site { position: 67 },
204 ConstraintRef::Site { position: 68 }, ConstraintRef::Site { position: 69 },
205 ConstraintRef::Site { position: 70 },
206 ];
207 }
208}
209
210prism_model! {
213 pub struct AddressModel;
214 pub struct AddressRoute;
215 impl PrismModel<
216 DefaultHostTypes,
217 AddrBounds,
218 Sha256Hasher,
219 AddressResolverTuple<Sha256Hasher>
220 > for AddressModel {
221 type Input = JsonInput;
222 type Output = AddressLabel;
223 type Route = AddressRoute;
224 fn route(input: Self::Input) -> Self::Output {
225 address_inference(input)
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn json_input_round_trips_canonical_bytes() {
236 let canonical = br#"{"foo":"bar"}"#.to_vec();
237 let input = JsonInput::new(canonical.clone()).expect("within bounds");
238 let mut out = [0u8; 4096];
239 let n = input.into_binding_bytes(&mut out).expect("buffer fits");
240 assert_eq!(n, canonical.len());
241 assert_eq!(&out[..n], canonical.as_slice());
242 }
243
244 #[test]
245 fn json_input_rejects_oversize() {
246 let oversize = vec![0u8; JSON_INPUT_MAX_BYTES + 1];
247 let err = JsonInput::new(oversize).expect_err("must reject oversize");
248 assert_eq!(err.shape_iri, JsonInput::LENGTH_VIOLATION.shape_iri);
249 }
250
251 #[test]
252 fn address_label_site_count_matches_wire_format_width() {
253 assert_eq!(
254 <AddressLabel as ConstrainedTypeShape>::SITE_COUNT,
255 ADDRESS_LABEL_BYTES
256 );
257 }
258
259 #[test]
260 fn address_label_carries_seventy_one_disjoint_site_constraints() {
261 let cs = <AddressLabel as ConstrainedTypeShape>::CONSTRAINTS;
262 assert_eq!(cs.len(), 71, "71 Site constraints (algebraic-closure)");
263 for c in cs {
264 assert!(matches!(c, ConstraintRef::Site { .. }));
265 }
266 }
267
268 #[test]
269 fn address_label_constraints_pin_every_wire_format_site() {
270 let cs = <AddressLabel as ConstrainedTypeShape>::CONSTRAINTS;
271 let positions: Vec<u32> = cs
272 .iter()
273 .filter_map(|c| match c {
274 ConstraintRef::Site { position } => Some(*position),
275 _ => None,
276 })
277 .collect();
278 assert_eq!(positions.len(), 71);
279 for (i, &p) in positions.iter().enumerate() {
280 assert_eq!(p, i as u32, "Site_{i} pins position {i}");
281 }
282 }
283}