1use std::io;
25
26use amplify::hex::ToHex;
27use strict_encoding::{
28 DecodeError, ReadRaw, StrictDecode, StrictEncode, StrictReader, StrictWriter, TypeName, WriteRaw,
29};
30use ultrasonic::{ContractId, Issue};
31
32use crate::sigs::ContentSigs;
33use crate::{Api, Schema, LIB_NAME_SONIC};
34
35pub const ARTICLES_MAGIC_NUMBER: [u8; 8] = *b"ARTICLES";
36pub const ARTICLES_VERSION: [u8; 2] = [0x00, 0x01];
37
38#[derive(Clone, Eq, PartialEq, Debug)]
40#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
41#[strict_type(lib = LIB_NAME_SONIC)]
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
43pub struct Articles {
44 #[cfg_attr(feature = "serde", serde(flatten))]
45 pub schema: Schema,
46 pub contract_sigs: ContentSigs,
47 pub issue: Issue,
48}
49
50impl Articles {
51 pub fn contract_id(&self) -> ContractId { self.issue.contract_id() }
52
53 pub fn api(&self, name: &TypeName) -> &Api { self.schema.api(name) }
54
55 pub fn merge(&mut self, other: Self) -> Result<bool, MergeError> {
56 if self.contract_id() != other.contract_id() {
57 return Err(MergeError::ContractMismatch);
58 }
59
60 self.schema.merge(other.schema)?;
61 self.contract_sigs.merge(other.contract_sigs);
62
63 Ok(true)
64 }
65
66 pub fn decode(reader: &mut StrictReader<impl ReadRaw>) -> Result<Self, DecodeError> {
67 let magic_bytes = <[u8; 8]>::strict_decode(reader)?;
68 if magic_bytes != ARTICLES_MAGIC_NUMBER {
69 return Err(DecodeError::DataIntegrityError(format!(
70 "wrong contract articles magic bytes {}",
71 magic_bytes.to_hex()
72 )));
73 }
74 let version = <[u8; 2]>::strict_decode(reader)?;
75 if version != ARTICLES_VERSION {
76 return Err(DecodeError::DataIntegrityError(format!(
77 "unsupported contract articles version {}",
78 u16::from_be_bytes(version)
79 )));
80 }
81 Self::strict_decode(reader)
82 }
83
84 pub fn encode(&self, mut writer: StrictWriter<impl WriteRaw>) -> io::Result<()> {
85 writer = ARTICLES_MAGIC_NUMBER.strict_encode(writer)?;
87 writer = ARTICLES_VERSION.strict_encode(writer)?;
89 self.strict_encode(writer)?;
90 Ok(())
91 }
92}
93
94#[derive(Clone, Eq, PartialEq, Debug, Display, Error)]
95#[display(doc_comments)]
96pub enum MergeError {
97 ContractMismatch,
99
100 CodexMismatch,
102}
103
104#[cfg(feature = "std")]
105mod _fs {
106 use std::fs::File;
107 use std::io::{self, Read};
108 use std::path::Path;
109
110 use amplify::confinement::U24 as U24MAX;
111 use strict_encoding::{DeserializeError, StreamReader, StreamWriter, StrictReader, StrictWriter};
112
113 use super::Articles;
114
115 impl Articles {
117 pub fn load(path: impl AsRef<Path>) -> Result<Self, DeserializeError> {
118 let file = File::open(path)?;
119 let mut reader = StrictReader::with(StreamReader::new::<U24MAX>(file));
120 let me = Self::decode(&mut reader)?;
121 match reader.unbox().unconfine().read_exact(&mut [0u8; 1]) {
122 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(me),
123 Err(e) => Err(e.into()),
124 Ok(()) => Err(DeserializeError::DataNotEntirelyConsumed),
125 }
126 }
127
128 pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
129 let file = File::create(path)?;
130 let writer = StrictWriter::with(StreamWriter::new::<U24MAX>(file));
131 self.encode(writer)
132 }
133 }
134}