1use crate::{Config, Error};
3use common_primitives::schema::{
4 IntentGroupId, IntentId, IntentSetting, IntentSettings, MappedEntityIdentifier, ModelType,
5 NameLookupResponse, PayloadLocation, SchemaId, SchemaStatus, SchemaVersion,
6 SchemaVersionResponse,
7};
8use core::fmt::Debug;
9use frame_support::{ensure, pallet_prelude::ConstU32, traits::StorageVersion, BoundedVec};
10use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
11use scale_info::TypeInfo;
12use sp_runtime::DispatchError;
13extern crate alloc;
14use alloc::{string::String, vec, vec::Vec};
15use frame_support::traits::Len;
16
17pub const SCHEMA_STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
19
20pub const SCHEMA_NAME_BYTES_MAX: u32 = 32; pub type SchemaNamePayload = BoundedVec<u8, ConstU32<SCHEMA_NAME_BYTES_MAX>>;
24pub type SchemaProtocolName = BoundedVec<u8, ConstU32<PROTOCOL_NAME_MAX>>;
26pub type SchemaDescriptor = BoundedVec<u8, ConstU32<DESCRIPTOR_MAX>>;
28pub const PROTOCOL_NAME_MIN: u32 = 3;
30pub const PROTOCOL_NAME_MAX: u32 = SCHEMA_NAME_BYTES_MAX - (DESCRIPTOR_MIN + 1);
32pub const DESCRIPTOR_MIN: u32 = 1;
34pub const DESCRIPTOR_MAX: u32 = SCHEMA_NAME_BYTES_MAX - (PROTOCOL_NAME_MIN + 1);
36pub const MAX_INTENTS_PER_DELEGATION_GROUP: u32 = 10;
38pub const SEPARATOR_CHAR: char = '.';
40pub const MAX_NUMBER_OF_VERSIONS: u32 = SchemaVersion::MAX as u32 - 1;
43
44#[allow(type_alias_bounds)]
46pub type SchemaPayload<T: Config> =
47 BoundedVec<u8, <T as Config>::SchemaModelMaxBytesBoundedVecLimit>;
48
49#[derive(Debug, serde::Serialize, serde::Deserialize)]
50pub struct GenesisSchema {
52 pub schema_id: SchemaId,
54 pub intent_id: IntentId,
56 pub model_type: ModelType,
58 pub model: String,
60 pub status: SchemaStatus,
62}
63
64#[derive(Debug, serde::Serialize, serde::Deserialize)]
65pub struct GenesisIntent {
67 pub intent_id: IntentId,
69 pub payload_location: PayloadLocation,
71 pub name: String,
73 pub settings: Vec<IntentSetting>,
75}
76
77#[derive(Debug, serde::Serialize, serde::Deserialize)]
78pub struct GenesisIntentGroup {
80 pub intent_group_id: IntentGroupId,
82 pub name: String,
84 pub intent_ids: Vec<IntentId>,
86}
87
88#[derive(Debug, serde::Serialize, serde::Deserialize)]
89pub struct GenesisSchemasPalletConfig {
91 pub schema_identifier_max: Option<SchemaId>,
93 pub intent_identifier_max: Option<IntentId>,
95 pub intent_group_identifier_max: Option<IntentGroupId>,
97 pub max_schema_model_size: Option<u32>,
99 pub schemas: Option<Vec<GenesisSchema>>,
101 pub intents: Option<Vec<GenesisIntent>>,
103 pub intent_groups: Option<Vec<GenesisIntentGroup>>,
105}
106
107#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen)]
108#[scale_info(skip_type_params(T))]
109pub struct IntentGroup<T: Config> {
111 pub intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
113}
114
115#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen)]
116pub struct IntentInfo {
118 pub payload_location: PayloadLocation,
120 pub settings: IntentSettings,
122}
123
124#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen)]
125pub struct SchemaInfo {
127 pub intent_id: IntentId,
129 pub model_type: ModelType,
131 pub payload_location: PayloadLocation,
133 pub settings: IntentSettings,
135 pub status: SchemaStatus,
137}
138
139#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen)]
140pub struct SchemaName {
146 pub namespace: SchemaProtocolName,
148 pub descriptor: SchemaDescriptor,
150}
151
152pub type FullyQualifiedName = SchemaName;
155
156#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen, Default)]
157pub struct SchemaVersionId {
161 pub ids: BoundedVec<SchemaId, ConstU32<MAX_NUMBER_OF_VERSIONS>>,
164}
165
166impl SchemaName {
167 pub fn try_parse<T: Config>(
181 payload: SchemaNamePayload,
182 require_descriptor: bool,
183 ) -> Result<SchemaName, DispatchError> {
184 let mut str = String::from_utf8(payload.into_inner())
186 .map_err(|_| Error::<T>::InvalidSchemaNameEncoding)?;
187 ensure!(str.is_ascii(), Error::<T>::InvalidSchemaNameEncoding);
188
189 str = String::from(str.to_lowercase().trim());
191
192 ensure!(
197 str.chars()
198 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == SEPARATOR_CHAR),
199 Error::<T>::InvalidSchemaNameCharacters
200 );
201
202 let chunks: Vec<_> = str.split(SEPARATOR_CHAR).collect();
204 ensure!(
205 chunks.len() == 2 || (chunks.len() == 1 && !require_descriptor),
206 Error::<T>::InvalidSchemaNameStructure
207 );
208
209 let namespace = BoundedVec::try_from(chunks[0].as_bytes().to_vec())
211 .map_err(|_| Error::<T>::InvalidSchemaNamespaceLength)?;
212 ensure!(
213 PROTOCOL_NAME_MIN <= namespace.len() as u32,
214 Error::<T>::InvalidSchemaNamespaceLength
215 );
216
217 ensure!(namespace[0].is_ascii_alphabetic(), Error::<T>::InvalidSchemaNameStructure);
225 ensure!(!namespace.ends_with(b"-"), Error::<T>::InvalidSchemaNameStructure);
227
228 let descriptor = match chunks.len() == 2 {
230 true => {
231 let descriptor = BoundedVec::try_from(chunks[1].as_bytes().to_vec())
232 .map_err(|_| Error::<T>::InvalidSchemaDescriptorLength)?;
233 ensure!(
234 DESCRIPTOR_MIN <= descriptor.len() as u32,
235 Error::<T>::InvalidSchemaDescriptorLength
236 );
237 ensure!(
245 descriptor[0].is_ascii_alphabetic(),
246 Error::<T>::InvalidSchemaNameStructure
247 );
248 ensure!(!descriptor.ends_with(b"-"), Error::<T>::InvalidSchemaNameStructure);
250 descriptor
251 },
252 false => BoundedVec::default(),
253 };
254
255 Ok(SchemaName { namespace, descriptor })
256 }
257
258 pub fn get_combined_name(&self) -> Vec<u8> {
260 [
261 self.namespace.clone().into_inner(),
262 vec![SEPARATOR_CHAR as u8],
263 self.descriptor.clone().into_inner(),
264 ]
265 .concat()
266 }
267
268 pub fn new_with_descriptor(&self, descriptor: SchemaDescriptor) -> Self {
270 Self { namespace: self.namespace.clone(), descriptor }
271 }
272
273 pub fn descriptor_exists(&self) -> bool {
275 self.descriptor.len() > 0
276 }
277}
278
279impl SchemaVersionId {
280 pub fn add<T: Config>(&mut self, schema_id: SchemaId) -> Result<SchemaVersion, DispatchError> {
282 let is_new = !self.ids.iter().any(|id| id == &schema_id);
283 ensure!(is_new, Error::<T>::SchemaIdAlreadyExists);
284 self.ids
285 .try_push(schema_id)
286 .map_err(|_| Error::<T>::ExceedsMaxNumberOfVersions)?;
287 let version = self.ids.len() as SchemaVersion;
288 Ok(version)
289 }
290
291 pub fn convert_to_response(&self, schema_name: &SchemaName) -> Vec<SchemaVersionResponse> {
293 self.ids
294 .iter()
295 .enumerate()
296 .map(|(index, schema_id)| SchemaVersionResponse {
297 schema_name: schema_name.get_combined_name(),
298 schema_id: *schema_id,
299 schema_version: (index + 1) as SchemaVersion,
300 })
301 .collect()
302 }
303}
304
305pub trait ConvertToResponse<I, R> {
307 fn convert_to_response(&self, name: &I) -> R;
309}
310
311impl ConvertToResponse<Vec<u8>, Vec<SchemaVersionResponse>> for Vec<SchemaId> {
312 fn convert_to_response(&self, schema_name: &Vec<u8>) -> Vec<SchemaVersionResponse> {
313 self.iter()
314 .enumerate()
315 .map(|(index, id)| SchemaVersionResponse {
316 schema_name: schema_name.clone(),
317 schema_id: *id,
318 schema_version: (index + 1) as SchemaVersion,
319 })
320 .collect()
321 }
322}
323
324impl ConvertToResponse<SchemaName, NameLookupResponse> for MappedEntityIdentifier {
325 fn convert_to_response(&self, name: &SchemaName) -> NameLookupResponse {
326 NameLookupResponse { name: name.get_combined_name(), entity_id: *self }
327 }
328}