use crate::constraint::Constraint;
use crate::ion_path::IonPath;
use crate::isl::isl_constraint::IslConstraintValue;
use crate::isl::isl_type::IslType;
use crate::isl::IslVersion;
use crate::result::{invalid_schema_error, IonSchemaResult, ValidationResult};
use crate::system::{PendingTypes, TypeId, TypeStore};
use crate::violation::{Violation, ViolationCode};
use crate::IonSchemaElement;
use ion_rs::element::Element;
use ion_rs::IonType;
use ion_rs::Symbol;
use std::fmt::{Display, Formatter};
use std::sync::Arc;
pub(crate) trait TypeValidator {
fn is_valid(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> bool;
fn validate(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> ValidationResult;
}
#[derive(Debug, Clone)]
pub struct TypeDefinition {
id: TypeId,
type_store: Arc<TypeStore>,
}
impl TypeDefinition {
pub(crate) fn new(id: TypeId, type_store: Arc<TypeStore>) -> Self {
Self { id, type_store }
}
pub fn id(&self) -> TypeId {
self.id
}
pub fn validate<I: Into<IonSchemaElement>>(&self, value: I) -> ValidationResult {
let type_def = self.type_store.get_type_by_id(self.id).unwrap();
let schema_element: IonSchemaElement = value.into();
type_def.validate(&schema_element, &self.type_store, &mut IonPath::default())
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum BuiltInTypeDefinition {
Atomic(IonType, Nullability),
Derived(TypeDefinitionImpl),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Nullability {
Nullable,
NotNullable,
}
impl BuiltInTypeDefinition {
pub(crate) fn parse_from_isl_type(
isl_version: IslVersion,
isl_type: &IslType,
type_store: &mut TypeStore,
pending_types: &mut PendingTypes,
) -> IonSchemaResult<Self> {
let mut constraints = vec![];
let type_name = isl_type.name();
for isl_constraint in isl_type.constraints() {
let constraint = Constraint::resolve_from_isl_constraint(
isl_version,
&isl_constraint.constraint_value,
type_store,
pending_types,
true,
)?;
constraints.push(constraint);
}
let builtin_type_def = BuiltInTypeDefinition::Derived(TypeDefinitionImpl::new(
type_name.to_owned(),
constraints,
None,
));
Ok(builtin_type_def)
}
}
impl TypeValidator for BuiltInTypeDefinition {
fn is_valid(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> bool {
let violation = self.validate(value, type_store, ion_path);
violation.is_ok()
}
fn validate(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> ValidationResult {
match &self {
BuiltInTypeDefinition::Atomic(ion_type, is_nullable) => {
match value {
IonSchemaElement::SingleElement(element) => {
if *is_nullable == Nullability::NotNullable && element.is_null() {
return Err(Violation::new(
"type_constraint",
ViolationCode::InvalidNull,
format!("expected type {ion_type:?} doesn't allow null"),
ion_path,
));
}
if element.ion_type() != *ion_type {
return Err(Violation::new(
"type_constraint",
ViolationCode::TypeMismatched,
format!(
"expected type {:?}, found {:?}",
ion_type,
element.ion_type()
),
ion_path,
));
}
Ok(())
}
IonSchemaElement::Document(document) => Err(Violation::new(
"type_constraint",
ViolationCode::TypeMismatched,
format!("expected type {ion_type:?}, found document"),
ion_path,
)),
}
}
BuiltInTypeDefinition::Derived(other_type) => {
if other_type.name() == &Some("document".to_owned()) {
if Option::is_none(&value.as_document()) {
return Err(Violation::new(
"type_constraint",
ViolationCode::TypeMismatched,
format!(
"expected type document found {:?}",
value.as_element().unwrap().ion_type()
),
ion_path,
));
}
return Ok(());
}
other_type.validate(value, type_store, ion_path)
}
}
}
}
impl Display for BuiltInTypeDefinition {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self {
BuiltInTypeDefinition::Atomic(ion_type, _) => write!(f, "{ion_type}"),
BuiltInTypeDefinition::Derived(type_def) => write!(f, "{type_def}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum TypeDefinitionKind {
Named(TypeDefinitionImpl),
Anonymous(TypeDefinitionImpl),
BuiltIn(BuiltInTypeDefinition),
}
impl TypeDefinitionKind {
pub fn named<A: Into<String>, B: Into<Vec<Constraint>>>(
name: A,
constraints: B,
) -> TypeDefinitionKind {
TypeDefinitionKind::Named(TypeDefinitionImpl::new(
Some(name.into()),
constraints.into(),
None,
))
}
pub fn is_deferred_type_def(&self) -> bool {
match self {
TypeDefinitionKind::Named(type_def) => type_def.is_deferred_type_def,
_ => false,
}
}
pub fn anonymous<A: Into<Vec<Constraint>>>(constraints: A) -> TypeDefinitionKind {
TypeDefinitionKind::Anonymous(TypeDefinitionImpl::new(None, constraints.into(), None))
}
pub fn constraints(&self) -> &[Constraint] {
match &self {
TypeDefinitionKind::Named(named_type) => named_type.constraints(),
TypeDefinitionKind::Anonymous(anonymous_type) => anonymous_type.constraints(),
_ => &[],
}
}
pub fn is_valid_for_base_nullable_type(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> bool {
let built_in_type_name = match self {
TypeDefinitionKind::Named(_) | TypeDefinitionKind::Anonymous(_) => {
None
}
TypeDefinitionKind::BuiltIn(built_int_type) => match built_int_type {
BuiltInTypeDefinition::Atomic(ion_type, nullability) => {
Some(format!("${ion_type}"))
}
BuiltInTypeDefinition::Derived(type_def) => {
let type_name = type_def.name().as_ref().unwrap().to_string();
if !type_name.starts_with('$') {
Some(format!("${type_name}"))
} else {
Some(type_name)
}
}
},
}
.unwrap(); let type_def = type_store
.get_type_by_id(
type_store
.get_builtin_type_id(built_in_type_name.as_str())
.unwrap(),
)
.unwrap();
type_def.is_valid(value, type_store, ion_path)
}
}
impl Display for TypeDefinitionKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self {
TypeDefinitionKind::Named(named_type_def) => {
write!(f, "{named_type_def}")
}
TypeDefinitionKind::Anonymous(anonymous_type_def) => {
write!(f, "{anonymous_type_def}")
}
TypeDefinitionKind::BuiltIn(builtin_type_def) => {
write!(f, "{builtin_type_def}")
}
}
}
}
impl TypeValidator for TypeDefinitionKind {
fn is_valid(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> bool {
let violation = self.validate(value, type_store, ion_path);
violation.is_ok()
}
fn validate(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> ValidationResult {
match self {
TypeDefinitionKind::Named(named_type) => {
named_type.validate(value, type_store, ion_path)
}
TypeDefinitionKind::Anonymous(anonymous_type) => {
anonymous_type.validate(value, type_store, ion_path)
}
TypeDefinitionKind::BuiltIn(built_in_type) => {
built_in_type.validate(value, type_store, ion_path)
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct TypeDefinitionImpl {
name: Option<String>,
constraints: Vec<Constraint>,
is_deferred_type_def: bool,
isl_type_struct: Option<Element>,
}
impl TypeDefinitionImpl {
pub fn new(
name: Option<String>,
constraints: Vec<Constraint>,
isl_type_struct: Option<Element>,
) -> Self {
Self {
name,
constraints,
is_deferred_type_def: false,
isl_type_struct,
}
}
pub fn new_deferred_type_def(name: String) -> Self {
Self {
name: Some(name),
constraints: vec![],
is_deferred_type_def: true,
isl_type_struct: None,
}
}
pub fn name(&self) -> &Option<String> {
&self.name
}
pub fn with_name(self, alias: String) -> Self {
Self {
name: Some(alias),
constraints: self.constraints,
is_deferred_type_def: self.is_deferred_type_def,
isl_type_struct: None,
}
}
pub fn is_deferred_type_def(&self) -> bool {
self.is_deferred_type_def
}
pub fn constraints(&self) -> &[Constraint] {
&self.constraints
}
pub(crate) fn parse_from_isl_type_and_update_pending_types(
isl_version: IslVersion,
isl_type: &IslType,
type_store: &mut TypeStore,
pending_types: &mut PendingTypes,
) -> IonSchemaResult<TypeId> {
let mut constraints = vec![];
let type_name = isl_type.name();
if let Some(type_name) = type_name {
if let Some(type_def) = type_store.get_type_def_by_name(type_name) {
if isl_version == IslVersion::V2_0 && !type_def.is_deferred_type_def() {
return invalid_schema_error(format!(
"The schema document can not have two type definitions with same name: {}",
type_name
));
}
}
pending_types.add_parent(type_name.to_owned(), type_store);
}
let type_id = pending_types.add_type(type_store, type_name.to_owned());
let mut found_type_constraint = false;
for isl_constraint in isl_type.constraints() {
if let IslConstraintValue::Type(_) = isl_constraint.constraint_value {
found_type_constraint = true;
}
let constraint = Constraint::resolve_from_isl_constraint(
isl_version,
&isl_constraint.constraint_value,
type_store,
pending_types,
isl_type.is_open_content_allowed(),
)?;
constraints.push(constraint);
}
let isl_struct = isl_type.isl_type_struct.as_ref();
if !found_type_constraint && isl_version == IslVersion::V1_0 {
let isl_type_name = match type_name.to_owned() {
Some(name) => name,
None => match isl_struct {
None => "".to_owned(),
Some(isl_type_struct) => format!("{isl_type_struct}"),
},
};
let isl_constraint: IslConstraintValue =
IslConstraintValue::from_ion_element(
isl_version,
"type",
&Element::symbol(Symbol::from("any")),
&isl_type_name,
&mut vec![],
)?;
let constraint = Constraint::resolve_from_isl_constraint(
isl_version,
&isl_constraint,
type_store,
pending_types,
true, )?;
constraints.push(constraint);
}
let type_def = TypeDefinitionImpl::new(
type_name.to_owned(),
constraints,
isl_type.isl_type_struct.to_owned(),
);
let actual_type_id;
if type_name.is_some() {
actual_type_id = pending_types.update_named_type(
type_id,
type_name.as_ref().unwrap(),
type_def,
type_store,
);
pending_types.clear_parent();
} else {
actual_type_id = pending_types.update_anonymous_type(type_id, type_def, type_store);
}
Ok(actual_type_id)
}
}
impl PartialEq for TypeDefinitionImpl {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name() && self.constraints == other.constraints()
}
}
impl TypeValidator for TypeDefinitionImpl {
fn is_valid(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> bool {
let violation = self.validate(value, type_store, ion_path);
violation.is_ok()
}
fn validate(
&self,
value: &IonSchemaElement,
type_store: &TypeStore,
ion_path: &mut IonPath,
) -> ValidationResult {
let mut violations: Vec<Violation> = vec![];
let type_name = match self.name() {
None => match self.isl_type_struct.as_ref() {
None => "".to_owned(),
Some(anonymous_struct) => {
format!("{anonymous_struct}")
}
},
Some(name) => name.to_owned(),
};
for constraint in self.constraints() {
if let Err(violation) = constraint.validate(value, type_store, ion_path) {
violations.push(violation);
}
}
if violations.is_empty() {
return Ok(());
}
Err(Violation::with_violations(
type_name,
ViolationCode::TypeConstraintsUnsatisfied,
"value didn't satisfy type constraint(s)",
ion_path,
violations,
))
}
}
impl Display for TypeDefinitionImpl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let type_def_name = match &self.name {
None => match &self.isl_type_struct {
None => "".to_owned(),
Some(type_name) => format!("{type_name}"),
},
Some(type_name) => type_name.to_owned(),
};
write!(f, "{type_def_name}")
}
}
#[cfg(test)]
mod type_definition_tests {
use super::*;
use crate::constraint::Constraint;
use crate::isl::isl_constraint::v_1_0::*;
use crate::isl::isl_type::v_1_0::*;
use crate::isl::isl_type::IslType;
use crate::isl::isl_type_reference::v_1_0::*;
use crate::isl::ranges::*;
use crate::isl::util::Ieee754InterchangeFormat;
use crate::isl::util::TimestampPrecision;
use crate::isl::*;
use crate::system::PendingTypes;
use rstest::*;
use std::collections::HashSet;
#[rstest(
isl_type, type_def,
case::type_constraint_with_anonymous_type(
anonymous_type([type_constraint(named_type_ref("int"))]),
TypeDefinitionKind::anonymous([Constraint::type_constraint(0)])
),
case::type_constraint_with_named_type(
named_type("my_int", [type_constraint(named_type_ref("int"))]),
TypeDefinitionKind::named("my_int", [Constraint::type_constraint(0)])
),
case::type_constraint_with_self_reference_type(
named_type("my_int", [type_constraint(named_type_ref("my_int"))]),
TypeDefinitionKind::named("my_int", [Constraint::type_constraint(35)])
),
case::type_constraint_with_nested_self_reference_type(
named_type("my_int", [type_constraint(anonymous_type_ref([type_constraint(named_type_ref("my_int"))]))]),
TypeDefinitionKind::named("my_int", [Constraint::type_constraint(36)]) ),
case::type_constraint_with_nested_type(
named_type("my_int", [type_constraint(anonymous_type_ref([type_constraint(named_type_ref("int"))]))]),
TypeDefinitionKind::named("my_int", [Constraint::type_constraint(36)])
),
case::type_constraint_with_nested_multiple_types(
named_type("my_int", [type_constraint(anonymous_type_ref([type_constraint(named_type_ref("int"))])), type_constraint(anonymous_type_ref([type_constraint(named_type_ref("my_int"))]))]),
TypeDefinitionKind::named("my_int", [Constraint::type_constraint(36), Constraint::type_constraint(37)])
),
case::all_of_constraint(
anonymous_type([all_of([anonymous_type_ref([type_constraint(named_type_ref("int"))])])]),
TypeDefinitionKind::anonymous([Constraint::all_of([36]), Constraint::type_constraint(34)])
),
case::any_of_constraint(
anonymous_type([any_of([anonymous_type_ref([type_constraint(named_type_ref("int"))]), anonymous_type_ref([type_constraint(named_type_ref("decimal"))])])]),
TypeDefinitionKind::anonymous([Constraint::any_of([36, 37]), Constraint::type_constraint(34)])
),
case::one_of_constraint(
anonymous_type([one_of([anonymous_type_ref([type_constraint(named_type_ref("int"))]), anonymous_type_ref([type_constraint(named_type_ref("decimal"))])])]),
TypeDefinitionKind::anonymous([Constraint::one_of([36, 37]), Constraint::type_constraint(34)])
),
case::not_constraint(
anonymous_type([not(anonymous_type_ref([type_constraint(named_type_ref("int"))]))]),
TypeDefinitionKind::anonymous([Constraint::not(36), Constraint::type_constraint(34)])
),
case::ordered_elements_constraint(
anonymous_type([ordered_elements([variably_occurring_type_ref(named_type_ref("symbol"), UsizeRange::new_single_value(1)), variably_occurring_type_ref(anonymous_type_ref([type_constraint(named_type_ref("int"))]), UsizeRange::new_single_value(1))])]),
TypeDefinitionKind::anonymous([Constraint::ordered_elements([5, 36]), Constraint::type_constraint(34)])
),
case::fields_constraint(
anonymous_type([fields(vec![("name".to_owned(), variably_occurring_type_ref(named_type_ref("string"), UsizeRange::zero_or_one())), ("id".to_owned(), variably_occurring_type_ref(named_type_ref("int"), UsizeRange::zero_or_one()))].into_iter())]),
TypeDefinitionKind::anonymous([Constraint::fields(vec![("name".to_owned(), 4), ("id".to_owned(), 0)].into_iter()), Constraint::type_constraint(34)])
),
case::field_names_constraint(
isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::field_names(named_type_ref("symbol"), true)]),
TypeDefinitionKind::anonymous([Constraint::field_names(5, true), Constraint::type_constraint(34)])
),
case::contains_constraint(
anonymous_type([contains([true.into(), 1.into(), "hello".to_owned().into()])]),
TypeDefinitionKind::anonymous([Constraint::contains([true.into(), 1.into(), "hello".to_owned().into()]), Constraint::type_constraint(34)])
),
case::container_length_constraint(
anonymous_type([container_length(3.into())]),
TypeDefinitionKind::anonymous([Constraint::container_length(3.into()), Constraint::type_constraint(34)])
),
case::byte_length_constraint(
anonymous_type([byte_length(3.into())]),
TypeDefinitionKind::anonymous([Constraint::byte_length(3.into()), Constraint::type_constraint(34)])
),
case::codepoint_length_constraint(
anonymous_type([codepoint_length(3.into())]),
TypeDefinitionKind::anonymous([Constraint::codepoint_length(3.into()), Constraint::type_constraint(34)])
),
case::element_constraint(
anonymous_type([element(named_type_ref("int"))]),
TypeDefinitionKind::anonymous([Constraint::element(0, false), Constraint::type_constraint(34)])
),
case::distinct_element_constraint(
isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::element(named_type_ref("int"), true)]),
TypeDefinitionKind::anonymous([Constraint::element(0, true), Constraint::type_constraint(34)])
),
case::annotations_constraint(
anonymous_type([annotations(vec!["closed"], vec![Symbol::from("red").into(), Symbol::from("blue").into(), Symbol::from("green").into()])]),
TypeDefinitionKind::anonymous([Constraint::annotations(vec!["closed"], vec![Symbol::from("red").into(), Symbol::from("blue").into(), Symbol::from("green").into()]), Constraint::type_constraint(34)])
),
case::annotations_v2_0_constraint(
isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::annotations(isl_type_reference::v_2_0::anonymous_type_ref([isl_constraint::v_2_0::container_length(1.into())]))]),
TypeDefinitionKind::anonymous([Constraint::annotations_v2_0(36), Constraint::type_constraint(34)])
),
case::precision_constraint(
anonymous_type([precision(3.into())]),
TypeDefinitionKind::anonymous([Constraint::precision(3.into()), Constraint::type_constraint(34)])
),
case::scale_constraint(
anonymous_type([scale(2.into())]),
TypeDefinitionKind::anonymous([Constraint::scale(2.into()), Constraint::type_constraint(34)])
),
case::exponent_constraint(
isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::exponent(2.into())]),
TypeDefinitionKind::anonymous([Constraint::exponent(2.into()), Constraint::type_constraint(34)])
),
case::timestamp_precision_constraint(
anonymous_type([timestamp_precision(TimestampPrecisionRange::new_single_value(TimestampPrecision::Month))]),
TypeDefinitionKind::anonymous([Constraint::timestamp_precision(TimestampPrecision::Month.into()), Constraint::type_constraint(34)])
),
case::valid_values_constraint(
anonymous_type([valid_values(vec![2.into(), ion_rs::Decimal::new(35, -1).into(), 5e7.into(), "hello".to_owned().into(), Symbol::from("hi").into(), NumberRange::new_inclusive(2.into(), 3.into()).unwrap().into()]).unwrap()]),
TypeDefinitionKind::anonymous([Constraint::valid_values(vec![2.into(), ion_rs::Decimal::new(35, -1).into(), 5e7.into(), "hello".to_owned().into(), Symbol::from("hi").into(), NumberRange::new_inclusive(2.into(), 3.into()).unwrap().into()], IslVersion::V1_0).unwrap(), Constraint::type_constraint(34)])
),
case::utf8_byte_length_constraint(
anonymous_type([utf8_byte_length(3.into())]),
TypeDefinitionKind::anonymous([Constraint::utf8_byte_length(3.into()), Constraint::type_constraint(34)])
),
case::regex_constraint(
anonymous_type(
[regex(
false, false, "[abc]".to_string()
)]
),
TypeDefinitionKind::anonymous([
Constraint::regex(
false, false, "[abc]".to_string(),
IslVersion::V1_0
).unwrap(),
Constraint::type_constraint(34)
])
),
case::timestamp_offset_constraint(
anonymous_type(
[timestamp_offset(vec!["-00:00".try_into().unwrap()])]
),
TypeDefinitionKind::anonymous([Constraint::timestamp_offset(vec!["-00:00".try_into().unwrap()]),
Constraint::type_constraint(34)
])
),
case::ieee754_float_constraint(
isl_type::v_2_0::anonymous_type([isl_constraint::v_2_0::ieee754_float(Ieee754InterchangeFormat::Binary16)]),
TypeDefinitionKind::anonymous([Constraint::ieee754_float(Ieee754InterchangeFormat::Binary16), Constraint::type_constraint(34)])
),
)]
fn isl_type_to_type_definition(isl_type: IslType, type_def: TypeDefinitionKind) {
let type_store = &mut TypeStore::default();
let pending_types = &mut PendingTypes::default();
let this_type_def = {
let type_id = TypeDefinitionImpl::parse_from_isl_type_and_update_pending_types(
IslVersion::V1_0,
&isl_type,
type_store,
pending_types,
)
.unwrap();
pending_types
.update_type_store(type_store, None, &HashSet::new())
.unwrap();
type_store.get_type_by_id(type_id).unwrap()
};
assert_eq!(this_type_def, &type_def);
}
}