1use std::io;
25
26use aluvm::{Lib, LibId};
27use amplify::confinement::{SmallOrdMap, SmallOrdSet, TinyOrdMap};
28use amplify::hex::ToHex;
29use commit_verify::ReservedBytes;
30use strict_encoding::{
31 DecodeError, ReadRaw, StrictDecode, StrictEncode, StrictReader, StrictWriter, TypeName, WriteRaw,
32};
33use strict_types::TypeSystem;
34use ultrasonic::{CallId, Codex, LibRepo};
35
36use crate::sigs::ContentSigs;
37use crate::{Annotations, Api, MergeError, MethodName, LIB_NAME_SONIC};
38
39pub const SCHEMA_MAGIC_NUMBER: [u8; 8] = *b"COISSUER";
40pub const SCHEMA_VERSION: [u8; 2] = [0x00, 0x01];
41
42#[derive(Clone, Eq, PartialEq, Debug)]
44#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
45#[strict_type(lib = LIB_NAME_SONIC)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
47pub struct Schema {
48 pub codex: Codex,
49 pub default_api: Api,
50 pub default_api_sigs: ContentSigs,
51 pub custom_apis: SmallOrdMap<Api, ContentSigs>,
52 pub libs: SmallOrdSet<Lib>,
53 pub types: TypeSystem,
54 pub codex_sigs: ContentSigs,
55 pub annotations: TinyOrdMap<Annotations, ContentSigs>,
56 pub reserved: ReservedBytes<8>,
57}
58
59impl LibRepo for Schema {
60 fn get_lib(&self, lib_id: LibId) -> Option<&Lib> { self.libs.iter().find(|lib| lib.lib_id() == lib_id) }
61}
62
63impl Schema {
64 pub fn new(codex: Codex, api: Api, libs: impl IntoIterator<Item = Lib>, types: TypeSystem) -> Self {
65 Schema {
67 codex,
68 default_api: api,
69 default_api_sigs: none!(),
70 custom_apis: none!(),
71 libs: SmallOrdSet::from_iter_checked(libs),
72 types,
73 codex_sigs: none!(),
74 annotations: none!(),
75 reserved: zero!(),
76 }
77 }
78
79 pub fn api(&self, name: &TypeName) -> &Api {
80 self.custom_apis
81 .keys()
82 .find(|api| api.name() == Some(name))
83 .expect("API is not known")
84 }
85
86 pub fn call_id(&self, method: impl Into<MethodName>) -> CallId {
87 self.default_api
88 .verifier(method)
89 .expect("calling to method absent in Codex API")
90 }
91
92 pub fn merge(&mut self, other: Self) -> Result<bool, MergeError> {
93 if self.codex.codex_id() != other.codex.codex_id() {
94 return Err(MergeError::CodexMismatch);
95 }
96 self.codex_sigs.merge(other.codex_sigs);
97
98 if self.default_api != other.default_api {
99 let _ = self
100 .custom_apis
101 .insert(other.default_api, other.default_api_sigs);
102 } else {
103 self.default_api_sigs.merge(other.default_api_sigs);
104 }
105
106 for (api, other_sigs) in other.custom_apis {
107 let Ok(entry) = self.custom_apis.entry(api) else {
108 continue;
109 };
110 entry.or_default().merge(other_sigs);
111 }
112
113 let _ = self.libs.extend(other.libs);
117 let _ = self.types.extend(other.types);
118
119 for (annotation, other_sigs) in other.annotations {
120 let Ok(entry) = self.annotations.entry(annotation) else {
121 continue;
122 };
123 entry.or_default().merge(other_sigs);
124 }
125
126 Ok(true)
127 }
128
129 pub fn decode(reader: &mut StrictReader<impl ReadRaw>) -> Result<Self, DecodeError> {
130 let magic_bytes = <[u8; 8]>::strict_decode(reader)?;
131 if magic_bytes != SCHEMA_MAGIC_NUMBER {
132 return Err(DecodeError::DataIntegrityError(format!(
133 "wrong contract issuer schema magic bytes {}",
134 magic_bytes.to_hex()
135 )));
136 }
137 let version = <[u8; 2]>::strict_decode(reader)?;
138 if version != SCHEMA_VERSION {
139 return Err(DecodeError::DataIntegrityError(format!(
140 "unsupported contract issuer schema version {}",
141 u16::from_be_bytes(version)
142 )));
143 }
144 Self::strict_decode(reader)
145 }
146
147 pub fn encode(&self, mut writer: StrictWriter<impl WriteRaw>) -> io::Result<()> {
148 writer = SCHEMA_MAGIC_NUMBER.strict_encode(writer)?;
150 writer = SCHEMA_VERSION.strict_encode(writer)?;
152 self.strict_encode(writer)?;
153 Ok(())
154 }
155}
156
157#[cfg(feature = "std")]
158mod _fs {
159 use std::fs::File;
160 use std::io::{self, Read};
161 use std::path::Path;
162
163 use amplify::confinement::U24 as U24MAX;
164 use strict_encoding::{DeserializeError, StreamReader, StreamWriter, StrictReader, StrictWriter};
165
166 use super::Schema;
167
168 impl Schema {
169 pub fn load(path: impl AsRef<Path>) -> Result<Self, DeserializeError> {
170 let file = File::open(path)?;
171 let mut reader = StrictReader::with(StreamReader::new::<U24MAX>(file));
172 let me = Self::decode(&mut reader)?;
173 match reader.unbox().unconfine().read_exact(&mut [0u8; 1]) {
174 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(me),
175 Err(e) => Err(e.into()),
176 Ok(()) => Err(DeserializeError::DataNotEntirelyConsumed),
177 }
178 }
179
180 pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
181 let file = File::create_new(path)?;
182 let writer = StrictWriter::with(StreamWriter::new::<U24MAX>(file));
183 self.encode(writer)
184 }
185 }
186}