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    AllViewpoints,
49}
50
51impl Display for ReservedWords {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::TrackerSchemas => write!(f, "tracker_schemas"),
55            Self::Governance => write!(f, "governance"),
56            Self::Any => write!(f, "Any"),
57            Self::Witnesses => write!(f, "Witnesses"),
58            Self::Owner => write!(f, "Owner"),
59            Self::AllViewpoints => write!(f, "AllViewpoints"),
60        }
61    }
62}
63
64impl std::str::FromStr for SchemaType {
65    type Err = String;
66
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        // Special case: empty string deserializes to default (empty) digest
69        if s.is_empty() {
70            return Err("Schema_id can not be empty".to_string());
71        }
72
73        match s {
74            "governance" => Ok(Self::Governance),
75            "tracker_schemas" => Ok(Self::TrackerSchemas),
76            _ => Ok(Self::Type(s.to_string())),
77        }
78    }
79}
80
81impl SchemaType {
82    /// Returns the serialized string length of the schema identifier.
83    pub const fn len(&self) -> usize {
84        match self {
85            Self::Governance => "governance".len(),
86            Self::Type(schema_id) => schema_id.len(),
87            Self::TrackerSchemas => "tracker_schemas".len(),
88        }
89    }
90
91    /// Returns `true` when the custom schema identifier is empty.
92    pub const fn is_empty(&self) -> bool {
93        match self {
94            Self::Governance => false,
95            Self::Type(schschema_id) => schschema_id.is_empty(),
96            Self::TrackerSchemas => false,
97        }
98    }
99
100    /// Returns `true` when the schema identifier is valid in stored state.
101    pub fn is_valid(&self) -> bool {
102        match self {
103            Self::Governance => true,
104            Self::TrackerSchemas => true,
105            Self::Type(schema_id) => {
106                !schema_id.is_empty()
107                    && schema_id != &ReservedWords::Governance.to_string()
108                    && schema_id != &ReservedWords::TrackerSchemas.to_string()
109                    && schema_id.trim().len() == schema_id.len()
110            }
111        }
112    }
113
114    /// Returns `true` when the schema identifier is valid in incoming requests.
115    pub fn is_valid_in_request(&self) -> bool {
116        match self {
117            Self::Governance => true,
118            Self::TrackerSchemas => false,
119            Self::Type(schema_id) => {
120                !schema_id.is_empty()
121                    && schema_id != &ReservedWords::Governance.to_string()
122                    && schema_id != &ReservedWords::TrackerSchemas.to_string()
123                    && schema_id.trim().len() == schema_id.len()
124            }
125        }
126    }
127}
128
129impl Display for SchemaType {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        match self {
132            Self::TrackerSchemas => write!(f, "tracker_schemas"),
133            Self::Governance => write!(f, "governance"),
134            Self::Type(schema_id) => write!(f, "{}", schema_id),
135        }
136    }
137}
138
139impl SchemaType {
140    /// Returns `true` when the schema is the governance schema.
141    pub const fn is_gov(&self) -> bool {
142        matches!(self, Self::Governance)
143    }
144}
145
146impl<'de> Deserialize<'de> for SchemaType {
147    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148    where
149        D: Deserializer<'de>,
150    {
151        let s = <String as serde::Deserialize>::deserialize(deserializer)?;
152        if s.is_empty() {
153            return Err(serde::de::Error::custom(
154                "Schema can not be empty".to_string(),
155            ));
156        }
157
158        Ok(match s.as_str() {
159            "governance" => Self::Governance,
160            "tracker_schemas" => Self::TrackerSchemas,
161            _ => Self::Type(s),
162        })
163    }
164}
165
166impl Serialize for SchemaType {
167    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168    where
169        S: Serializer,
170    {
171        match self {
172            Self::TrackerSchemas => serializer.serialize_str("tracker_schemas"),
173            Self::Governance => serializer.serialize_str("governance"),
174            Self::Type(schema) => serializer.serialize_str(schema),
175        }
176    }
177}