1use std::cmp::Ordering;
24use std::fmt::{self, Display, Formatter};
25use std::str::FromStr;
26
27use aluvm::library::LibId;
28use amplify::confinement::TinyOrdMap;
29use amplify::{ByteArray, Bytes32};
30use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
31use commit_verify::{CommitEncode, CommitEngine, CommitId, CommitmentId, DigestExt, Sha256};
32use strict_encoding::{
33 StrictDecode, StrictDeserialize, StrictEncode, StrictSerialize, StrictType, TypeName,
34};
35use strict_types::{FieldName, SemId};
36
37use super::{AssignmentType, GenesisSchema, OwnedStateSchema, TransitionSchema};
38use crate::{impl_serde_baid64, Ffv, GlobalStateSchema, StateType, LIB_NAME_RGB_COMMIT};
39
40#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Display)]
41#[wrapper(FromStr, LowerHex, UpperHex)]
42#[display("0x{0:04X}")]
43#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
44#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
45#[cfg_attr(
46 feature = "serde",
47 derive(Serialize, Deserialize),
48 serde(crate = "serde_crate", rename_all = "camelCase")
49)]
50pub struct MetaType(u16);
51impl MetaType {
52 pub const fn with(ty: u16) -> Self { Self(ty) }
53}
54
55#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Display)]
56#[wrapper(FromStr, LowerHex, UpperHex)]
57#[display("0x{0:04X}")]
58#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
59#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
60#[cfg_attr(
61 feature = "serde",
62 derive(Serialize, Deserialize),
63 serde(crate = "serde_crate", rename_all = "camelCase")
64)]
65pub struct GlobalStateType(u16);
66impl GlobalStateType {
67 pub const fn with(ty: u16) -> Self { Self(ty) }
68}
69
70#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Display)]
71#[wrapper(FromStr, LowerHex, UpperHex)]
72#[display("0x{0:04X}")]
73#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
74#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
75#[cfg_attr(
76 feature = "serde",
77 derive(Serialize, Deserialize),
78 serde(crate = "serde_crate", rename_all = "camelCase")
79)]
80pub struct TransitionType(u16);
81impl TransitionType {
82 pub const fn with(ty: u16) -> Self { Self(ty) }
83}
84
85#[derive(Clone, PartialEq, Eq, Debug)]
86#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
87#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = order)]
88#[cfg_attr(
89 feature = "serde",
90 derive(Serialize, Deserialize),
91 serde(crate = "serde_crate", rename_all = "camelCase")
92)]
93pub struct AssignmentDetails {
94 pub owned_state_schema: OwnedStateSchema,
95 pub name: FieldName,
96 pub default_transition: TransitionType,
97}
98
99#[derive(Clone, PartialEq, Eq, Debug)]
100#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
101#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = order)]
102#[cfg_attr(
103 feature = "serde",
104 derive(Serialize, Deserialize),
105 serde(crate = "serde_crate", rename_all = "camelCase")
106)]
107pub struct GlobalDetails {
108 pub global_state_schema: GlobalStateSchema,
109 pub name: FieldName,
110}
111
112#[derive(Clone, PartialEq, Eq, Debug)]
113#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
114#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = order)]
115#[cfg_attr(
116 feature = "serde",
117 derive(Serialize, Deserialize),
118 serde(crate = "serde_crate", rename_all = "camelCase")
119)]
120pub struct MetaDetails {
121 pub sem_id: SemId,
122 pub name: FieldName,
123}
124
125#[derive(Clone, PartialEq, Eq, Debug)]
126#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
127#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = order)]
128#[cfg_attr(
129 feature = "serde",
130 derive(Serialize, Deserialize),
131 serde(crate = "serde_crate", rename_all = "camelCase")
132)]
133pub struct TransitionDetails {
134 pub transition_schema: TransitionSchema,
135 pub name: FieldName,
136}
137
138impl TransitionType {
139 pub const REPLACE: Self = TransitionType(8011);
140 pub fn is_replace(self) -> bool { self == Self::REPLACE }
141}
142
143#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
147#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
148#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
149#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
150pub struct SchemaId(
151 #[from]
152 #[from([u8; 32])]
153 Bytes32,
154);
155
156impl SchemaId {
157 pub const fn from_array(id: [u8; 32]) -> Self { SchemaId(Bytes32::from_array(id)) }
158}
159
160impl From<Sha256> for SchemaId {
161 fn from(hasher: Sha256) -> Self { hasher.finish().into() }
162}
163
164impl CommitmentId for SchemaId {
165 const TAG: &'static str = "urn:lnp-bp:rgb:schema#2024-02-03";
166}
167
168impl DisplayBaid64 for SchemaId {
169 const HRI: &'static str = "rgb:sch";
170 const CHUNKING: bool = false;
171 const PREFIX: bool = true;
172 const EMBED_CHECKSUM: bool = false;
173 const MNEMONIC: bool = true;
174 fn to_baid64_payload(&self) -> [u8; 32] { self.to_byte_array() }
175}
176impl FromBaid64Str for SchemaId {}
177impl FromStr for SchemaId {
178 type Err = Baid64ParseError;
179 fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
180}
181impl Display for SchemaId {
182 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
183}
184
185impl_serde_baid64!(SchemaId);
186
187#[derive(Clone, Eq, Debug)]
188#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
189#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
190#[cfg_attr(
191 feature = "serde",
192 derive(Serialize, Deserialize),
193 serde(crate = "serde_crate", rename_all = "camelCase")
194)]
195pub struct Schema {
196 pub ffv: Ffv,
197
198 pub name: TypeName,
199
200 pub meta_types: TinyOrdMap<MetaType, MetaDetails>,
201 pub global_types: TinyOrdMap<GlobalStateType, GlobalDetails>,
202 pub owned_types: TinyOrdMap<AssignmentType, AssignmentDetails>,
203 pub genesis: GenesisSchema,
204 pub transitions: TinyOrdMap<TransitionType, TransitionDetails>,
205
206 pub default_assignment: Option<AssignmentType>,
207}
208
209impl CommitEncode for Schema {
210 type CommitmentId = SchemaId;
211
212 fn commit_encode(&self, e: &mut CommitEngine) {
213 e.commit_to_serialized(&self.ffv);
214
215 e.commit_to_serialized(&self.name);
216
217 e.commit_to_map(&self.meta_types);
218 e.commit_to_map(&self.global_types);
219 e.commit_to_map(&self.owned_types);
220 e.commit_to_serialized(&self.genesis);
221 e.commit_to_map(&self.transitions);
222
223 e.commit_to_option(&self.default_assignment);
224 }
225}
226
227impl PartialEq for Schema {
228 fn eq(&self, other: &Self) -> bool { self.schema_id() == other.schema_id() }
229}
230
231impl Ord for Schema {
232 fn cmp(&self, other: &Self) -> Ordering { self.schema_id().cmp(&other.schema_id()) }
233}
234
235impl PartialOrd for Schema {
236 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
237}
238
239impl StrictSerialize for Schema {}
240impl StrictDeserialize for Schema {}
241
242impl Schema {
243 #[inline]
244 pub fn schema_id(&self) -> SchemaId { self.commit_id() }
245
246 pub fn types(&self) -> impl Iterator<Item = SemId> + '_ {
247 self.meta_types
248 .values()
249 .map(|i| &i.sem_id)
250 .cloned()
251 .chain(
252 self.global_types
253 .values()
254 .map(|i| i.global_state_schema.sem_id),
255 )
256 .chain(
257 self.owned_types
258 .values()
259 .filter_map(|ai| OwnedStateSchema::sem_id(&ai.owned_state_schema)),
260 )
261 }
262
263 pub fn libs(&self) -> impl Iterator<Item = LibId> + '_ {
264 self.genesis
265 .validator
266 .iter()
267 .copied()
268 .chain(
269 self.transitions
270 .values()
271 .filter_map(|i| i.transition_schema.validator),
272 )
273 .map(|site| site.lib)
274 }
275
276 pub fn default_transition_for_assignment(
277 &self,
278 assignment_type: &AssignmentType,
279 ) -> TransitionType {
280 self.owned_types
281 .get(assignment_type)
282 .expect("invalid schema")
283 .default_transition
284 }
285
286 pub fn assignment(&self, name: impl Into<FieldName>) -> (&AssignmentType, &AssignmentDetails) {
287 let name = name.into();
288 self.owned_types
289 .iter()
290 .find(|(_, i)| i.name == name)
291 .expect("cannot find assignment with the given name")
292 }
293
294 pub fn assignment_type(&self, name: impl Into<FieldName>) -> AssignmentType {
295 *self.assignment(name).0
296 }
297
298 pub fn assignment_name(&self, type_id: AssignmentType) -> &FieldName {
299 &self
300 .owned_types
301 .iter()
302 .find(|(id, _)| *id == &type_id)
303 .expect("cannot find assignment with the given type ID")
304 .1
305 .name
306 }
307
308 pub fn assignment_types_for_state(&self, state_type: StateType) -> Vec<&AssignmentType> {
309 self.owned_types
310 .iter()
311 .filter_map(|(at, ai)| {
312 if ai.owned_state_schema.state_type() == state_type {
313 Some(at)
314 } else {
315 None
316 }
317 })
318 .collect()
319 }
320
321 pub fn global(&self, name: impl Into<FieldName>) -> (&GlobalStateType, &GlobalDetails) {
322 let name = name.into();
323 self.global_types
324 .iter()
325 .find(|(_, i)| i.name == name)
326 .expect("cannot find global with the given name")
327 }
328
329 pub fn global_type(&self, name: impl Into<FieldName>) -> GlobalStateType {
330 *self.global(name).0
331 }
332
333 pub fn meta(&self, name: impl Into<FieldName>) -> (&MetaType, &MetaDetails) {
334 let name = name.into();
335 self.meta_types
336 .iter()
337 .find(|(_, i)| i.name == name)
338 .expect("cannot find meta with the given name")
339 }
340
341 pub fn meta_type(&self, name: impl Into<FieldName>) -> MetaType { *self.meta(name).0 }
342
343 pub fn meta_name(&self, type_id: MetaType) -> &FieldName {
344 &self
345 .meta_types
346 .iter()
347 .find(|(id, _)| *id == &type_id)
348 .expect("cannot find meta with the given type ID")
349 .1
350 .name
351 }
352
353 pub fn transition(&self, name: impl Into<FieldName>) -> (&TransitionType, &TransitionDetails) {
354 let name = name.into();
355 self.transitions
356 .iter()
357 .find(|(_, i)| i.name == name)
358 .expect("cannot find transition with the given name")
359 }
360
361 pub fn transition_type(&self, name: impl Into<FieldName>) -> TransitionType {
362 *self.transition(name).0
363 }
364}
365
366#[cfg(test)]
367mod test {
368 use strict_encoding::StrictDumb;
369
370 use super::*;
371
372 #[test]
373 fn display() {
374 let dumb = SchemaId::strict_dumb();
375 assert_eq!(
376 dumb.to_string(),
377 "rgb:sch:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#distant-history-exotic"
378 );
379 assert_eq!(
380 &format!("{dumb:-}"),
381 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#distant-history-exotic"
382 );
383
384 let less_dumb = SchemaId::from_byte_array(*b"EV4350-'4vwj'4;v-w94w'e'vFVVDhpq");
385 assert_eq!(
386 less_dumb.to_string(),
387 "rgb:sch:RVY0MzUwLSc0dndqJzQ7di13OTR3J2UndkZWVkRocHE#lemon-diamond-cartoon"
388 );
389 assert_eq!(
390 &format!("{less_dumb:-}"),
391 "RVY0MzUwLSc0dndqJzQ7di13OTR3J2UndkZWVkRocHE#lemon-diamond-cartoon"
392 );
393 assert_eq!(
394 &format!("{less_dumb:#}"),
395 "rgb:sch:RVY0MzUwLSc0dndqJzQ7di13OTR3J2UndkZWVkRocHE"
396 );
397 assert_eq!(&format!("{less_dumb:-#}"), "RVY0MzUwLSc0dndqJzQ7di13OTR3J2UndkZWVkRocHE");
398 }
399}