Skip to content

Commit

Permalink
H-3356: Add abstract flag for data types (#5224)
Browse files Browse the repository at this point in the history
  • Loading branch information
TimDiekmann authored Sep 25, 2024
1 parent 187efb5 commit 0133856
Show file tree
Hide file tree
Showing 20 changed files with 109 additions and 45 deletions.
6 changes: 6 additions & 0 deletions apps/hash-graph/libs/graph/src/store/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ where
C: AsClient,
A: AuthorizationApi,
{
type Value = Arc<DataTypeWithMetadata>;

#[expect(refining_impl_trait)]
async fn provide_type(
&self,
Expand Down Expand Up @@ -345,6 +347,8 @@ where
C: AsClient,
A: AuthorizationApi,
{
type Value = Arc<PropertyType>;

#[expect(refining_impl_trait)]
async fn provide_type(
&self,
Expand Down Expand Up @@ -464,6 +468,8 @@ where
C: AsClient,
A: AuthorizationApi,
{
type Value = Arc<ClosedEntityType>;

#[expect(refining_impl_trait)]
async fn provide_type(
&self,
Expand Down
2 changes: 2 additions & 0 deletions apps/hash-graph/libs/store/src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ mod tests {

#[expect(refining_impl_trait)]
impl OntologyTypeProvider<DataTypeWithMetadata> for TestDataTypeProvider {
type Value = DataTypeWithMetadata;

async fn provide_type(&self, _: &VersionedUrl) -> Result<DataTypeWithMetadata, Report<!>> {
unimplemented!()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ mod raw {
)]
#[serde(default, skip_serializing_if = "Vec::is_empty")]
all_of: Vec<DataTypeReference>,

#[serde(default)]
r#abstract: bool,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -194,6 +197,7 @@ mod raw {
title: common.title,
title_plural: common.title_plural,
all_of: common.all_of,
r#abstract: common.r#abstract,
constraints,
}
}
Expand All @@ -214,6 +218,7 @@ pub struct DataType {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub all_of: Vec<DataTypeReference>,

pub r#abstract: bool,
#[serde(flatten)]
pub constraints: ValueConstraints,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ pub enum TraversalError {
actual: VersionedUrl,
expected: VersionedUrl,
},
#[error("Values cannot be assigned to an abstract data type. `{id}` is abstract.")]
AbstractDataType { id: VersionedUrl },
#[error("the value provided does not match the constraints of the data type")]
ConstraintUnfulfilled,
#[error("the property `{key}` was required, but not specified")]
Expand Down
1 change: 0 additions & 1 deletion libs/@local/hash-graph-types/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#![feature(hash_raw_entry)]

extern crate alloc;
extern crate core;

pub mod knowledge;
pub mod ontology;
Expand Down
4 changes: 3 additions & 1 deletion libs/@local/hash-graph-types/rust/src/ontology/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,12 @@ pub struct OntologyTypeWithMetadata<S: OntologyType> {
}

pub trait OntologyTypeProvider<O> {
type Value: Borrow<O> + Send;

fn provide_type(
&self,
type_id: &VersionedUrl,
) -> impl Future<Output = Result<impl Borrow<O> + Send, Report<impl Context>>> + Send;
) -> impl Future<Output = Result<Self::Value, Report<impl Context>>> + Send;
}

pub trait DataTypeProvider: OntologyTypeProvider<DataTypeWithMetadata> {
Expand Down
39 changes: 26 additions & 13 deletions libs/@local/hash-validation/src/entity_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,17 +267,30 @@ impl EntityVisitor for ValueValidator {
&mut self,
data_type: &DataTypeWithMetadata,
value: &mut JsonValue,
_: &mut ValueMetadata,
metadata: &mut ValueMetadata,
_: &P,
) -> Result<(), Report<[TraversalError]>>
where
P: DataTypeProvider + Sync,
{
data_type
.schema
.validate_constraints(value)
.change_context(TraversalError::ConstraintUnfulfilled)
.map_err(Report::expand)
let mut status = ReportSink::new();

status.attempt(
data_type
.schema
.validate_constraints(value)
.change_context(TraversalError::ConstraintUnfulfilled),
);

if metadata.data_type_id.as_ref() == Some(&data_type.schema.id)
&& data_type.schema.r#abstract
{
status.capture(TraversalError::AbstractDataType {
id: data_type.schema.id.clone(),
});
}

status.finish()
}
}

Expand Down Expand Up @@ -312,20 +325,20 @@ impl EntityVisitor for EntityPreprocessor {
});
}

if let Err(error) = type_provider
let desired_data_type = type_provider
.provide_type(data_type_url)
.await
.change_context_lazy(|| TraversalError::DataTypeRetrieval {
id: DataTypeReference {
url: data_type.schema.id.clone(),
},
})?
.borrow()
.schema
.validate_constraints(value)
.change_context(TraversalError::ConstraintUnfulfilled)
})?;

if let Err(error) = ValueValidator
.visit_value(desired_data_type.borrow(), value, metadata, type_provider)
.await
{
status.capture(error);
status.append(error);
}
}
} else {
Expand Down
59 changes: 41 additions & 18 deletions libs/@local/hash-validation/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![feature(extend_one)]
#![feature(hash_raw_entry)]

extern crate alloc;

pub mod error;

pub use self::entity_type::{EntityPreprocessor, EntityValidationError};
Expand Down Expand Up @@ -92,6 +94,7 @@ pub trait EntityProvider {

#[cfg(test)]
mod tests {
use alloc::sync::Arc;
use std::collections::HashMap;

use graph_types::{
Expand Down Expand Up @@ -150,9 +153,9 @@ mod tests {

struct Provider {
entities: HashMap<EntityId, Entity>,
entity_types: HashMap<VersionedUrl, ClosedEntityType>,
property_types: HashMap<VersionedUrl, PropertyType>,
data_types: HashMap<VersionedUrl, DataTypeWithMetadata>,
entity_types: HashMap<VersionedUrl, Arc<ClosedEntityType>>,
property_types: HashMap<VersionedUrl, Arc<PropertyType>>,
data_types: HashMap<VersionedUrl, Arc<DataTypeWithMetadata>>,
}
impl Provider {
fn new(
Expand All @@ -166,14 +169,22 @@ mod tests {
.into_iter()
.map(|entity| (entity.metadata.record_id.entity_id, entity))
.collect(),
entity_types: entity_types.into_iter().collect(),
entity_types: entity_types
.into_iter()
.map(|(url, schema)| (url, Arc::new(schema)))
.collect(),
property_types: property_types
.into_iter()
.map(|schema| (schema.id.clone(), schema))
.map(|schema| (schema.id.clone(), Arc::new(schema)))
.collect(),
data_types: data_types
.into_iter()
.map(|schema| (schema.id.clone(), generate_data_type_metadata(schema)))
.map(|schema| {
(
schema.id.clone(),
Arc::new(generate_data_type_metadata(schema)),
)
})
.collect(),
}
}
Expand Down Expand Up @@ -232,42 +243,54 @@ mod tests {
}

impl OntologyTypeProvider<ClosedEntityType> for Provider {
type Value = Arc<ClosedEntityType>;

#[expect(refining_impl_trait)]
async fn provide_type(
&self,
type_id: &VersionedUrl,
) -> Result<&ClosedEntityType, Report<InvalidEntityType>> {
self.entity_types.get(type_id).ok_or_else(|| {
Report::new(InvalidEntityType {
id: type_id.clone(),
) -> Result<Arc<ClosedEntityType>, Report<InvalidEntityType>> {
self.entity_types
.get(type_id)
.map(Arc::clone)
.ok_or_else(|| {
Report::new(InvalidEntityType {
id: type_id.clone(),
})
})
})
}
}

impl OntologyTypeProvider<PropertyType> for Provider {
type Value = Arc<PropertyType>;

#[expect(refining_impl_trait)]
async fn provide_type(
&self,
type_id: &VersionedUrl,
) -> Result<&PropertyType, Report<InvalidPropertyType>> {
self.property_types.get(type_id).ok_or_else(|| {
Report::new(InvalidPropertyType {
id: type_id.clone(),
) -> Result<Arc<PropertyType>, Report<InvalidPropertyType>> {
self.property_types
.get(type_id)
.map(Arc::clone)
.ok_or_else(|| {
Report::new(InvalidPropertyType {
id: type_id.clone(),
})
})
})
}
}

impl PropertyTypeProvider for Provider {}

impl OntologyTypeProvider<DataTypeWithMetadata> for Provider {
type Value = Arc<DataTypeWithMetadata>;

#[expect(refining_impl_trait)]
async fn provide_type(
&self,
type_id: &VersionedUrl,
) -> Result<&DataTypeWithMetadata, Report<InvalidDataType>> {
self.data_types.get(type_id).ok_or_else(|| {
) -> Result<Arc<DataTypeWithMetadata>, Report<InvalidDataType>> {
self.data_types.get(type_id).map(Arc::clone).ok_or_else(|| {
Report::new(InvalidDataType {
id: type_id.clone(),
})
Expand Down
3 changes: 2 additions & 1 deletion tests/hash-graph-http/tests/ambiguous.http
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ X-Authenticated-User-Actor-Id: {{account_id}}
"title": "Length",
"type": "number",
"description": "A unit of length",
"minimum": 0
"minimum": 0,
"abstract": true
},
{
"$schema": "https://blockprotocol.org/types/modules/graph/0.3/schema/data-type",
Expand Down
3 changes: 2 additions & 1 deletion tests/hash-graph-test-data/rust/src/data_type/boolean.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"$id": "https://blockprotocol.org/@blockprotocol/types/data-type/boolean/v/1",
"title": "Boolean",
"description": "A True or False value",
"type": "boolean"
"type": "boolean",
"abstract": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
{
"$ref": "https://hash.ai/@hash/types/data-type/length/v/1"
}
]
],
"abstract": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
{
"$ref": "https://hash.ai/@hash/types/data-type/length/v/1"
}
]
],
"abstract": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"title": "Empty List",
"description": "An Empty List",
"type": "array",
"const": []
"const": [],
"abstract": false
}
3 changes: 2 additions & 1 deletion tests/hash-graph-test-data/rust/src/data_type/length.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
{
"$ref": "https://blockprotocol.org/@blockprotocol/types/data-type/number/v/1"
}
]
],
"abstract": true
}
3 changes: 2 additions & 1 deletion tests/hash-graph-test-data/rust/src/data_type/meter.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
{
"$ref": "https://hash.ai/@hash/types/data-type/length/v/1"
}
]
],
"abstract": false
}
3 changes: 2 additions & 1 deletion tests/hash-graph-test-data/rust/src/data_type/null.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"$id": "https://blockprotocol.org/@blockprotocol/types/data-type/null/v/1",
"title": "Null",
"description": "A placeholder value representing 'nothing'",
"type": "null"
"type": "null",
"abstract": false
}
3 changes: 2 additions & 1 deletion tests/hash-graph-test-data/rust/src/data_type/number.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"$id": "https://blockprotocol.org/@blockprotocol/types/data-type/number/v/1",
"title": "Number",
"description": "An arithmetical value (in the Real number system)",
"type": "number"
"type": "number",
"abstract": false
}
3 changes: 2 additions & 1 deletion tests/hash-graph-test-data/rust/src/data_type/object_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"$id": "https://blockprotocol.org/@blockprotocol/types/data-type/object/v/1",
"title": "Object",
"description": "A plain JSON object with no pre-defined structure",
"type": "object"
"type": "object",
"abstract": false
}
3 changes: 2 additions & 1 deletion tests/hash-graph-test-data/rust/src/data_type/object_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"$id": "https://blockprotocol.org/@blockprotocol/types/data-type/object/v/2",
"title": "Object",
"description": "An updated plain JSON object with no pre-defined structure",
"type": "object"
"type": "object",
"abstract": false
}
3 changes: 2 additions & 1 deletion tests/hash-graph-test-data/rust/src/data_type/text.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"$id": "https://blockprotocol.org/@blockprotocol/types/data-type/text/v/1",
"title": "Text",
"description": "An ordered sequence of characters",
"type": "string"
"type": "string",
"abstract": false
}

0 comments on commit 0133856

Please sign in to comment.