Skip to content

Commit

Permalink
H-2976: Improve validation for ontology types (#5827)
Browse files Browse the repository at this point in the history
  • Loading branch information
TimDiekmann authored Dec 9, 2024
1 parent a7f3936 commit 43abc52
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 77 deletions.
2 changes: 2 additions & 0 deletions libs/@blockprotocol/type-system/rust/src/schema/array/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod raw;
mod validation;

use serde::{Deserialize, Serialize, Serializer};

pub use self::validation::{ArraySchemaValidationError, ArraySchemaValidator};
use crate::schema::{OneOfSchema, PropertyType, PropertyValues};

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use thiserror::Error;

use crate::{Valid, Validator, schema::PropertyValueArray};

#[derive(Debug, Error)]
pub enum ArraySchemaValidationError {
#[error(
"Unsatisfiable item number constraints, expected minimum amount of items ({min}) to be \
less than or equal to the maximum amount of items ({max})"
)]
IncompatibleItemNumberConstraints { min: usize, max: usize },
}

pub struct ArraySchemaValidator;

impl<T: Sync> Validator<PropertyValueArray<T>> for ArraySchemaValidator {
type Error = ArraySchemaValidationError;

fn validate_ref<'v>(
&self,
value: &'v PropertyValueArray<T>,
) -> Result<&'v Valid<PropertyValueArray<T>>, Self::Error> {
if let Some((min, max)) = value.min_items.zip(value.max_items) {
if min > max {
return Err(
ArraySchemaValidationError::IncompatibleItemNumberConstraints { min, max },
);
}
}

Ok(Valid::new_ref_unchecked(value))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,13 @@ use serde_json::{Value as JsonValue, json};
use thiserror::Error;

use crate::{
Valid,
schema::{
DataType, DataTypeUuid, InheritanceDepth, ValueLabel,
data_type::{DataTypeEdge, constraint::ValueConstraints},
},
url::VersionedUrl,
};

#[derive(Debug, Clone, Serialize, Deserialize)]
// #[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))]
pub struct ResolvedDataType {
#[serde(flatten)]
pub schema: Arc<DataType>,
#[serde(default, skip_serializing_if = "HashMap::is_empty", rename = "$defs")]
pub definitions: HashMap<VersionedUrl, Arc<DataType>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand Down Expand Up @@ -114,14 +104,6 @@ impl ClosedDataType {
}
}

impl ResolvedDataType {
#[must_use]
pub fn data_type(&self) -> &Valid<DataType> {
// Valid closed schemas imply that the schema is valid
Valid::new_ref_unchecked(&self.schema)
}
}

#[derive(Debug, Default, Clone)]
pub struct DataTypeResolveData {
inheritance_depths: HashMap<DataTypeUuid, (InheritanceDepth, Arc<DataType>)>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod constraint;
mod conversion;

pub use self::{
closed::{ClosedDataType, DataTypeResolveData, ResolvedDataType},
closed::{ClosedDataType, DataTypeResolveData},
constraint::{
AnyOfConstraints, ArrayConstraints, ArraySchema, ArrayTypeTag, ArrayValidationError,
BooleanSchema, BooleanTypeTag, ConstraintError, ConstraintValidator, NullSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use thiserror::Error;

use crate::{
Valid, Validator,
schema::{ClosedDataType, DataType, DataTypeReference, ResolvedDataType},
schema::{ClosedDataType, DataType, DataTypeReference},
url::VersionedUrl,
};

Expand Down Expand Up @@ -72,44 +72,8 @@ impl Validator<DataType> for DataTypeValidator {
return Err(ValidateDataTypeError::NonPrimitiveValueInheritance);
}

// TODO: Implement validation for data types
// see https://linear.app/hash/issue/H-2976/validate-ontology-types-on-creation
Ok(Valid::new_ref_unchecked(value))
}
}

impl Validator<ResolvedDataType> for DataTypeValidator {
type Error = ValidateDataTypeError;

fn validate_ref<'v>(
&self,
value: &'v ResolvedDataType,
) -> Result<&'v Valid<ResolvedDataType>, Self::Error> {
let mut checked_types = HashSet::new();
let mut types_to_check = value
.schema
.data_type_references()
.map(|(reference, _)| reference)
.collect::<Vec<_>>();
while let Some(reference) = types_to_check.pop() {
if !checked_types.insert(reference) || reference.url == value.schema.id {
continue;
}

let data_type = value.definitions.get(&reference.url).ok_or_else(|| {
ValidateDataTypeError::MissingDataType {
data_type_id: reference.url.clone(),
}
})?;
types_to_check.extend(
data_type
.data_type_references()
.map(|(reference, _)| reference),
);
}

// TODO: Implement validation for data types
// see https://linear.app/hash/issue/H-2976/validate-ontology-types-on-creation
// Unsatisfiable constraints will automatically be checked when attempting to close the
// schema so it's not needed to check constraints here.
Ok(Valid::new_ref_unchecked(value))
}
}
Expand All @@ -121,8 +85,7 @@ impl Validator<ClosedDataType> for DataTypeValidator {
&self,
value: &'v ClosedDataType,
) -> Result<&'v Valid<ClosedDataType>, Self::Error> {
// TODO: Validate ontology types on creation
// see https://linear.app/hash/issue/H-2976/validate-ontology-types-on-creation
// Closed data types are validated on creation
Ok(Valid::new_ref_unchecked(value))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ pub enum EntityTypeValidationError {
#[display("Property object validation failed: {_0}")]
#[from]
ObjectValidationFailed(ObjectSchemaValidationError),
#[display(
"Unsatisfiable link number constraints, expected minimum amount of items ({min}) to be \
less than or equal to the maximum amount of items ({max})"
)]
IncompatibleLinkNumberConstraints { min: usize, max: usize },
}

#[derive(Debug)]
Expand All @@ -29,8 +34,6 @@ impl Validator<EntityType> for EntityTypeValidator {
&self,
value: &'v EntityType,
) -> Result<&'v Valid<EntityType>, Self::Error> {
ObjectSchemaValidator.validate_ref(value)?;

for (property, value) in &value.constraints.properties {
let reference = match value {
ValueOrArray::Value(value) => value,
Expand All @@ -42,8 +45,16 @@ impl Validator<EntityType> for EntityTypeValidator {
reference: reference.clone(),
});
}
// TODO: Validate reference
// see https://linear.app/hash/issue/H-3046
}

for links in value.constraints.links.values() {
if let Some((min, max)) = links.min_items.zip(links.max_items) {
if min > max {
return Err(
EntityTypeValidationError::IncompatibleLinkNumberConstraints { min, max },
);
}
}
}

Ok(Valid::new_ref_unchecked(value))
Expand All @@ -70,8 +81,16 @@ impl Validator<ClosedEntityType> for EntityTypeValidator {
reference: reference.clone(),
});
}
// TODO: Validate reference
// see https://linear.app/hash/issue/H-3046
}

for links in value.constraints.links.values() {
if let Some((min, max)) = links.min_items.zip(links.max_items) {
if min > max {
return Err(
EntityTypeValidationError::IncompatibleLinkNumberConstraints { min, max },
);
}
}
}

Ok(Valid::new_ref_unchecked(value))
Expand Down
11 changes: 7 additions & 4 deletions libs/@blockprotocol/type-system/rust/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ mod one_of;
mod identifier;

pub use self::{
array::{PropertyArraySchema, PropertyValueArray, ValueOrArray},
array::{
ArraySchemaValidationError, ArraySchemaValidator, PropertyArraySchema, PropertyValueArray,
ValueOrArray,
},
closed_resolver::{InheritanceDepth, OntologyTypeResolver},
data_type::{
AnyOfConstraints, ArrayConstraints, ArraySchema, ArrayTypeTag, ArrayValidationError,
Expand All @@ -27,9 +30,9 @@ pub use self::{
DataTypeEdge, DataTypeReference, DataTypeResolveData, DataTypeValidator,
JsonSchemaValueType, NullSchema, NullTypeTag, NumberConstraints, NumberSchema,
NumberTypeTag, NumberValidationError, ObjectConstraints, ObjectSchema, ObjectTypeTag,
ObjectValidationError, Operator, ResolvedDataType, SingleValueConstraints,
SingleValueSchema, StringConstraints, StringFormat, StringFormatError, StringSchema,
StringTypeTag, StringValidationError, TupleConstraints, ValidateDataTypeError, ValueLabel,
ObjectValidationError, Operator, SingleValueConstraints, SingleValueSchema,
StringConstraints, StringFormat, StringFormatError, StringSchema, StringTypeTag,
StringValidationError, TupleConstraints, ValidateDataTypeError, ValueLabel,
ValueSchemaMetadata, Variable,
},
domain_validator::{DomainValidationError, DomainValidator, ValidateOntologyType},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
schema::{
ObjectSchemaValidationError, ObjectSchemaValidator, OneOfSchemaValidationError,
OneOfSchemaValidator, PropertyType, PropertyTypeReference, PropertyValues, ValueOrArray,
array::{ArraySchemaValidationError, ArraySchemaValidator},
},
url::BaseUrl,
};
Expand All @@ -17,6 +18,9 @@ pub enum PropertyTypeValidationError {
#[display("Property object validation failed: {_0}")]
#[from]
ObjectValidationFailed(ObjectSchemaValidationError),
#[display("Property array validation failed: {_0}")]
#[from]
ArrayValidationFailed(ArraySchemaValidationError),
#[display("OneOf validation failed: {_0}")]
#[from]
OneOfValidationFailed(OneOfSchemaValidationError),
Expand Down Expand Up @@ -48,25 +52,23 @@ impl Validator<PropertyValues> for PropertyTypeValidator {
value: &'v PropertyValues,
) -> Result<&'v Valid<PropertyValues>, Self::Error> {
match value {
PropertyValues::DataTypeReference(_) => {
// TODO: Validate reference
// see https://linear.app/hash/issue/H-3046
}
PropertyValues::DataTypeReference(_) => {}
PropertyValues::PropertyTypeObject(object) => {
ObjectSchemaValidator.validate_ref(object)?;
for (property, value) in &object.properties {
let reference = match value {
ValueOrArray::Value(value) => value,
ValueOrArray::Array(array) => &array.items,
ValueOrArray::Array(array) => {
ArraySchemaValidator.validate_ref(array)?;
&array.items
}
};
if *property != reference.url.base_url {
return Err(PropertyTypeValidationError::InvalidPropertyReference {
base_url: property.clone(),
reference: reference.clone(),
});
}
// TODO: Validate reference
// see https://linear.app/hash/issue/H-3046
}
}
PropertyValues::ArrayOfPropertyValues(array) => {
Expand Down

0 comments on commit 43abc52

Please sign in to comment.