Skip to main content

ave_common/
schematype.rs

1//! Schema identifiers used by Ave subjects and governance rules.
2
3use std::fmt::Display;
4
5use borsh::{BorshDeserialize, BorshSerialize};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7#[cfg(feature = "openapi")]
8use utoipa::ToSchema;
9
10#[cfg(feature = "typescript")]
11use ts_rs::TS;
12
13/// Schema identifier.
14///
15/// Reserved values are represented as dedicated variants and custom schema ids
16/// are stored in [`SchemaType::Type`].
17#[derive(
18    Default,
19    Debug,
20    Clone,
21    Hash,
22    PartialEq,
23    Eq,
24    Ord,
25    PartialOrd,
26    BorshSerialize,
27    BorshDeserialize,
28)]
29#[cfg_attr(feature = "openapi", derive(ToSchema))]
30/// Reserved words used by the schema system.
31#[cfg_attr(feature = "typescript", derive(TS))]
32#[cfg_attr(feature = "typescript", ts(export, type = "string"))]
33pub enum SchemaType {
34    #[default]
35    Governance,
36    Type(String),
37    TrackerSchemas,
38}
39
40#[cfg_attr(feature = "typescript", derive(TS))]
41#[cfg_attr(feature = "typescript", ts(export))]
42pub enum ReservedWords {
43    TrackerSchemas,
44    Governance,
45    Any,
46    Witnesses,
47    Owner,
48}
49
50impl Display for ReservedWords {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            Self::TrackerSchemas => write!(f, "tracker_schemas"),
54            Self::Governance => write!(f, "governance"),
55            Self::Any => write!(f, "Any"),
56            Self::Witnesses => write!(f, "Witnesses"),
57            Self::Owner => write!(f, "Owner"),
58        }
59    }
60}
61
62impl std::str::FromStr for SchemaType {
63    type Err = String;
64
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        // Special case: empty string deserializes to default (empty) digest
67        if s.is_empty() {
68            return Err("Schema_id can not be empty".to_string());
69        }
70
71        match s {
72            "governance" => Ok(Self::Governance),
73            "tracker_schemas" => Ok(Self::TrackerSchemas),
74            _ => Ok(Self::Type(s.to_string())),
75        }
76    }
77}
78
79impl SchemaType {
80    /// Returns the serialized string length of the schema identifier.
81    pub const fn len(&self) -> usize {
82        match self {
83            Self::Governance => "governance".len(),
84            Self::Type(schema_id) => schema_id.len(),
85            Self::TrackerSchemas => "tracker_schemas".len(),
86        }
87    }
88
89    /// Returns `true` when the custom schema identifier is empty.
90    pub const fn is_empty(&self) -> bool {
91        match self {
92            Self::Governance => false,
93            Self::Type(schschema_id) => schschema_id.is_empty(),
94            Self::TrackerSchemas => false,
95        }
96    }
97
98    /// Returns `true` when the schema identifier is valid in stored state.
99    pub fn is_valid(&self) -> bool {
100        match self {
101            Self::Governance => true,
102            Self::TrackerSchemas => true,
103            Self::Type(schema_id) => {
104                !schema_id.is_empty()
105                    && schema_id != &ReservedWords::Governance.to_string()
106                    && schema_id != &ReservedWords::TrackerSchemas.to_string()
107                    && schema_id.trim().len() == schema_id.len()
108            }
109        }
110    }
111
112    /// Returns `true` when the schema identifier is valid in incoming requests.
113    pub fn is_valid_in_request(&self) -> bool {
114        match self {
115            Self::Governance => true,
116            Self::TrackerSchemas => false,
117            Self::Type(schema_id) => {
118                !schema_id.is_empty()
119                    && schema_id != &ReservedWords::Governance.to_string()
120                    && schema_id != &ReservedWords::TrackerSchemas.to_string()
121                    && schema_id.trim().len() == schema_id.len()
122            }
123        }
124    }
125}
126
127impl Display for SchemaType {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            Self::TrackerSchemas => write!(f, "tracker_schemas"),
131            Self::Governance => write!(f, "governance"),
132            Self::Type(schema_id) => write!(f, "{}", schema_id),
133        }
134    }
135}
136
137impl SchemaType {
138    /// Returns `true` when the schema is the governance schema.
139    pub const fn is_gov(&self) -> bool {
140        matches!(self, Self::Governance)
141    }
142}
143
144impl<'de> Deserialize<'de> for SchemaType {
145    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
146    where
147        D: Deserializer<'de>,
148    {
149        let s = <String as serde::Deserialize>::deserialize(deserializer)?;
150        if s.is_empty() {
151            return Err(serde::de::Error::custom(
152                "Schema can not be empty".to_string(),
153            ));
154        }
155
156        Ok(match s.as_str() {
157            "governance" => Self::Governance,
158            "tracker_schemas" => Self::TrackerSchemas,
159            _ => Self::Type(s),
160        })
161    }
162}
163
164impl Serialize for SchemaType {
165    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166    where
167        S: Serializer,
168    {
169        match self {
170            Self::TrackerSchemas => serializer.serialize_str("tracker_schemas"),
171            Self::Governance => serializer.serialize_str("governance"),
172            Self::Type(schema) => serializer.serialize_str(schema),
173        }
174    }
175}