1use prism::operation::TermValue;
17use prism::pipeline::{
18 ConstrainedTypeShape, ConstraintRef, IntoBindingValue, PartitionProductFields, ShapeViolation,
19 ViolationKind,
20};
21
22use crate::ring::shapes::bounds::{MAX_WITT_LEVEL, RING_VALUE_MAX_BYTES};
23
24const INVALID_RING_VIOLATION: ShapeViolation = ShapeViolation {
27 shape_iri: "https://uor.foundation/addr/RingElement",
28 constraint_iri: "https://uor.foundation/addr/RingElement/validCanonicalBytes",
29 property_iri: "https://uor.foundation/addr/inputBytes",
30 expected_range: "https://uor.foundation/addr/ValidRingElementBytes",
31 min_count: 0,
32 max_count: 1,
33 kind: ViolationKind::ValueCheck,
34};
35
36const WITT_LEVEL_VIOLATION: ShapeViolation = ShapeViolation {
37 shape_iri: "https://uor.foundation/addr/RingElement",
38 constraint_iri: "https://uor.foundation/addr/RingElement/wittLevelBound",
39 property_iri: "https://uor.foundation/addr/RingElement/wittLevel",
40 expected_range: "http://www.w3.org/2001/XMLSchema#unsignedByte",
41 min_count: 0,
42 max_count: MAX_WITT_LEVEL as u32,
43 kind: ViolationKind::CardinalityViolation,
44};
45
46const TOTAL_WIDTH_VIOLATION: ShapeViolation = ShapeViolation {
47 shape_iri: "https://uor.foundation/addr/RingElement",
48 constraint_iri: "https://uor.foundation/addr/RingElement/serializedWidth",
49 property_iri: "https://uor.foundation/addr/RingElement/totalByteCount",
50 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
51 min_count: 0,
52 max_count: RING_VALUE_MAX_BYTES as u32,
53 kind: ViolationKind::CardinalityViolation,
54};
55
56#[derive(Clone)]
61pub struct RingElement {
62 pub(crate) bytes: [u8; RING_VALUE_MAX_BYTES],
63 pub(crate) len: u16,
64}
65
66impl core::fmt::Debug for RingElement {
67 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68 f.debug_struct("RingElement")
69 .field("len", &self.len)
70 .finish_non_exhaustive()
71 }
72}
73
74impl PartialEq for RingElement {
75 fn eq(&self, other: &Self) -> bool {
76 self.tagged_bytes() == other.tagged_bytes()
77 }
78}
79impl Eq for RingElement {}
80
81impl RingElement {
82 pub fn from_components(witt_level: u8, coefficient: u64) -> Result<Self, ShapeViolation> {
84 if witt_level > MAX_WITT_LEVEL {
85 return Err(WITT_LEVEL_VIOLATION);
86 }
87 let coefficient_bytes = (witt_level + 1) as usize;
88 let total = 1 + coefficient_bytes;
89 let mut me = Self {
90 bytes: [0u8; RING_VALUE_MAX_BYTES],
91 len: total as u16,
92 };
93 me.bytes[0] = witt_level;
94 let le = coefficient.to_le_bytes();
95 me.bytes[1..1 + coefficient_bytes].copy_from_slice(&le[..coefficient_bytes]);
96 Ok(me)
97 }
98
99 pub fn parse(raw: &[u8]) -> Result<Self, ShapeViolation> {
101 if raw.is_empty() {
102 return Err(INVALID_RING_VIOLATION);
103 }
104 if raw.len() > RING_VALUE_MAX_BYTES {
105 return Err(TOTAL_WIDTH_VIOLATION);
106 }
107 let witt_level = raw[0];
108 if witt_level > MAX_WITT_LEVEL {
109 return Err(WITT_LEVEL_VIOLATION);
110 }
111 let expected_len = 1 + (witt_level as usize + 1);
112 if raw.len() != expected_len {
113 return Err(INVALID_RING_VIOLATION);
114 }
115 let mut me = Self {
116 bytes: [0u8; RING_VALUE_MAX_BYTES],
117 len: raw.len() as u16,
118 };
119 me.bytes[..raw.len()].copy_from_slice(raw);
120 Ok(me)
121 }
122
123 #[must_use]
125 pub fn tagged_bytes(&self) -> &[u8] {
126 &self.bytes[..self.len as usize]
127 }
128
129 #[must_use]
131 pub fn witt_level(&self) -> u8 {
132 self.bytes[0]
133 }
134}
135
136impl ConstrainedTypeShape for RingElement {
139 const IRI: &'static str = "https://uor.foundation/addr/RingElement";
140 const SITE_COUNT: usize = 1;
141 const CONSTRAINTS: &'static [ConstraintRef] = &[];
142 const CYCLE_SIZE: u64 = u64::MAX;
143}
144
145impl prism::uor_foundation::pipeline::__sdk_seal::Sealed for RingElement {}
146
147impl<'a> IntoBindingValue<'a> for RingElement {
148 fn as_binding_value<const INLINE_BYTES: usize>(&self) -> TermValue<'a, INLINE_BYTES> {
149 TermValue::inline_from_slice(self.tagged_bytes())
152 }
153}
154
155impl PartitionProductFields for RingElement {
156 const FIELDS: &'static [(u32, u32)] = &[];
157 const FIELD_NAMES: &'static [&'static str] = &[];
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn from_components_round_trip() {
166 let e = RingElement::from_components(2, 0x0001_0203).expect("valid");
167 assert_eq!(e.bytes[0], 2);
168 assert_eq!(&e.bytes[1..4], &[0x03, 0x02, 0x01]);
169 }
170
171 #[test]
172 fn parse_matches_construction() {
173 let constructed = RingElement::from_components(1, 0x0102).expect("valid");
174 let parsed = RingElement::parse(&[1, 0x02, 0x01]).expect("valid");
175 assert_eq!(constructed, parsed);
176 }
177
178 #[test]
179 fn rejects_overflow_witt_level() {
180 let err = RingElement::from_components(MAX_WITT_LEVEL + 1, 0).expect_err("must reject");
181 assert_eq!(err.constraint_iri, WITT_LEVEL_VIOLATION.constraint_iri);
182 let err = RingElement::parse(&[MAX_WITT_LEVEL + 1, 0]).expect_err("must reject");
183 assert_eq!(err.constraint_iri, WITT_LEVEL_VIOLATION.constraint_iri);
184 }
185
186 #[test]
187 fn rejects_truncated_bytes() {
188 let err = RingElement::parse(&[2, 0, 0]).expect_err("must reject");
189 assert_eq!(err.constraint_iri, INVALID_RING_VIOLATION.constraint_iri);
190 }
191}