use std::collections::BTreeSet;
use crate::{
ConstraintSourceKey, Decimal, DeclaredEntityVersion, EntitySourceKey, FieldSourceKey,
IndexSourceKey, MAX_FRAGMENT_CONSTRAINTS, MAX_FRAGMENT_ENTITIES, MAX_FRAGMENT_FIELDS,
MAX_FRAGMENT_INDEXES, MAX_FRAGMENT_RELATIONS, MAX_FRAGMENT_TYPES, MAX_SCHEMA_FIELD_TYPE_DEPTH,
RelationSourceKey, RuleSourceKey, ScalarKind, ScalarLiteral, SchemaContractError, SchemaName,
SourceCheckExpr, SourceRuleOperation, TypeSourceKey,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FieldType {
Scalar(ScalarType),
List(Box<Self>),
Named(TypeSourceKey),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ScalarType {
Account,
Blob {
max_len: Option<u32>,
},
Bool,
Date,
Decimal {
scale: u32,
},
Duration,
Float32,
Float64,
Int8,
Int16,
Int32,
Int64,
Int128,
IntBig {
max_bytes: u32,
},
Principal,
Subaccount,
Text {
max_len: Option<u32>,
},
Timestamp,
Nat8,
Nat16,
Nat32,
Nat64,
Nat128,
NatBig {
max_bytes: u32,
},
Ulid,
Unit,
}
impl ScalarType {
#[must_use]
pub const fn kind(self) -> ScalarKind {
match self {
Self::Account => ScalarKind::Account,
Self::Blob { .. } => ScalarKind::Blob,
Self::Bool => ScalarKind::Bool,
Self::Date => ScalarKind::Date,
Self::Decimal { .. } => ScalarKind::Decimal,
Self::Duration => ScalarKind::Duration,
Self::Float32 => ScalarKind::Float32,
Self::Float64 => ScalarKind::Float64,
Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 => ScalarKind::Int,
Self::Int128 => ScalarKind::Int128,
Self::IntBig { .. } => ScalarKind::IntBig,
Self::Principal => ScalarKind::Principal,
Self::Subaccount => ScalarKind::Subaccount,
Self::Text { .. } => ScalarKind::Text,
Self::Timestamp => ScalarKind::Timestamp,
Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 => ScalarKind::Nat,
Self::Nat128 => ScalarKind::Nat128,
Self::NatBig { .. } => ScalarKind::NatBig,
Self::Ulid => ScalarKind::Ulid,
Self::Unit => ScalarKind::Unit,
}
}
pub(crate) const fn validate(self) -> Result<(), SchemaContractError> {
match self {
Self::Decimal { scale } if scale > Decimal::max_supported_scale() => {
Err(SchemaContractError::InvalidFieldType)
}
Self::IntBig { max_bytes: 0 } | Self::NatBig { max_bytes: 0 } => {
Err(SchemaContractError::InvalidFieldType)
}
_ => Ok(()),
}
}
pub(crate) fn accepts_literal(self, literal: &ScalarLiteral) -> bool {
match (self, literal) {
(Self::Account, ScalarLiteral::Account(_))
| (Self::Bool, ScalarLiteral::Bool(_))
| (Self::Date, ScalarLiteral::Date(_))
| (Self::Duration, ScalarLiteral::Duration(_))
| (Self::Float32, ScalarLiteral::Float32(_))
| (Self::Float64, ScalarLiteral::Float64(_))
| (Self::Int128, ScalarLiteral::Int(_))
| (Self::Principal, ScalarLiteral::Principal(_))
| (Self::Subaccount, ScalarLiteral::Subaccount(_))
| (Self::Timestamp, ScalarLiteral::Timestamp(_))
| (Self::Nat128, ScalarLiteral::Nat(_))
| (Self::Ulid, ScalarLiteral::Ulid(_))
| (Self::Unit, ScalarLiteral::Unit(_)) => true,
(Self::Blob { max_len }, ScalarLiteral::Blob(value)) => {
max_len.is_none_or(|max| value.len() <= max as usize)
}
(Self::Text { max_len }, ScalarLiteral::Text(value)) => {
max_len.is_none_or(|max| value.chars().count() <= max as usize)
}
(Self::Int8, ScalarLiteral::Int(value)) => i8::try_from(*value).is_ok(),
(Self::Int16, ScalarLiteral::Int(value)) => i16::try_from(*value).is_ok(),
(Self::Int32, ScalarLiteral::Int(value)) => i32::try_from(*value).is_ok(),
(Self::Int64, ScalarLiteral::Int(value)) => i64::try_from(*value).is_ok(),
(Self::IntBig { max_bytes }, ScalarLiteral::IntBig(value)) => {
value.to_leb128().len() <= max_bytes as usize
}
(Self::Nat8, ScalarLiteral::Nat(value)) => u8::try_from(*value).is_ok(),
(Self::Nat16, ScalarLiteral::Nat(value)) => u16::try_from(*value).is_ok(),
(Self::Nat32, ScalarLiteral::Nat(value)) => u32::try_from(*value).is_ok(),
(Self::Nat64, ScalarLiteral::Nat(value)) => u64::try_from(*value).is_ok(),
(Self::NatBig { max_bytes }, ScalarLiteral::NatBig(value)) => {
value.to_leb128().len() <= max_bytes as usize
}
(Self::Decimal { scale }, ScalarLiteral::Decimal(value)) => {
decimal_fits_scale(*value, scale)
}
_ => false,
}
}
}
impl FieldType {
pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
self.validate_at_depth(0)
}
const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
let Some(depth) = depth.checked_add(1) else {
return Err(SchemaContractError::FieldTypeDepthExceeded);
};
if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
return Err(SchemaContractError::FieldTypeDepthExceeded);
}
match self {
Self::Scalar(scalar) => scalar.validate(),
Self::List(item) => item.validate_at_depth(depth),
Self::Named(_) => Ok(()),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FieldInsertPolicy {
Required,
Nullable,
Default(ScalarLiteral),
Generated,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FieldManagementPolicy {
CreatedAt,
UpdatedAt,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FieldFragment {
source_key: FieldSourceKey,
name: SchemaName,
field_type: FieldType,
nullable: bool,
insert_policy: FieldInsertPolicy,
management: Option<FieldManagementPolicy>,
}
impl FieldFragment {
#[must_use]
pub fn new(
name: SchemaName,
field_type: FieldType,
nullable: bool,
insert_policy: FieldInsertPolicy,
management: Option<FieldManagementPolicy>,
) -> Self {
Self {
source_key: FieldSourceKey::from_name(&name),
name,
field_type,
nullable,
insert_policy,
management,
}
}
#[must_use]
pub const fn source_key(&self) -> &FieldSourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub const fn field_type(&self) -> &FieldType {
&self.field_type
}
#[must_use]
pub const fn nullable(&self) -> bool {
self.nullable
}
#[must_use]
pub const fn insert_policy(&self) -> &FieldInsertPolicy {
&self.insert_policy
}
#[must_use]
pub const fn management(&self) -> Option<FieldManagementPolicy> {
self.management
}
pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
ensure_current_name_key(self.source_key.as_str(), &self.name)?;
self.field_type.validate()?;
if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
literal.validate()?;
match &self.field_type {
FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
return Err(SchemaContractError::LiteralTypeMismatch);
}
}
}
if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
return Err(SchemaContractError::InvalidFieldPolicy);
}
if self.management.is_some()
&& (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
|| self.nullable
|| !matches!(self.insert_policy, FieldInsertPolicy::Required))
{
return Err(SchemaContractError::InvalidFieldPolicy);
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum IndexKeyFragment {
Field(FieldSourceKey),
Lower(FieldSourceKey),
Upper(FieldSourceKey),
Trim(FieldSourceKey),
LowerTrim(FieldSourceKey),
Date(FieldSourceKey),
Year(FieldSourceKey),
Month(FieldSourceKey),
Day(FieldSourceKey),
}
impl IndexKeyFragment {
#[must_use]
pub const fn field(&self) -> &FieldSourceKey {
match self {
Self::Field(field)
| Self::Lower(field)
| Self::Upper(field)
| Self::Trim(field)
| Self::LowerTrim(field)
| Self::Date(field)
| Self::Year(field)
| Self::Month(field)
| Self::Day(field) => field,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IndexFragment {
source_key: IndexSourceKey,
name: SchemaName,
key: Vec<IndexKeyFragment>,
unique: bool,
predicate: Option<SourceCheckExpr>,
}
impl IndexFragment {
pub fn try_new(
name: SchemaName,
key: Vec<IndexKeyFragment>,
unique: bool,
predicate: Option<SourceCheckExpr>,
) -> Result<Self, SchemaContractError> {
if key.is_empty() {
return Err(SchemaContractError::InvalidReferenceList);
}
if let Some(predicate) = &predicate {
predicate.validate()?;
}
Ok(Self {
source_key: IndexSourceKey::from_name(&name),
name,
key,
unique,
predicate,
})
}
#[must_use]
pub const fn source_key(&self) -> &IndexSourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub fn key(&self) -> &[IndexKeyFragment] {
&self.key
}
#[must_use]
pub const fn unique(&self) -> bool {
self.unique
}
#[must_use]
pub const fn predicate(&self) -> Option<&SourceCheckExpr> {
self.predicate.as_ref()
}
fn validate(&self) -> Result<(), SchemaContractError> {
let rebuilt = Self::try_new(
self.name.clone(),
self.key.clone(),
self.unique,
self.predicate.clone(),
)?;
ensure_canonical_rebuild(self, &rebuilt)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RelationDeleteAction {
Restrict,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RelationFragment {
source_key: RelationSourceKey,
name: SchemaName,
local_fields: Vec<FieldSourceKey>,
target_entity: EntitySourceKey,
target_fields: Vec<FieldSourceKey>,
on_delete: RelationDeleteAction,
}
impl RelationFragment {
pub fn try_new(
name: SchemaName,
local_fields: Vec<FieldSourceKey>,
target_entity: EntitySourceKey,
target_fields: Vec<FieldSourceKey>,
on_delete: RelationDeleteAction,
) -> Result<Self, SchemaContractError> {
if local_fields.is_empty() || local_fields.len() != target_fields.len() {
return Err(SchemaContractError::InvalidReferenceList);
}
ensure_unique(&local_fields)?;
ensure_unique(&target_fields)?;
Ok(Self {
source_key: RelationSourceKey::from_name(&name),
name,
local_fields,
target_entity,
target_fields,
on_delete,
})
}
#[must_use]
pub const fn source_key(&self) -> &RelationSourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub fn local_fields(&self) -> &[FieldSourceKey] {
&self.local_fields
}
#[must_use]
pub const fn target_entity(&self) -> &EntitySourceKey {
&self.target_entity
}
#[must_use]
pub fn target_fields(&self) -> &[FieldSourceKey] {
&self.target_fields
}
#[must_use]
pub const fn on_delete(&self) -> RelationDeleteAction {
self.on_delete
}
fn validate(&self) -> Result<(), SchemaContractError> {
let rebuilt = Self::try_new(
self.name.clone(),
self.local_fields.clone(),
self.target_entity.clone(),
self.target_fields.clone(),
self.on_delete,
)?;
ensure_canonical_rebuild(self, &rebuilt)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ConstraintFragmentKind {
Check(SourceCheckExpr),
TargetedRule(TargetedRuleFragment),
}
impl ConstraintFragmentKind {
fn validate(&self) -> Result<(), SchemaContractError> {
match self {
Self::Check(expression) => expression.validate(),
Self::TargetedRule(rule) => rule.validate(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TargetedRuleFragment {
root: FieldSourceKey,
target_type: TypeSourceKey,
rule: RuleSourceKey,
operation: SourceRuleOperation,
}
impl TargetedRuleFragment {
#[must_use]
pub fn new(
root: FieldSourceKey,
target_type: TypeSourceKey,
rule: SchemaName,
operation: SourceRuleOperation,
) -> Self {
Self {
root,
target_type,
rule: RuleSourceKey::from_name(&rule),
operation,
}
}
#[must_use]
pub const fn root(&self) -> &FieldSourceKey {
&self.root
}
#[must_use]
pub const fn target_type(&self) -> &TypeSourceKey {
&self.target_type
}
#[must_use]
pub const fn rule(&self) -> &RuleSourceKey {
&self.rule
}
#[must_use]
pub const fn operation(&self) -> &SourceRuleOperation {
&self.operation
}
fn validate(&self) -> Result<(), SchemaContractError> {
self.operation.validate()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConstraintFragment {
source_key: ConstraintSourceKey,
name: SchemaName,
kind: ConstraintFragmentKind,
}
impl ConstraintFragment {
#[must_use]
pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
Self {
source_key: ConstraintSourceKey::from_name(&name),
name,
kind: ConstraintFragmentKind::Check(expression),
}
}
#[must_use]
pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
let source_key = ConstraintSourceKey::for_targeted_field_rule(
rule.root(),
rule.target_type(),
rule.rule(),
);
let name = SchemaName::for_targeted_rule(&source_key);
Self {
source_key,
name,
kind: ConstraintFragmentKind::TargetedRule(rule),
}
}
#[must_use]
pub const fn source_key(&self) -> &ConstraintSourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub const fn kind(&self) -> &ConstraintFragmentKind {
&self.kind
}
fn validate(&self) -> Result<(), SchemaContractError> {
self.kind.validate()?;
let rebuilt = match &self.kind {
ConstraintFragmentKind::Check(expression) => {
Self::check(self.name.clone(), expression.clone())
}
ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
};
ensure_canonical_rebuild(self, &rebuilt)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EntityFragment {
source_key: EntitySourceKey,
name: SchemaName,
version: DeclaredEntityVersion,
fields: Vec<FieldFragment>,
primary_key: Vec<FieldSourceKey>,
indexes: Vec<IndexFragment>,
relations: Vec<RelationFragment>,
constraints: Vec<ConstraintFragment>,
}
impl EntityFragment {
pub fn try_new(
name: SchemaName,
version: DeclaredEntityVersion,
mut fields: Vec<FieldFragment>,
primary_key: Vec<FieldSourceKey>,
mut indexes: Vec<IndexFragment>,
mut relations: Vec<RelationFragment>,
mut constraints: Vec<ConstraintFragment>,
) -> Result<Self, SchemaContractError> {
let source_key = EntitySourceKey::from_name(&name);
check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
check_len(
"entity constraints",
constraints.len(),
MAX_FRAGMENT_CONSTRAINTS,
)?;
if primary_key.is_empty() {
return Err(SchemaContractError::InvalidReferenceList);
}
ensure_unique(&primary_key)?;
fields.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
indexes.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
relations.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
constraints.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
ensure_unique_names(fields.iter().map(FieldFragment::name))?;
ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
ensure_unique_names(relations.iter().map(RelationFragment::name))?;
ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
for field in &fields {
field.validate()?;
}
validate_management_cardinality(&fields)?;
for index in &indexes {
index.validate()?;
}
for relation in &relations {
relation.validate()?;
}
for constraint in &constraints {
constraint.validate()?;
}
let field_keys = fields
.iter()
.map(|field| field.source_key.clone())
.collect::<BTreeSet<_>>();
if primary_key.iter().any(|field| !field_keys.contains(field)) {
return Err(SchemaContractError::InvalidLocalReference);
}
validate_insert_generation(&fields, &primary_key)?;
for index in &indexes {
if index
.key()
.iter()
.any(|component| !field_keys.contains(component.field()))
|| index.predicate().is_some_and(|predicate| {
predicate
.dependencies()
.iter()
.any(|field| !field_keys.contains(field))
})
{
return Err(SchemaContractError::InvalidLocalReference);
}
}
for relation in &relations {
if relation
.local_fields()
.iter()
.any(|field| !field_keys.contains(field))
|| (relation.target_entity() == &source_key
&& relation
.target_fields()
.iter()
.any(|field| !field_keys.contains(field)))
{
return Err(SchemaContractError::InvalidLocalReference);
}
}
for constraint in &constraints {
let invalid = match constraint.kind() {
ConstraintFragmentKind::Check(expression) => expression
.dependencies()
.iter()
.any(|field| !field_keys.contains(field)),
ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
};
if invalid {
return Err(SchemaContractError::InvalidLocalReference);
}
}
Ok(Self {
source_key,
name,
version,
fields,
primary_key,
indexes,
relations,
constraints,
})
}
#[must_use]
pub const fn source_key(&self) -> &EntitySourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub const fn version(&self) -> DeclaredEntityVersion {
self.version
}
#[must_use]
pub fn fields(&self) -> &[FieldFragment] {
&self.fields
}
#[must_use]
pub fn primary_key(&self) -> &[FieldSourceKey] {
&self.primary_key
}
#[must_use]
pub fn indexes(&self) -> &[IndexFragment] {
&self.indexes
}
#[must_use]
pub fn relations(&self) -> &[RelationFragment] {
&self.relations
}
#[must_use]
pub fn constraints(&self) -> &[ConstraintFragment] {
&self.constraints
}
pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
let rebuilt = Self::try_new(
self.name.clone(),
self.version,
self.fields.clone(),
self.primary_key.clone(),
self.indexes.clone(),
self.relations.clone(),
self.constraints.clone(),
)?;
ensure_canonical_rebuild(self, &rebuilt)
}
}
fn validate_insert_generation(
fields: &[FieldFragment],
primary_key: &[FieldSourceKey],
) -> Result<(), SchemaContractError> {
for field in fields {
if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
continue;
}
if field.nullable() || field.management().is_some() {
return Err(SchemaContractError::InvalidFieldPolicy);
}
match field.field_type() {
FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
FieldType::Scalar(
ScalarType::Nat8
| ScalarType::Nat16
| ScalarType::Nat32
| ScalarType::Nat64
| ScalarType::Nat128,
) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
return Err(SchemaContractError::InvalidFieldPolicy);
}
}
}
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RecordFieldFragment {
source_key: FieldSourceKey,
name: SchemaName,
field_type: FieldType,
nullable: bool,
}
impl RecordFieldFragment {
#[must_use]
pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
Self {
source_key: FieldSourceKey::from_name(&name),
name,
field_type,
nullable,
}
}
#[must_use]
pub const fn source_key(&self) -> &FieldSourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub const fn field_type(&self) -> &FieldType {
&self.field_type
}
#[must_use]
pub const fn nullable(&self) -> bool {
self.nullable
}
fn validate(&self) -> Result<(), SchemaContractError> {
if !current_name_key_matches(self.source_key.as_str(), &self.name) {
return Err(SchemaContractError::NonCanonical);
}
self.field_type.validate()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TupleElementFragment {
field_type: FieldType,
nullable: bool,
}
impl TupleElementFragment {
#[must_use]
pub const fn new(field_type: FieldType, nullable: bool) -> Self {
Self {
field_type,
nullable,
}
}
#[must_use]
pub const fn field_type(&self) -> &FieldType {
&self.field_type
}
#[must_use]
pub const fn nullable(&self) -> bool {
self.nullable
}
const fn validate(&self) -> Result<(), SchemaContractError> {
self.field_type.validate()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RecordTypeFragment {
source_key: TypeSourceKey,
name: SchemaName,
fields: Vec<RecordFieldFragment>,
}
impl RecordTypeFragment {
pub fn try_new(
name: SchemaName,
mut fields: Vec<RecordFieldFragment>,
) -> Result<Self, SchemaContractError> {
check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
fields.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
for field in &fields {
field.validate()?;
}
Ok(Self {
source_key: TypeSourceKey::from_name(&name),
name,
fields,
})
}
#[must_use]
pub const fn source_key(&self) -> &TypeSourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub fn fields(&self) -> &[RecordFieldFragment] {
&self.fields
}
fn validate(&self) -> Result<(), SchemaContractError> {
let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
if rebuilt != *self {
return Err(SchemaContractError::NonCanonical);
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EnumVariantFragment {
source_key: TypeSourceKey,
name: SchemaName,
payload: Option<FieldType>,
}
impl EnumVariantFragment {
#[must_use]
pub fn new(name: SchemaName) -> Self {
Self {
source_key: TypeSourceKey::from_name(&name),
name,
payload: None,
}
}
#[must_use]
pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
Self {
source_key: TypeSourceKey::from_name(&name),
name,
payload: Some(payload),
}
}
#[must_use]
pub const fn source_key(&self) -> &TypeSourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub const fn payload(&self) -> Option<&FieldType> {
self.payload.as_ref()
}
fn validate(&self) -> Result<(), SchemaContractError> {
if !current_name_key_matches(self.source_key.as_str(), &self.name) {
return Err(SchemaContractError::NonCanonical);
}
match &self.payload {
Some(payload) => payload.validate(),
None => Ok(()),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EnumTypeFragment {
source_key: TypeSourceKey,
name: SchemaName,
variants: Vec<EnumVariantFragment>,
}
impl EnumTypeFragment {
pub fn try_new(
name: SchemaName,
mut variants: Vec<EnumVariantFragment>,
) -> Result<Self, SchemaContractError> {
if variants.is_empty() {
return Err(SchemaContractError::InvalidReferenceList);
}
check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
variants.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
for variant in &variants {
variant.validate()?;
}
Ok(Self {
source_key: TypeSourceKey::from_name(&name),
name,
variants,
})
}
#[must_use]
pub const fn source_key(&self) -> &TypeSourceKey {
&self.source_key
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
&self.name
}
#[must_use]
pub fn variants(&self) -> &[EnumVariantFragment] {
&self.variants
}
fn validate(&self) -> Result<(), SchemaContractError> {
let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
if rebuilt != *self {
return Err(SchemaContractError::NonCanonical);
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum NamedTypeFragment {
Record(RecordTypeFragment),
Enum(EnumTypeFragment),
Newtype {
source_key: TypeSourceKey,
name: SchemaName,
inner: FieldType,
},
List {
source_key: TypeSourceKey,
name: SchemaName,
item: FieldType,
},
Set {
source_key: TypeSourceKey,
name: SchemaName,
item: FieldType,
},
Map {
source_key: TypeSourceKey,
name: SchemaName,
key: FieldType,
value: FieldType,
},
Tuple {
source_key: TypeSourceKey,
name: SchemaName,
members: Vec<TupleElementFragment>,
},
}
impl NamedTypeFragment {
#[must_use]
pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
Self::Newtype {
source_key: TypeSourceKey::from_name(&name),
name,
inner,
}
}
#[must_use]
pub fn list(name: SchemaName, item: FieldType) -> Self {
Self::List {
source_key: TypeSourceKey::from_name(&name),
name,
item,
}
}
#[must_use]
pub fn set(name: SchemaName, item: FieldType) -> Self {
Self::Set {
source_key: TypeSourceKey::from_name(&name),
name,
item,
}
}
#[must_use]
pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
Self::Map {
source_key: TypeSourceKey::from_name(&name),
name,
key,
value,
}
}
#[must_use]
pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
Self::Tuple {
source_key: TypeSourceKey::from_name(&name),
name,
members,
}
}
#[must_use]
pub const fn source_key(&self) -> &TypeSourceKey {
match self {
Self::Record(record) => record.source_key(),
Self::Enum(r#enum) => r#enum.source_key(),
Self::Newtype { source_key, .. }
| Self::List { source_key, .. }
| Self::Set { source_key, .. }
| Self::Map { source_key, .. }
| Self::Tuple { source_key, .. } => source_key,
}
}
#[must_use]
pub const fn name(&self) -> &SchemaName {
match self {
Self::Record(record) => record.name(),
Self::Enum(r#enum) => r#enum.name(),
Self::Newtype { name, .. }
| Self::List { name, .. }
| Self::Set { name, .. }
| Self::Map { name, .. }
| Self::Tuple { name, .. } => name,
}
}
fn validate(&self) -> Result<(), SchemaContractError> {
ensure_current_name_key(self.source_key().as_str(), self.name())?;
match self {
Self::Record(record) => record.validate(),
Self::Enum(r#enum) => r#enum.validate(),
Self::Newtype { inner, .. }
| Self::List { item: inner, .. }
| Self::Set { item: inner, .. } => inner.validate(),
Self::Map { key, value, .. } => {
key.validate()?;
value.validate()
}
Self::Tuple { members, .. } => {
if members.is_empty() {
return Err(SchemaContractError::InvalidReferenceList);
}
check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
members.iter().try_for_each(TupleElementFragment::validate)
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SchemaFragment {
entities: Vec<EntityFragment>,
types: Vec<NamedTypeFragment>,
}
impl SchemaFragment {
pub fn try_new(
mut entities: Vec<EntityFragment>,
mut types: Vec<NamedTypeFragment>,
) -> Result<Self, SchemaContractError> {
check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
entities.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
types.sort_unstable_by(|left, right| left.source_key().cmp(right.source_key()));
ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
ensure_unique_names(entities.iter().map(EntityFragment::name))?;
ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
for entity in &entities {
entity.validate()?;
}
for r#type in &types {
r#type.validate()?;
}
Ok(Self { entities, types })
}
#[must_use]
pub fn entities(&self) -> &[EntityFragment] {
&self.entities
}
#[must_use]
pub fn types(&self) -> &[NamedTypeFragment] {
&self.types
}
pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
for r#type in &self.types {
r#type.validate()?;
}
let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
if rebuilt != *self {
return Err(SchemaContractError::NonCanonical);
}
Ok(())
}
}
pub(crate) const fn check_len(
kind: &'static str,
len: usize,
max: usize,
) -> Result<(), SchemaContractError> {
if len > max {
return Err(SchemaContractError::TooManyItems { kind, len, max });
}
Ok(())
}
fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
source_key == name.as_str()
}
fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
if !current_name_key_matches(source_key, name) {
return Err(SchemaContractError::NonCanonical);
}
Ok(())
}
fn ensure_canonical_rebuild<T: PartialEq>(
current: &T,
rebuilt: &T,
) -> Result<(), SchemaContractError> {
if current != rebuilt {
return Err(SchemaContractError::NonCanonical);
}
Ok(())
}
fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
where
T: Ord,
{
let mut seen = BTreeSet::new();
if values.iter().any(|value| !seen.insert(value)) {
return Err(SchemaContractError::InvalidReferenceList);
}
Ok(())
}
fn ensure_unique_sorted_by<T, K>(
values: &[T],
key: impl Fn(&T) -> &K,
) -> Result<(), SchemaContractError>
where
K: Eq,
{
if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
return Err(SchemaContractError::DuplicateSourceKey);
}
Ok(())
}
fn ensure_unique_names<'a>(
names: impl IntoIterator<Item = &'a SchemaName>,
) -> Result<(), SchemaContractError> {
let mut seen = BTreeSet::new();
if names.into_iter().any(|name| !seen.insert(name)) {
return Err(SchemaContractError::DuplicateName);
}
Ok(())
}
fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
for policy in [
FieldManagementPolicy::CreatedAt,
FieldManagementPolicy::UpdatedAt,
] {
if fields
.iter()
.filter(|field| field.management() == Some(policy))
.count()
> 1
{
return Err(SchemaContractError::InvalidFieldPolicy);
}
}
Ok(())
}
fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
match value.scale().cmp(&scale) {
std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
}
}
#[cfg(test)]
mod tests {
use super::{
FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
SchemaContractError, SchemaName,
};
#[test]
fn independently_decoded_field_key_and_name_must_match() {
let field = FieldFragment {
source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
name: SchemaName::try_new("current_name").expect("fixture name should admit"),
field_type: FieldType::Scalar(ScalarType::Nat64),
nullable: false,
insert_policy: FieldInsertPolicy::Required,
management: None,
};
assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
}
}