1pub(crate) fn present<'de, D: serde::Deserializer<'de>, T: serde::Deserialize<'de>>(
13 deserializer: D,
14) -> Result<Option<T>, D::Error> {
15 T::deserialize(deserializer).map(Some)
16}
17
18pub(crate) fn non_empty_map<'de, D, K, V>(deserializer: D) -> Result<std::collections::BTreeMap<K, V>, D::Error>
20where
21 D: serde::Deserializer<'de>,
22 K: serde::Deserialize<'de> + Ord,
23 V: serde::Deserialize<'de>,
24{
25 let map = <std::collections::BTreeMap<K, V> as serde::Deserialize>::deserialize(deserializer)?;
26 if map.is_empty() { Err(serde::de::Error::custom("an empty map is left out, not written")) } else { Ok(map) }
27}
28
29pub(crate) fn present_text<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Option<String>, D::Error> {
31 let text = <String as serde::Deserialize>::deserialize(deserializer)?;
32 if text.is_empty() {
33 Err(serde::de::Error::custom("an empty text is left out, not written"))
34 } else {
35 Ok(Some(text))
36 }
37}
38
39pub(crate) fn is_uuid(s: &str) -> bool {
41 s.len() == 36
42 && s.bytes().enumerate().all(|(i, b)| match i {
43 8 | 13 | 18 | 23 => b == b'-',
44 _ => b.is_ascii_digit() || (b'a'..=b'f').contains(&b),
45 })
46}
47
48pub(crate) fn parse_sha256(s: &str) -> Option<[u8; 32]> {
50 let hex = s.strip_prefix("sha256:")?;
51 if hex.len() != 64 || hex.bytes().any(|b| b.is_ascii_uppercase()) {
52 return None;
53 }
54 let mut bytes = [0u8; 32];
55 hex::decode_to_slice(hex, &mut bytes).ok()?;
56 Some(bytes)
57}
58
59pub(crate) fn non_empty<'de, D: serde::Deserializer<'de>, T: serde::Deserialize<'de>>(
61 deserializer: D,
62) -> Result<Vec<T>, D::Error> {
63 let items = <Vec<T> as serde::Deserialize>::deserialize(deserializer)?;
64 if items.is_empty() { Err(serde::de::Error::custom("an empty list is left out, not written")) } else { Ok(items) }
65}
66
67pub(crate) fn safe_u64<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
69 let value = <u64 as serde::Deserialize>::deserialize(deserializer)?;
70 if value > MAX_SAFE_INTEGER {
71 return Err(serde::de::Error::custom(format!("{value} is larger than 2^53 - 1")));
72 }
73 Ok(value)
74}
75
76mod balance;
77mod booking;
78mod canonical;
79mod chart;
80mod clock;
81mod date;
82mod decimal;
83mod document;
84mod event;
85mod money;
86mod names;
87mod object;
88mod party;
89mod report;
90mod tax;
91
92pub use balance::{BalanceError, Balances};
93pub use booking::{AccountId, Booking, BookingError, Posting};
94pub use canonical::{CanonicalError, MAX_SAFE_INTEGER, canonicalize, to_canonical_vec};
95pub use chart::{Account, AccountKind, Chart, ChartError, ChartTranslation, TranslationError};
96pub use clock::{Clock, DeviceId, Hlc};
97pub use date::Date;
98pub use decimal::Decimal;
99pub use document::{ContentHash, Document, DocumentId, DocumentKind, Original};
100pub use event::{AccountProblem, ChainProblem, DocumentProblem, Event, EventHash, EventSet, PartyProblem, Payload};
101pub use money::{Currency, CurrencyDefinition, CurrencyTable, DefinitionError, Money};
102pub use names::Names;
103pub use object::{Object, ObjectError, from_root, to_root};
104pub use party::{Country, Organisation, PartyId, Person, Registration, Role, RoleKind, Shares, TaxId};
105pub use report::{MappingStatus, ReportMapping, TaxRule, VatAccounts};
106pub use tax::{TaxCode, TaxCodeDefinition, TaxDirection, TaxTable};
107
108#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
110pub enum ParseError {
111 #[error("not a decimal number: {0:?}")]
113 Decimal(String),
114 #[error("not a currency code: {0:?}")]
116 Currency(String),
117 #[error("not a date: {0:?}")]
119 Date(String),
120 #[error("not a device id: {0:?}")]
122 Device(String),
123 #[error("not an account id: {0:?}")]
125 Account(String),
126 #[error("not a document id: {0:?}")]
128 Document(String),
129 #[error("not a party id: {0:?}")]
131 Party(String),
132 #[error("not a country code: {0:?}")]
134 Country(String),
135 #[error("not a tax code: {0:?}")]
137 TaxCode(String),
138 #[error("not a hash: {0:?}")]
140 Hash(String),
141}