|
@@ -1,809 +0,0 @@
|
|
|
-// Copyright 2019 Jsgenesis.
|
|
|
-//
|
|
|
-// This is distributed in the hope that it will be useful,
|
|
|
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
-// GNU General Public License for more details.
|
|
|
-//
|
|
|
-// You should have received a copy of the GNU General Public License
|
|
|
-// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
|
|
-
|
|
|
-// Ensure we're `no_std` when compiling for Wasm.
|
|
|
-#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
-
|
|
|
-#[cfg(feature = "std")]
|
|
|
-use serde_derive::{Deserialize, Serialize};
|
|
|
-
|
|
|
-use codec::{Decode, Encode};
|
|
|
-use rstd::collections::btree_set::BTreeSet;
|
|
|
-use rstd::prelude::*;
|
|
|
-use srml_support::{decl_event, decl_module, decl_storage, dispatch, ensure};
|
|
|
-use system;
|
|
|
-
|
|
|
-mod example;
|
|
|
-mod mock;
|
|
|
-mod tests;
|
|
|
-
|
|
|
-// Validation errors
|
|
|
-// --------------------------------------
|
|
|
-
|
|
|
-const ERROR_PROPERTY_NAME_TOO_SHORT: &str = "Property name is too short";
|
|
|
-const ERROR_PROPERTY_NAME_TOO_LONG: &str = "Property name is too long";
|
|
|
-const ERROR_PROPERTY_DESCRIPTION_TOO_SHORT: &str = "Property description is too long";
|
|
|
-const ERROR_PROPERTY_DESCRIPTION_TOO_LONG: &str = "Property description is too long";
|
|
|
-
|
|
|
-const ERROR_CLASS_NAME_TOO_SHORT: &str = "Class name is too short";
|
|
|
-const ERROR_CLASS_NAME_TOO_LONG: &str = "Class name is too long";
|
|
|
-const ERROR_CLASS_DESCRIPTION_TOO_SHORT: &str = "Class description is too long";
|
|
|
-const ERROR_CLASS_DESCRIPTION_TOO_LONG: &str = "Class description is too long";
|
|
|
-
|
|
|
-// Main logic errors
|
|
|
-// --------------------------------------
|
|
|
-
|
|
|
-const ERROR_CLASS_NOT_FOUND: &str = "Class was not found by id";
|
|
|
-const ERROR_UNKNOWN_CLASS_SCHEMA_ID: &str = "Unknown class schema id";
|
|
|
-const ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_PROP_INDEX: &str =
|
|
|
- "New class schema refers to an unknown property index";
|
|
|
-const ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_INTERNAL_ID: &str =
|
|
|
- "New class schema refers to an unknown internal class id";
|
|
|
-const ERROR_NO_PROPS_IN_CLASS_SCHEMA: &str =
|
|
|
- "Cannot add a class schema with an empty list of properties";
|
|
|
-const ERROR_ENTITY_NOT_FOUND: &str = "Entity was not found by id";
|
|
|
-// const ERROR_ENTITY_ALREADY_DELETED: &str = "Entity is already deleted";
|
|
|
-const ERROR_SCHEMA_ALREADY_ADDED_TO_ENTITY: &str =
|
|
|
- "Cannot add a schema that is already added to this entity";
|
|
|
-const ERROR_PROP_VALUE_DONT_MATCH_TYPE: &str =
|
|
|
- "Some of the provided property values don't match the expected property type";
|
|
|
-const ERROR_PROP_NAME_NOT_UNIQUE_IN_CLASS: &str = "Property name is not unique within its class";
|
|
|
-const ERROR_MISSING_REQUIRED_PROP: &str =
|
|
|
- "Some required property was not found when adding schema support to entity";
|
|
|
-const ERROR_UNKNOWN_ENTITY_PROP_ID: &str = "Some of the provided property ids cannot be found on the current list of propery values of this entity";
|
|
|
-const ERROR_TEXT_PROP_IS_TOO_LONG: &str = "Text propery is too long";
|
|
|
-const ERROR_VEC_PROP_IS_TOO_LONG: &str = "Vector propery is too long";
|
|
|
-const ERROR_INTERNAL_RPOP_DOES_NOT_MATCH_ITS_CLASS: &str =
|
|
|
- "Internal property does not match its class";
|
|
|
-
|
|
|
-/// Length constraint for input validation
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
|
|
|
-pub struct InputValidationLengthConstraint {
|
|
|
- /// Minimum length
|
|
|
- pub min: u16,
|
|
|
-
|
|
|
- /// Difference between minimum length and max length.
|
|
|
- /// While having max would have been more direct, this
|
|
|
- /// way makes max < min unrepresentable semantically,
|
|
|
- /// which is safer.
|
|
|
- pub max_min_diff: u16,
|
|
|
-}
|
|
|
-
|
|
|
-impl InputValidationLengthConstraint {
|
|
|
- /// Helper for computing max
|
|
|
- pub fn max(&self) -> u16 {
|
|
|
- self.min + self.max_min_diff
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_valid(
|
|
|
- &self,
|
|
|
- len: usize,
|
|
|
- too_short_msg: &'static str,
|
|
|
- too_long_msg: &'static str,
|
|
|
- ) -> Result<(), &'static str> {
|
|
|
- let length = len as u16;
|
|
|
- if length < self.min {
|
|
|
- Err(too_short_msg)
|
|
|
- } else if length > self.max() {
|
|
|
- Err(too_long_msg)
|
|
|
- } else {
|
|
|
- Ok(())
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub type ClassId = u64;
|
|
|
-pub type EntityId = u64;
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
|
|
|
-pub struct Class {
|
|
|
- pub id: ClassId,
|
|
|
-
|
|
|
- /// All properties that have been used on this class across different class schemas.
|
|
|
- /// Unlikely to be more than roughly 20 properties per class, often less.
|
|
|
- /// For Person, think "height", "weight", etc.
|
|
|
- pub properties: Vec<Property>,
|
|
|
-
|
|
|
- /// All scehmas that are available for this class, think v0.0 Person, v.1.0 Person, etc.
|
|
|
- pub schemas: Vec<ClassSchema>,
|
|
|
-
|
|
|
- pub name: Vec<u8>,
|
|
|
- pub description: Vec<u8>,
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
|
|
|
-pub struct Entity {
|
|
|
- pub id: EntityId,
|
|
|
-
|
|
|
- /// The class id of this entity.
|
|
|
- pub class_id: ClassId,
|
|
|
-
|
|
|
- /// What schemas under which this entity of a class is available, think
|
|
|
- /// v.2.0 Person schema for John, v3.0 Person schema for John
|
|
|
- /// Unlikely to be more than roughly 20ish, assuming schemas for a given class eventually stableize, or that very old schema are eventually removed.
|
|
|
- pub in_class_schema_indexes: Vec<u16>, // indices of schema in corresponding class
|
|
|
-
|
|
|
- /// Values for properties on class that are used by some schema used by this entity!
|
|
|
- /// Length is no more than Class.properties.
|
|
|
- pub values: Vec<ClassPropertyValue>,
|
|
|
- // pub deleted: bool,
|
|
|
-}
|
|
|
-
|
|
|
-/// A schema defines what properties describe an entity
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
|
|
|
-pub struct ClassSchema {
|
|
|
- /// Indices into properties vector for the corresponding class.
|
|
|
- pub properties: Vec<u16>,
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
|
|
|
-pub struct Property {
|
|
|
- pub prop_type: PropertyType,
|
|
|
- pub required: bool,
|
|
|
- pub name: Vec<u8>,
|
|
|
- pub description: Vec<u8>,
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
|
|
|
-pub enum PropertyType {
|
|
|
- None,
|
|
|
-
|
|
|
- // Single value:
|
|
|
- Bool,
|
|
|
- Uint16,
|
|
|
- Uint32,
|
|
|
- Uint64,
|
|
|
- Int16,
|
|
|
- Int32,
|
|
|
- Int64,
|
|
|
- Text(u16),
|
|
|
- Internal(ClassId),
|
|
|
-
|
|
|
- // Vector of values.
|
|
|
- // The first u16 value is the max length of this vector.
|
|
|
- BoolVec(u16),
|
|
|
- Uint16Vec(u16),
|
|
|
- Uint32Vec(u16),
|
|
|
- Uint64Vec(u16),
|
|
|
- Int16Vec(u16),
|
|
|
- Int32Vec(u16),
|
|
|
- Int64Vec(u16),
|
|
|
-
|
|
|
- /// The first u16 value is the max length of this vector.
|
|
|
- /// The second u16 value is the max length of every text item in this vector.
|
|
|
- TextVec(u16, u16),
|
|
|
-
|
|
|
- /// The first u16 value is the max length of this vector.
|
|
|
- /// The second ClassId value tells that an every element of this vector
|
|
|
- /// should be of a specific ClassId.
|
|
|
- InternalVec(u16, ClassId),
|
|
|
- // External(ExternalProperty),
|
|
|
- // ExternalVec(u16, ExternalProperty),
|
|
|
-}
|
|
|
-
|
|
|
-impl Default for PropertyType {
|
|
|
- fn default() -> Self {
|
|
|
- PropertyType::None
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
|
|
|
-pub enum PropertyValue {
|
|
|
- None,
|
|
|
-
|
|
|
- // Single value:
|
|
|
- Bool(bool),
|
|
|
- Uint16(u16),
|
|
|
- Uint32(u32),
|
|
|
- Uint64(u64),
|
|
|
- Int16(i16),
|
|
|
- Int32(i32),
|
|
|
- Int64(i64),
|
|
|
- Text(Vec<u8>),
|
|
|
- Internal(EntityId),
|
|
|
-
|
|
|
- // Vector of values:
|
|
|
- BoolVec(Vec<bool>),
|
|
|
- Uint16Vec(Vec<u16>),
|
|
|
- Uint32Vec(Vec<u32>),
|
|
|
- Uint64Vec(Vec<u64>),
|
|
|
- Int16Vec(Vec<i16>),
|
|
|
- Int32Vec(Vec<i32>),
|
|
|
- Int64Vec(Vec<i64>),
|
|
|
- TextVec(Vec<Vec<u8>>),
|
|
|
- InternalVec(Vec<EntityId>),
|
|
|
- // External(ExternalPropertyType),
|
|
|
- // ExternalVec(Vec<ExternalPropertyType>),
|
|
|
-}
|
|
|
-
|
|
|
-impl Default for PropertyValue {
|
|
|
- fn default() -> Self {
|
|
|
- PropertyValue::None
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
|
|
|
-pub struct ClassPropertyValue {
|
|
|
- /// Index is into properties vector of class.
|
|
|
- pub in_class_index: u16,
|
|
|
-
|
|
|
- /// Value of property with index `in_class_index` in a given class.
|
|
|
- pub value: PropertyValue,
|
|
|
-}
|
|
|
-
|
|
|
-pub trait Trait: system::Trait + Sized {
|
|
|
- type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
|
|
-}
|
|
|
-
|
|
|
-decl_storage! {
|
|
|
-
|
|
|
- trait Store for Module<T: Trait> as VersionedStore {
|
|
|
-
|
|
|
- pub ClassById get(class_by_id) config(): map ClassId => Class;
|
|
|
-
|
|
|
- pub EntityById get(entity_by_id) config(): map EntityId => Entity;
|
|
|
-
|
|
|
- pub NextClassId get(next_class_id) config(): ClassId;
|
|
|
-
|
|
|
- pub NextEntityId get(next_entity_id) config(): EntityId;
|
|
|
-
|
|
|
- pub PropertyNameConstraint get(property_name_constraint)
|
|
|
- config(): InputValidationLengthConstraint;
|
|
|
-
|
|
|
- pub PropertyDescriptionConstraint get(property_description_constraint)
|
|
|
- config(): InputValidationLengthConstraint;
|
|
|
-
|
|
|
- pub ClassNameConstraint get(class_name_constraint)
|
|
|
- config(): InputValidationLengthConstraint;
|
|
|
-
|
|
|
- pub ClassDescriptionConstraint get(class_description_constraint)
|
|
|
- config(): InputValidationLengthConstraint;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-decl_event!(
|
|
|
- pub enum Event<T>
|
|
|
- where
|
|
|
- <T as system::Trait>::AccountId,
|
|
|
- {
|
|
|
- ClassCreated(ClassId),
|
|
|
- ClassSchemaAdded(ClassId, u16),
|
|
|
-
|
|
|
- EntityCreated(EntityId),
|
|
|
- // EntityDeleted(EntityId),
|
|
|
- EntityPropertiesUpdated(EntityId),
|
|
|
- EntitySchemaAdded(EntityId, u16),
|
|
|
-
|
|
|
- /// This is a fake event that uses AccountId type just to make Rust compiler happy to compile this module.
|
|
|
- FixCompilation(AccountId),
|
|
|
- }
|
|
|
-);
|
|
|
-
|
|
|
-decl_module! {
|
|
|
- pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
|
|
- fn deposit_event() = default;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// Shortcuts for faster readability of match expression:
|
|
|
-use PropertyType as PT;
|
|
|
-use PropertyValue as PV;
|
|
|
-
|
|
|
-impl<T: Trait> Module<T> {
|
|
|
- /// Returns an id of a newly added class.
|
|
|
- pub fn create_class(name: Vec<u8>, description: Vec<u8>) -> Result<ClassId, &'static str> {
|
|
|
- Self::ensure_class_name_is_valid(&name)?;
|
|
|
-
|
|
|
- Self::ensure_class_description_is_valid(&description)?;
|
|
|
-
|
|
|
- let class_id = NextClassId::get();
|
|
|
-
|
|
|
- let new_class = Class {
|
|
|
- id: class_id,
|
|
|
- properties: vec![],
|
|
|
- schemas: vec![],
|
|
|
- name,
|
|
|
- description,
|
|
|
- };
|
|
|
-
|
|
|
- // Save newly created class:
|
|
|
- ClassById::insert(class_id, new_class);
|
|
|
-
|
|
|
- // Increment the next class id:
|
|
|
- NextClassId::mutate(|n| *n += 1);
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::ClassCreated(class_id));
|
|
|
- Ok(class_id)
|
|
|
- }
|
|
|
-
|
|
|
- /// Returns an index of a newly added class schema on success.
|
|
|
- pub fn add_class_schema(
|
|
|
- class_id: ClassId,
|
|
|
- existing_properties: Vec<u16>,
|
|
|
- new_properties: Vec<Property>,
|
|
|
- ) -> Result<u16, &'static str> {
|
|
|
- Self::ensure_known_class_id(class_id)?;
|
|
|
-
|
|
|
- let non_empty_schema = !existing_properties.is_empty() || !new_properties.is_empty();
|
|
|
-
|
|
|
- ensure!(non_empty_schema, ERROR_NO_PROPS_IN_CLASS_SCHEMA);
|
|
|
-
|
|
|
- let class = ClassById::get(class_id);
|
|
|
-
|
|
|
- // TODO Use BTreeSet for prop unique names when switched to Substrate 2.
|
|
|
- // There is no support for BTreeSet in Substrate 1 runtime.
|
|
|
- // use rstd::collections::btree_set::BTreeSet;
|
|
|
- let mut unique_prop_names = BTreeSet::new();
|
|
|
- for prop in class.properties.iter() {
|
|
|
- unique_prop_names.insert(prop.name.clone());
|
|
|
- }
|
|
|
-
|
|
|
- for prop in new_properties.iter() {
|
|
|
- Self::ensure_property_name_is_valid(&prop.name)?;
|
|
|
- Self::ensure_property_description_is_valid(&prop.description)?;
|
|
|
-
|
|
|
- // Check that the name of a new property is unique within its class.
|
|
|
- ensure!(
|
|
|
- !unique_prop_names.contains(&prop.name),
|
|
|
- ERROR_PROP_NAME_NOT_UNIQUE_IN_CLASS
|
|
|
- );
|
|
|
- unique_prop_names.insert(prop.name.clone());
|
|
|
- }
|
|
|
-
|
|
|
- // Check that existing props are valid indices of class properties vector:
|
|
|
- let has_unknown_props = existing_properties
|
|
|
- .iter()
|
|
|
- .any(|&prop_id| prop_id >= class.properties.len() as u16);
|
|
|
- ensure!(
|
|
|
- !has_unknown_props,
|
|
|
- ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_PROP_INDEX
|
|
|
- );
|
|
|
-
|
|
|
- // Check validity of Internal(ClassId) for new_properties.
|
|
|
- let has_unknown_internal_id = new_properties.iter().any(|prop| match prop.prop_type {
|
|
|
- PropertyType::Internal(other_class_id) => !ClassById::exists(other_class_id),
|
|
|
- _ => false,
|
|
|
- });
|
|
|
- ensure!(
|
|
|
- !has_unknown_internal_id,
|
|
|
- ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_INTERNAL_ID
|
|
|
- );
|
|
|
-
|
|
|
- // Use the current length of schemas in this class as an index
|
|
|
- // for the next schema that will be sent in a result of this function.
|
|
|
- let schema_idx = class.schemas.len() as u16;
|
|
|
-
|
|
|
- let mut schema = ClassSchema {
|
|
|
- properties: existing_properties,
|
|
|
- };
|
|
|
-
|
|
|
- let mut updated_class_props = class.properties;
|
|
|
- new_properties.into_iter().for_each(|prop| {
|
|
|
- let prop_id = updated_class_props.len() as u16;
|
|
|
- updated_class_props.push(prop);
|
|
|
- schema.properties.push(prop_id);
|
|
|
- });
|
|
|
-
|
|
|
- ClassById::mutate(class_id, |class| {
|
|
|
- class.properties = updated_class_props;
|
|
|
- class.schemas.push(schema);
|
|
|
- });
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::ClassSchemaAdded(class_id, schema_idx));
|
|
|
- Ok(schema_idx)
|
|
|
- }
|
|
|
-
|
|
|
- pub fn create_entity(class_id: ClassId) -> Result<EntityId, &'static str> {
|
|
|
- Self::ensure_known_class_id(class_id)?;
|
|
|
-
|
|
|
- let entity_id = NextEntityId::get();
|
|
|
-
|
|
|
- let new_entity = Entity {
|
|
|
- id: entity_id,
|
|
|
- class_id,
|
|
|
- in_class_schema_indexes: vec![],
|
|
|
- values: vec![],
|
|
|
- // deleted: false,
|
|
|
- };
|
|
|
-
|
|
|
- // Save newly created entity:
|
|
|
- EntityById::insert(entity_id, new_entity);
|
|
|
-
|
|
|
- // Increment the next entity id:
|
|
|
- NextEntityId::mutate(|n| *n += 1);
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::EntityCreated(entity_id));
|
|
|
- Ok(entity_id)
|
|
|
- }
|
|
|
-
|
|
|
- pub fn add_schema_support_to_entity(
|
|
|
- entity_id: EntityId,
|
|
|
- schema_id: u16,
|
|
|
- property_values: Vec<ClassPropertyValue>,
|
|
|
- ) -> dispatch::Result {
|
|
|
- Self::ensure_known_entity_id(entity_id)?;
|
|
|
-
|
|
|
- let (entity, class) = Self::get_entity_and_class(entity_id);
|
|
|
-
|
|
|
- // Check that schema_id is a valid index of class schemas vector:
|
|
|
- let known_schema_id = schema_id < class.schemas.len() as u16;
|
|
|
- ensure!(known_schema_id, ERROR_UNKNOWN_CLASS_SCHEMA_ID);
|
|
|
-
|
|
|
- // Check that schema id is not yet added to this entity:
|
|
|
- let schema_not_added = entity
|
|
|
- .in_class_schema_indexes
|
|
|
- .iter()
|
|
|
- .position(|x| *x == schema_id)
|
|
|
- .is_none();
|
|
|
- ensure!(schema_not_added, ERROR_SCHEMA_ALREADY_ADDED_TO_ENTITY);
|
|
|
-
|
|
|
- let class_schema_opt = class.schemas.get(schema_id as usize);
|
|
|
- let schema_prop_ids = class_schema_opt.unwrap().properties.clone();
|
|
|
-
|
|
|
- let current_entity_values = entity.values.clone();
|
|
|
- let mut appended_entity_values = entity.values;
|
|
|
-
|
|
|
- for &prop_id in schema_prop_ids.iter() {
|
|
|
- let prop_already_added = current_entity_values
|
|
|
- .iter()
|
|
|
- .any(|prop| prop.in_class_index == prop_id);
|
|
|
-
|
|
|
- if prop_already_added {
|
|
|
- // A property is already added to the entity and cannot be updated
|
|
|
- // while adding a schema support to this entity.
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- let class_prop = class.properties.get(prop_id as usize).unwrap();
|
|
|
-
|
|
|
- // If a value was not povided for the property of this schema:
|
|
|
- match property_values
|
|
|
- .iter()
|
|
|
- .find(|prop| prop.in_class_index == prop_id)
|
|
|
- {
|
|
|
- Some(new_prop) => {
|
|
|
- let ClassPropertyValue {
|
|
|
- in_class_index: new_id,
|
|
|
- value: new_value,
|
|
|
- } = new_prop;
|
|
|
-
|
|
|
- Self::ensure_property_value_is_valid(new_value.clone(), class_prop.clone())?;
|
|
|
-
|
|
|
- appended_entity_values.push(ClassPropertyValue {
|
|
|
- in_class_index: *new_id,
|
|
|
- value: new_value.clone(),
|
|
|
- });
|
|
|
- }
|
|
|
- None => {
|
|
|
- // All required prop values should be are provided
|
|
|
- if class_prop.required {
|
|
|
- return Err(ERROR_MISSING_REQUIRED_PROP);
|
|
|
- }
|
|
|
- // Add all missing non required schema prop values as PropertyValue::None
|
|
|
- else {
|
|
|
- appended_entity_values.push(ClassPropertyValue {
|
|
|
- in_class_index: prop_id,
|
|
|
- value: PropertyValue::None,
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- EntityById::mutate(entity_id, |entity| {
|
|
|
- // Add a new schema to the list of schemas supported by this entity.
|
|
|
- entity.in_class_schema_indexes.push(schema_id);
|
|
|
-
|
|
|
- // Update entity values only if new properties have been added.
|
|
|
- if appended_entity_values.len() > entity.values.len() {
|
|
|
- entity.values = appended_entity_values;
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::EntitySchemaAdded(entity_id, schema_id));
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- pub fn update_entity_property_values(
|
|
|
- entity_id: EntityId,
|
|
|
- new_property_values: Vec<ClassPropertyValue>,
|
|
|
- ) -> dispatch::Result {
|
|
|
- Self::ensure_known_entity_id(entity_id)?;
|
|
|
-
|
|
|
- let (entity, class) = Self::get_entity_and_class(entity_id);
|
|
|
-
|
|
|
- // Get current property values of an entity as a mutable vector,
|
|
|
- // so we can update them if new values provided present in new_property_values.
|
|
|
- let mut updated_values = entity.values;
|
|
|
- let mut updates_count = 0;
|
|
|
-
|
|
|
- // Iterate over a vector of new values and update corresponding properties
|
|
|
- // of this entity if new values are valid.
|
|
|
- for new_prop_value in new_property_values.iter() {
|
|
|
- let ClassPropertyValue {
|
|
|
- in_class_index: id,
|
|
|
- value: new_value,
|
|
|
- } = new_prop_value;
|
|
|
-
|
|
|
- // Try to find a current property value in the entity
|
|
|
- // by matching its id to the id of a property with an updated value.
|
|
|
- if let Some(current_prop_value) = updated_values
|
|
|
- .iter_mut()
|
|
|
- .find(|prop| *id == prop.in_class_index)
|
|
|
- {
|
|
|
- let ClassPropertyValue {
|
|
|
- in_class_index: valid_id,
|
|
|
- value: current_value,
|
|
|
- } = current_prop_value;
|
|
|
-
|
|
|
- // Get class-level information about this property
|
|
|
- let class_prop = class.properties.get(*valid_id as usize).unwrap();
|
|
|
-
|
|
|
- // Validate a new property value against the type of this property
|
|
|
- // and check any additional constraints like the length of a vector
|
|
|
- // if it's a vector property or the length of a text if it's a text property.
|
|
|
- Self::ensure_property_value_is_valid(new_value.clone(), class_prop.clone())?;
|
|
|
-
|
|
|
- // Update a current prop value in a mutable vector, if a new value is valid.
|
|
|
- *current_value = new_value.clone();
|
|
|
- updates_count += 1;
|
|
|
- } else {
|
|
|
- // Throw an error if a property was not found on entity
|
|
|
- // by an in-class index of a property update.
|
|
|
- return Err(ERROR_UNKNOWN_ENTITY_PROP_ID);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // If at least one of the entity property values should be update:
|
|
|
- if updates_count > 0 {
|
|
|
- EntityById::mutate(entity_id, |entity| {
|
|
|
- entity.values = updated_values;
|
|
|
- });
|
|
|
- Self::deposit_event(RawEvent::EntityPropertiesUpdated(entity_id));
|
|
|
- }
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- // Commented out for now <- requested by Bedeho.
|
|
|
- // pub fn delete_entity(entity_id: EntityId) -> dispatch::Result {
|
|
|
- // Self::ensure_known_entity_id(entity_id)?;
|
|
|
-
|
|
|
- // let is_deleted = EntityById::get(entity_id).deleted;
|
|
|
- // ensure!(!is_deleted, ERROR_ENTITY_ALREADY_DELETED);
|
|
|
-
|
|
|
- // EntityById::mutate(entity_id, |x| {
|
|
|
- // x.deleted = true;
|
|
|
- // });
|
|
|
-
|
|
|
- // Self::deposit_event(RawEvent::EntityDeleted(entity_id));
|
|
|
- // Ok(())
|
|
|
- // }
|
|
|
-
|
|
|
- // Helper functions:
|
|
|
- // ----------------------------------------------------------------
|
|
|
-
|
|
|
- pub fn ensure_known_class_id(class_id: ClassId) -> dispatch::Result {
|
|
|
- ensure!(ClassById::exists(class_id), ERROR_CLASS_NOT_FOUND);
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_known_entity_id(entity_id: EntityId) -> dispatch::Result {
|
|
|
- ensure!(EntityById::exists(entity_id), ERROR_ENTITY_NOT_FOUND);
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_valid_internal_prop(value: PropertyValue, prop: Property) -> dispatch::Result {
|
|
|
- match (value, prop.prop_type) {
|
|
|
- (PV::Internal(entity_id), PT::Internal(class_id)) => {
|
|
|
- Self::ensure_known_class_id(class_id)?;
|
|
|
- Self::ensure_known_entity_id(entity_id)?;
|
|
|
- let entity = Self::entity_by_id(entity_id);
|
|
|
- ensure!(
|
|
|
- entity.class_id == class_id,
|
|
|
- ERROR_INTERNAL_RPOP_DOES_NOT_MATCH_ITS_CLASS
|
|
|
- );
|
|
|
- Ok(())
|
|
|
- }
|
|
|
- _ => Ok(()),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn is_unknown_internal_entity_id(id: PropertyValue) -> bool {
|
|
|
- if let PropertyValue::Internal(entity_id) = id {
|
|
|
- !EntityById::exists(entity_id)
|
|
|
- } else {
|
|
|
- false
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn get_entity_and_class(entity_id: EntityId) -> (Entity, Class) {
|
|
|
- let entity = EntityById::get(entity_id);
|
|
|
- let class = ClassById::get(entity.class_id);
|
|
|
- (entity, class)
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_property_value_is_valid(
|
|
|
- value: PropertyValue,
|
|
|
- prop: Property,
|
|
|
- ) -> dispatch::Result {
|
|
|
- Self::ensure_prop_value_matches_its_type(value.clone(), prop.clone())?;
|
|
|
- Self::ensure_valid_internal_prop(value.clone(), prop.clone())?;
|
|
|
- Self::validate_max_len_if_text_prop(value.clone(), prop.clone())?;
|
|
|
- Self::validate_max_len_if_vec_prop(value.clone(), prop.clone())?;
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- pub fn validate_max_len_if_text_prop(value: PropertyValue, prop: Property) -> dispatch::Result {
|
|
|
- match (value, prop.prop_type) {
|
|
|
- (PV::Text(text), PT::Text(max_len)) => Self::validate_max_len_of_text(text, max_len),
|
|
|
- _ => Ok(()),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn validate_max_len_of_text(text: Vec<u8>, max_len: u16) -> dispatch::Result {
|
|
|
- if text.len() <= max_len as usize {
|
|
|
- Ok(())
|
|
|
- } else {
|
|
|
- Err(ERROR_TEXT_PROP_IS_TOO_LONG)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[rustfmt::skip]
|
|
|
- pub fn validate_max_len_if_vec_prop(
|
|
|
- value: PropertyValue,
|
|
|
- prop: Property,
|
|
|
- ) -> dispatch::Result {
|
|
|
-
|
|
|
- fn validate_vec_len<T>(vec: Vec<T>, max_len: u16) -> bool {
|
|
|
- vec.len() <= max_len as usize
|
|
|
- }
|
|
|
-
|
|
|
- fn validate_vec_len_ref<T>(vec: &Vec<T>, max_len: u16) -> bool {
|
|
|
- vec.len() <= max_len as usize
|
|
|
- }
|
|
|
-
|
|
|
- let is_valid_len = match (value, prop.prop_type) {
|
|
|
- (PV::BoolVec(vec), PT::BoolVec(max_len)) => validate_vec_len(vec, max_len),
|
|
|
- (PV::Uint16Vec(vec), PT::Uint16Vec(max_len)) => validate_vec_len(vec, max_len),
|
|
|
- (PV::Uint32Vec(vec), PT::Uint32Vec(max_len)) => validate_vec_len(vec, max_len),
|
|
|
- (PV::Uint64Vec(vec), PT::Uint64Vec(max_len)) => validate_vec_len(vec, max_len),
|
|
|
- (PV::Int16Vec(vec), PT::Int16Vec(max_len)) => validate_vec_len(vec, max_len),
|
|
|
- (PV::Int32Vec(vec), PT::Int32Vec(max_len)) => validate_vec_len(vec, max_len),
|
|
|
- (PV::Int64Vec(vec), PT::Int64Vec(max_len)) => validate_vec_len(vec, max_len),
|
|
|
-
|
|
|
- (PV::TextVec(vec), PT::TextVec(vec_max_len, text_max_len)) => {
|
|
|
- if validate_vec_len_ref(&vec, vec_max_len) {
|
|
|
- for text_item in vec.iter() {
|
|
|
- Self::validate_max_len_of_text(text_item.clone(), text_max_len)?;
|
|
|
- }
|
|
|
- true
|
|
|
- } else {
|
|
|
- false
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- (PV::InternalVec(vec), PT::InternalVec(vec_max_len, class_id)) => {
|
|
|
- Self::ensure_known_class_id(class_id)?;
|
|
|
- if validate_vec_len_ref(&vec, vec_max_len) {
|
|
|
- for entity_id in vec.iter() {
|
|
|
- Self::ensure_known_entity_id(entity_id.clone())?;
|
|
|
- let entity = Self::entity_by_id(entity_id);
|
|
|
- ensure!(entity.class_id == class_id, ERROR_INTERNAL_RPOP_DOES_NOT_MATCH_ITS_CLASS);
|
|
|
- }
|
|
|
- true
|
|
|
- } else {
|
|
|
- false
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- _ => true
|
|
|
- };
|
|
|
-
|
|
|
- if is_valid_len {
|
|
|
- Ok(())
|
|
|
- } else {
|
|
|
- Err(ERROR_VEC_PROP_IS_TOO_LONG)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_prop_value_matches_its_type(
|
|
|
- value: PropertyValue,
|
|
|
- prop: Property,
|
|
|
- ) -> dispatch::Result {
|
|
|
- if Self::does_prop_value_match_type(value, prop) {
|
|
|
- Ok(())
|
|
|
- } else {
|
|
|
- Err(ERROR_PROP_VALUE_DONT_MATCH_TYPE)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[rustfmt::skip]
|
|
|
- pub fn does_prop_value_match_type(
|
|
|
- value: PropertyValue,
|
|
|
- prop: Property,
|
|
|
- ) -> bool {
|
|
|
-
|
|
|
- // A non required property can be updated to None:
|
|
|
- if !prop.required && value == PV::None {
|
|
|
- return true
|
|
|
- }
|
|
|
-
|
|
|
- match (value, prop.prop_type) {
|
|
|
- (PV::None, PT::None) |
|
|
|
-
|
|
|
- // Single values
|
|
|
- (PV::Bool(_), PT::Bool) |
|
|
|
- (PV::Uint16(_), PT::Uint16) |
|
|
|
- (PV::Uint32(_), PT::Uint32) |
|
|
|
- (PV::Uint64(_), PT::Uint64) |
|
|
|
- (PV::Int16(_), PT::Int16) |
|
|
|
- (PV::Int32(_), PT::Int32) |
|
|
|
- (PV::Int64(_), PT::Int64) |
|
|
|
- (PV::Text(_), PT::Text(_)) |
|
|
|
- (PV::Internal(_), PT::Internal(_)) |
|
|
|
-
|
|
|
- // Vectors:
|
|
|
- (PV::BoolVec(_), PT::BoolVec(_)) |
|
|
|
- (PV::Uint16Vec(_), PT::Uint16Vec(_)) |
|
|
|
- (PV::Uint32Vec(_), PT::Uint32Vec(_)) |
|
|
|
- (PV::Uint64Vec(_), PT::Uint64Vec(_)) |
|
|
|
- (PV::Int16Vec(_), PT::Int16Vec(_)) |
|
|
|
- (PV::Int32Vec(_), PT::Int32Vec(_)) |
|
|
|
- (PV::Int64Vec(_), PT::Int64Vec(_)) |
|
|
|
- (PV::TextVec(_), PT::TextVec(_, _)) |
|
|
|
- (PV::InternalVec(_), PT::InternalVec(_, _)) => true,
|
|
|
-
|
|
|
- // (PV::External(_), PT::External(_)) => true,
|
|
|
- // (PV::ExternalVec(_), PT::ExternalVec(_, _)) => true,
|
|
|
- _ => false,
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_property_name_is_valid(text: &Vec<u8>) -> dispatch::Result {
|
|
|
- PropertyNameConstraint::get().ensure_valid(
|
|
|
- text.len(),
|
|
|
- ERROR_PROPERTY_NAME_TOO_SHORT,
|
|
|
- ERROR_PROPERTY_NAME_TOO_LONG,
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_property_description_is_valid(text: &Vec<u8>) -> dispatch::Result {
|
|
|
- PropertyDescriptionConstraint::get().ensure_valid(
|
|
|
- text.len(),
|
|
|
- ERROR_PROPERTY_DESCRIPTION_TOO_SHORT,
|
|
|
- ERROR_PROPERTY_DESCRIPTION_TOO_LONG,
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_class_name_is_valid(text: &Vec<u8>) -> dispatch::Result {
|
|
|
- ClassNameConstraint::get().ensure_valid(
|
|
|
- text.len(),
|
|
|
- ERROR_CLASS_NAME_TOO_SHORT,
|
|
|
- ERROR_CLASS_NAME_TOO_LONG,
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- pub fn ensure_class_description_is_valid(text: &Vec<u8>) -> dispatch::Result {
|
|
|
- ClassDescriptionConstraint::get().ensure_valid(
|
|
|
- text.len(),
|
|
|
- ERROR_CLASS_DESCRIPTION_TOO_SHORT,
|
|
|
- ERROR_CLASS_DESCRIPTION_TOO_LONG,
|
|
|
- )
|
|
|
- }
|
|
|
-}
|