Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

H-3426: Implement folding for data type constraints #5380

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ use std::collections::{HashMap, hash_map::Entry};

#[cfg(feature = "postgres")]
use bytes::BytesMut;
use error_stack::{Report, bail};
use itertools::Itertools;
#[cfg(feature = "postgres")]
use postgres_types::{FromSql, IsNull, ToSql, Type};
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json};
use thiserror::Error;

use crate::{
Expand Down Expand Up @@ -60,6 +62,22 @@ pub enum ResolveClosedDataTypeError {
AmbiguousMetadata,
#[error("No description was found for the schema.")]
MissingDescription,
#[error("The data type constraints intersected to different types.")]
IntersectedDifferentTypes,
#[error("The value {} does not satisfy the constraint: {}", .0, json!(.1))]
UnsatisfiedConstraint(JsonValue, ValueConstraints),
#[error("The value {0} does not satisfy the constraint")]
UnsatisfiedEnumConstraintVariant(JsonValue),
#[error("No value satisfy the constraint: {}", json!(.0))]
UnsatisfiedEnumConstraint(ValueConstraints),
#[error("Conflicting const values: {0} and {1}")]
ConflictingConstValues(JsonValue, JsonValue),
#[error("Conflicting enum values, no common values found: {} and {}", json!(.0), json!(.1))]
ConflictingEnumValues(Vec<JsonValue>, Vec<JsonValue>),
#[error("The const value is not in the enum values: {} and {}", .0, json!(.1))]
ConflictingConstEnumValue(JsonValue, Vec<JsonValue>),
#[error("The constraint is unsatisfiable: {}", json!(.0))]
UnsatisfiableConstraint(ValueConstraints),
}

impl ClosedDataType {
Expand All @@ -74,7 +92,7 @@ impl ClosedDataType {
pub fn from_resolve_data(
data_type: DataType,
resolve_data: &DataTypeResolveData,
) -> Result<Self, ResolveClosedDataTypeError> {
) -> Result<Self, Report<ResolveClosedDataTypeError>> {
let (description, label) = if data_type.description.is_some() || !data_type.label.is_empty()
{
(data_type.description, data_type.label)
Expand All @@ -91,10 +109,9 @@ impl ClosedDataType {
title_plural: data_type.title_plural.clone(),
description: description.ok_or(ResolveClosedDataTypeError::MissingDescription)?,
label,
all_of: iter::once(&data_type.constraints)
.chain(resolve_data.constraints())
.cloned()
.collect(),
all_of: ValueConstraints::fold_intersections(
iter::once(data_type.constraints).chain(resolve_data.constraints().cloned()),
)?,
r#abstract: data_type.r#abstract,
})
}
Expand Down Expand Up @@ -209,7 +226,9 @@ impl DataTypeResolveData {
///
/// Returns an error if the metadata is ambiguous. This is the case if two schemas at the same
/// inheritance depth specify different metadata.
pub fn find_metadata_schema(&self) -> Result<Option<&DataType>, ResolveClosedDataTypeError> {
pub fn find_metadata_schema(
&self,
) -> Result<Option<&DataType>, Report<ResolveClosedDataTypeError>> {
let mut found_schema_data = None::<(InheritanceDepth, &DataType)>;
for (depth, stored_schema) in self.ordered_schemas() {
if stored_schema.description.is_some() || !stored_schema.label.is_empty() {
Expand All @@ -222,7 +241,7 @@ impl DataTypeResolveData {
if stored_schema.description != found_schema.description
|| stored_schema.label != found_schema.label
{
return Err(ResolveClosedDataTypeError::AmbiguousMetadata);
bail!(ResolveClosedDataTypeError::AmbiguousMetadata);
}
}
cmp::Ordering::Greater => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ use error_stack::{Report, ReportSink, ResultExt};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;

use crate::schema::{ConstraintError, SingleValueSchema, data_type::constraint::Constraint};
use crate::schema::{
ConstraintError, SingleValueSchema,
data_type::{
closed::ResolveClosedDataTypeError,
constraint::{Constraint, ConstraintValidator},
},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))]
Expand All @@ -15,7 +21,18 @@ pub struct AnyOfConstraints {
pub any_of: Vec<SingleValueSchema>,
}

impl Constraint<JsonValue> for AnyOfConstraints {
impl Constraint for AnyOfConstraints {
fn intersection(
self,
other: Self,
) -> Result<(Self, Option<Self>), Report<ResolveClosedDataTypeError>> {
// TODO: Implement folding for anyOf constraints
// see https://linear.app/hash/issue/H-3430/implement-folding-for-anyof-constraints
Ok((self, Some(other)))
}
}

impl ConstraintValidator<JsonValue> for AnyOfConstraints {
type Error = ConstraintError;

fn is_valid(&self, value: &JsonValue) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use thiserror::Error;

use crate::schema::{
ConstraintError, JsonSchemaValueType, NumberSchema, StringSchema, ValueLabel,
data_type::constraint::{Constraint, boolean::BooleanSchema},
data_type::{
closed::ResolveClosedDataTypeError,
constraint::{Constraint, ConstraintValidator, boolean::BooleanSchema},
},
};

#[derive(Debug, Error)]
Expand Down Expand Up @@ -43,7 +46,27 @@ pub enum ArrayItemConstraints {
String(StringSchema),
}

impl Constraint<JsonValue> for ArrayItemConstraints {
impl Constraint for ArrayItemConstraints {
fn intersection(
self,
other: Self,
) -> Result<(Self, Option<Self>), Report<ResolveClosedDataTypeError>> {
match (self, other) {
(Self::Boolean(lhs), Self::Boolean(rhs)) => lhs
.intersection(rhs)
.map(|(lhs, rhs)| (Self::Boolean(lhs), rhs.map(Self::Boolean))),
(Self::Number(lhs), Self::Number(rhs)) => lhs
.intersection(rhs)
.map(|(lhs, rhs)| (Self::Number(lhs), rhs.map(Self::Number))),
(Self::String(lhs), Self::String(rhs)) => lhs
.intersection(rhs)
.map(|(lhs, rhs)| (Self::String(lhs), rhs.map(Self::String))),
_ => bail!(ResolveClosedDataTypeError::IntersectedDifferentTypes),
indietyp marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

impl ConstraintValidator<JsonValue> for ArrayItemConstraints {
type Error = ConstraintError;

fn is_valid(&self, value: &JsonValue) -> bool {
Expand Down Expand Up @@ -104,7 +127,38 @@ pub enum ArraySchema {
Tuple(TupleConstraints),
}

impl Constraint<JsonValue> for ArraySchema {
impl Constraint for ArraySchema {
fn intersection(
self,
other: Self,
) -> Result<(Self, Option<Self>), Report<ResolveClosedDataTypeError>> {
Ok(match (self, other) {
(Self::Constrained(lhs), Self::Constrained(rhs)) => {
let (combined, remainder) = lhs.intersection(rhs)?;
(
Self::Constrained(combined),
remainder.map(Self::Constrained),
)
}
(Self::Tuple(lhs), Self::Constrained(rhs)) => {
// TODO: Implement folding for array constraints
// see https://linear.app/hash/issue/H-3429/implement-folding-for-array-constraints
(Self::Tuple(lhs), Some(Self::Constrained(rhs)))
}
(Self::Constrained(lhs), Self::Tuple(rhs)) => {
// TODO: Implement folding for array constraints
// see https://linear.app/hash/issue/H-3429/implement-folding-for-array-constraints
(Self::Constrained(lhs), Some(Self::Tuple(rhs)))
}
(Self::Tuple(lhs), Self::Tuple(rhs)) => {
let (combined, remainder) = lhs.intersection(rhs)?;
(Self::Tuple(combined), remainder.map(Self::Tuple))
}
})
}
}

impl ConstraintValidator<JsonValue> for ArraySchema {
type Error = ConstraintError;

fn is_valid(&self, value: &JsonValue) -> bool {
Expand All @@ -127,7 +181,7 @@ impl Constraint<JsonValue> for ArraySchema {
}
}

impl Constraint<[JsonValue]> for ArraySchema {
impl ConstraintValidator<[JsonValue]> for ArraySchema {
type Error = ConstraintError;

fn is_valid(&self, value: &[JsonValue]) -> bool {
Expand Down Expand Up @@ -158,7 +212,18 @@ pub struct ArrayConstraints {
pub items: Option<ArrayItemsSchema>,
}

impl Constraint<[JsonValue]> for ArrayConstraints {
impl Constraint for ArrayConstraints {
fn intersection(
self,
other: Self,
) -> Result<(Self, Option<Self>), Report<ResolveClosedDataTypeError>> {
// TODO: Implement folding for array constraints
// see https://linear.app/hash/issue/H-3429/implement-folding-for-array-constraints
Ok((self, Some(other)))
}
}

impl ConstraintValidator<[JsonValue]> for ArrayConstraints {
type Error = [ArrayValidationError];

fn is_valid(&self, value: &[JsonValue]) -> bool {
Expand Down Expand Up @@ -198,7 +263,18 @@ pub struct TupleConstraints {
pub prefix_items: Vec<ArrayItemsSchema>,
}

impl Constraint<[JsonValue]> for TupleConstraints {
impl Constraint for TupleConstraints {
fn intersection(
self,
other: Self,
) -> Result<(Self, Option<Self>), Report<ResolveClosedDataTypeError>> {
// TODO: Implement folding for array constraints
// see https://linear.app/hash/issue/H-3429/implement-folding-for-array-constraints
Ok((self, Some(other)))
}
}

impl ConstraintValidator<[JsonValue]> for TupleConstraints {
type Error = [ArrayValidationError];

fn is_valid(&self, value: &[JsonValue]) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use error_stack::{Report, bail};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;

use crate::schema::{Constraint, ConstraintError, JsonSchemaValueType};
use crate::schema::{
ConstraintError, ConstraintValidator, JsonSchemaValueType,
data_type::{closed::ResolveClosedDataTypeError, constraint::Constraint},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))]
Expand All @@ -16,7 +19,16 @@ pub enum BooleanTypeTag {
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct BooleanSchema;
indietyp marked this conversation as resolved.
Show resolved Hide resolved

impl Constraint<JsonValue> for BooleanSchema {
impl Constraint for BooleanSchema {
fn intersection(
self,
_other: Self,
) -> Result<(Self, Option<Self>), Report<ResolveClosedDataTypeError>> {
Ok((self, None))
}
}

impl ConstraintValidator<JsonValue> for BooleanSchema {
type Error = ConstraintError;

fn is_valid(&self, value: &JsonValue) -> bool {
Expand Down
Loading
Loading