pallet_schemas/
types.rs

1//! Types for the Schema Pallet
2use crate::{Config, Error};
3use common_primitives::schema::{
4	ModelType, PayloadLocation, SchemaId, SchemaSetting, SchemaSettings, SchemaVersion,
5	SchemaVersionResponse,
6};
7use core::fmt::Debug;
8use frame_support::{ensure, pallet_prelude::ConstU32, traits::StorageVersion, BoundedVec};
9use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
10use scale_info::TypeInfo;
11use sp_runtime::DispatchError;
12extern crate alloc;
13use alloc::{string::String, vec, vec::Vec};
14use frame_support::traits::Len;
15
16/// Current storage version of the schemas pallet.
17pub const SCHEMA_STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
18
19/// The maximum size of schema name including all parts
20pub const SCHEMA_NAME_BYTES_MAX: u32 = 32; // Hard limit of 32 bytes
21/// A schema name following following structure NAMESPACE.DESCRIPTOR
22pub type SchemaNamePayload = BoundedVec<u8, ConstU32<SCHEMA_NAME_BYTES_MAX>>;
23/// schema namespace type
24pub type SchemaNamespace = BoundedVec<u8, ConstU32<NAMESPACE_MAX>>;
25/// schema descriptor type
26pub type SchemaDescriptor = BoundedVec<u8, ConstU32<DESCRIPTOR_MAX>>;
27/// The minimum size of a namespace in schema
28pub const NAMESPACE_MIN: u32 = 3;
29/// The maximum size of a namespace in schema
30pub const NAMESPACE_MAX: u32 = SCHEMA_NAME_BYTES_MAX - (DESCRIPTOR_MIN + 1);
31/// The minimum size of a schema descriptor
32pub const DESCRIPTOR_MIN: u32 = 1;
33/// The maximum size of a schema descriptor
34pub const DESCRIPTOR_MAX: u32 = SCHEMA_NAME_BYTES_MAX - (NAMESPACE_MIN + 1);
35/// separator character
36pub const SEPARATOR_CHAR: char = '.';
37/// maximum number of versions for a certain schema name
38/// -1 is to avoid overflow when converting the (index + 1) to `SchemaVersion` in `SchemaVersionId`
39pub const MAX_NUMBER_OF_VERSIONS: u32 = SchemaVersion::MAX as u32 - 1;
40
41#[derive(Debug, serde::Serialize, serde::Deserialize)]
42/// Genesis Schemas need a way to load up and this is it!
43pub struct GenesisSchema {
44	/// The type of model (AvroBinary, Parquet, etc.)
45	pub model_type: ModelType,
46	/// The payload location
47	pub payload_location: PayloadLocation,
48	/// The Payload Model
49	pub model: String,
50	/// Schema Full Name: {Namespace}.{Descriptor}
51	pub name: String,
52	/// Settings
53	pub settings: Vec<SchemaSetting>,
54}
55
56#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen)]
57/// A structure defining a Schema information (excluding the payload)
58pub struct SchemaInfo {
59	/// The type of model (AvroBinary, Parquet, etc.)
60	pub model_type: ModelType,
61	/// The payload location
62	pub payload_location: PayloadLocation,
63	/// additional control settings for the schema
64	pub settings: SchemaSettings,
65	/// Defines if a schema has a name or not
66	pub has_name: bool,
67}
68
69#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen)]
70/// A structure defining name of a schema
71pub struct SchemaName {
72	/// namespace or domain of the schema
73	pub namespace: SchemaNamespace,
74	/// name or descriptor of this schema
75	pub descriptor: SchemaDescriptor,
76}
77
78#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen, Default)]
79/// A structure defining name of a schema
80pub struct SchemaVersionId {
81	/// the index of each item + 1 is considered as their version.
82	/// Ex: the schemaId located in `ids[2]` is for version number 3
83	pub ids: BoundedVec<SchemaId, ConstU32<MAX_NUMBER_OF_VERSIONS>>,
84}
85
86impl SchemaName {
87	/// parses and verifies the request and returns the SchemaName type if successful
88	pub fn try_parse<T: Config>(
89		payload: SchemaNamePayload,
90		is_strict: bool,
91	) -> Result<SchemaName, DispatchError> {
92		// check if all ascii
93		let mut str = String::from_utf8(payload.into_inner())
94			.map_err(|_| Error::<T>::InvalidSchemaNameEncoding)?;
95		ensure!(str.is_ascii(), Error::<T>::InvalidSchemaNameEncoding);
96
97		// to canonical form
98		str = String::from(str.to_lowercase().trim());
99
100		// check if alphabetic or - or separator character
101		ensure!(
102			str.chars().all(|c| c.is_ascii_alphabetic() || c == '-' || c == SEPARATOR_CHAR),
103			Error::<T>::InvalidSchemaNameCharacters
104		);
105
106		// split to namespace and descriptor
107		let chunks: Vec<_> = str.split(SEPARATOR_CHAR).collect();
108		ensure!(
109			chunks.len() == 2 || (chunks.len() == 1 && !is_strict),
110			Error::<T>::InvalidSchemaNameStructure
111		);
112
113		// check namespace
114		let namespace = BoundedVec::try_from(chunks[0].as_bytes().to_vec())
115			.map_err(|_| Error::<T>::InvalidSchemaNamespaceLength)?;
116		ensure!(NAMESPACE_MIN <= namespace.len() as u32, Error::<T>::InvalidSchemaNamespaceLength);
117		// should not start or end with -
118		ensure!(
119			!(namespace.starts_with(b"-") || namespace.ends_with(b"-")),
120			Error::<T>::InvalidSchemaNameStructure
121		);
122
123		// check descriptor
124		let descriptor = match chunks.len() == 2 {
125			true => {
126				let descriptor = BoundedVec::try_from(chunks[1].as_bytes().to_vec())
127					.map_err(|_| Error::<T>::InvalidSchemaDescriptorLength)?;
128				ensure!(
129					DESCRIPTOR_MIN <= descriptor.len() as u32,
130					Error::<T>::InvalidSchemaDescriptorLength
131				);
132				// should not start or end with -
133				ensure!(
134					!(descriptor.starts_with(b"-") || descriptor.ends_with(b"-")),
135					Error::<T>::InvalidSchemaNameStructure
136				);
137				descriptor
138			},
139			false => BoundedVec::default(),
140		};
141
142		Ok(SchemaName { namespace, descriptor })
143	}
144
145	/// get the combined name namespace.descriptor
146	pub fn get_combined_name(&self) -> Vec<u8> {
147		[
148			self.namespace.clone().into_inner(),
149			vec![SEPARATOR_CHAR as u8],
150			self.descriptor.clone().into_inner(),
151		]
152		.concat()
153	}
154
155	/// creates a new SchemaName using provided descriptor
156	pub fn new_with_descriptor(&self, descriptor: SchemaDescriptor) -> Self {
157		Self { namespace: self.namespace.clone(), descriptor }
158	}
159
160	/// returns true if the descriptor exists
161	pub fn descriptor_exists(&self) -> bool {
162		self.descriptor.len() > 0
163	}
164}
165
166impl SchemaVersionId {
167	/// adds a new schema id and returns the version for that schema_id
168	pub fn add<T: Config>(&mut self, schema_id: SchemaId) -> Result<SchemaVersion, DispatchError> {
169		let is_new = !self.ids.iter().any(|id| id == &schema_id);
170		ensure!(is_new, Error::<T>::SchemaIdAlreadyExists);
171		self.ids
172			.try_push(schema_id)
173			.map_err(|_| Error::<T>::ExceedsMaxNumberOfVersions)?;
174		let version = self.ids.len() as SchemaVersion;
175		Ok(version)
176	}
177
178	/// convert into a response vector
179	pub fn convert_to_response(&self, schema_name: &SchemaName) -> Vec<SchemaVersionResponse> {
180		self.ids
181			.iter()
182			.enumerate()
183			.map(|(index, schema_id)| SchemaVersionResponse {
184				schema_name: schema_name.get_combined_name(),
185				schema_id: *schema_id,
186				schema_version: (index + 1) as SchemaVersion,
187			})
188			.collect()
189	}
190}