use std::collections::{BTreeMap, HashMap, HashSet}; use crate::{ CallOperationRequest, FetchInterfaceRequest, FetchInterfaceResponse, InterfaceDescriptor, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER, NameNamespace, Object, ObjectObservation, ObserveRequest, PathSegment, TypeDeclaration, TypeExpr, UNION_CASE_FIELD, UNION_VALUE_FIELD, ValidationError, ValidationErrorKind, Value, ValueKind, }; pub(crate) fn validate_object(object: &Object) -> Result<(), ValidationError> { validate_object_name(&object.name, true)?; let mut seen = HashSet::new(); for interface in &object.interfaces { if !seen.insert(interface.as_str()) { return Err( ValidationError::new(ValidationErrorKind::DuplicateInterface { reference: interface.clone(), }) .prepend(PathSegment::Field("interfaces".into())), ); } } Ok(()) } fn validate_object_name(name: &str, allow_root: bool) -> Result<(), ValidationError> { let valid = (allow_root || !name.is_empty()) && !name.contains('/') && name != "." && name != ".."; if valid { Ok(()) } else { Err(ValidationError::new( ValidationErrorKind::InvalidObjectName { name: name.to_owned(), }, )) } } pub(crate) fn validate_object_at_path(object: &Object, path: &str) -> Result<(), ValidationError> { validate_path(path)?; object .validate() .map_err(|error| error.prepend(PathSegment::Object(path.to_owned())))?; let expected = if path == "/" { "" } else { path.rsplit('/') .next() .expect("non-root path has a segment") }; if object.name == expected { Ok(()) } else { Err( ValidationError::new(ValidationErrorKind::ObjectNameMismatch { expected: expected.to_owned(), actual: object.name.clone(), }) .prepend(PathSegment::Object(path.to_owned())), ) } } pub(crate) fn validate_object_consistency( left: &Object, right: &Object, ) -> Result<(), ValidationError> { left.validate()?; right.validate()?; if let (Some(left_ref), Some(right_ref)) = (&left.r#ref, &right.r#ref) && left_ref == right_ref && left.validator.is_some() && left.validator == right.validator && left != right { return Err(ValidationError::new( ValidationErrorKind::InconsistentObjectObservation { reference: left_ref.clone(), }, )); } Ok(()) } pub(crate) fn validate_path(path: &str) -> Result<(), ValidationError> { let valid = if path == "/" { true } else if let Some(remainder) = path.strip_prefix('/') { !remainder.is_empty() && remainder .split('/') .all(|segment| !segment.is_empty() && segment != "." && segment != "..") } else { false }; if valid { Ok(()) } else { Err(ValidationError::new(ValidationErrorKind::InvalidPath { path: path.to_owned(), })) } } pub(crate) fn validate_descriptor(descriptor: &InterfaceDescriptor) -> Result<(), ValidationError> { let declarations = declaration_map(descriptor)?; for declaration in &descriptor.types { validate_type_expr(&declaration.definition, &declarations).map_err(|error| { error .prepend(PathSegment::Declaration(declaration.name.clone())) .prepend(PathSegment::Descriptor) })?; } let mut operation_names = HashSet::new(); for operation in &descriptor.operations { if !operation_names.insert(operation.name.as_str()) { return Err(ValidationError::new(ValidationErrorKind::DuplicateName { namespace: NameNamespace::Operation, name: operation.name.clone(), }) .prepend(PathSegment::Operation(operation.name.clone())) .prepend(PathSegment::Descriptor)); } let mut parameter_names = HashSet::new(); for parameter in &operation.parameters { if !parameter_names.insert(parameter.name.as_str()) { return Err(ValidationError::new(ValidationErrorKind::DuplicateName { namespace: NameNamespace::Parameter, name: parameter.name.clone(), }) .prepend(PathSegment::Parameter(parameter.name.clone())) .prepend(PathSegment::Operation(operation.name.clone())) .prepend(PathSegment::Descriptor)); } validate_type_expr(¶meter.r#type, &declarations).map_err(|error| { error .prepend(PathSegment::Parameter(parameter.name.clone())) .prepend(PathSegment::Operation(operation.name.clone())) .prepend(PathSegment::Descriptor) })?; } validate_type_expr(&operation.returns.r#type, &declarations).map_err(|error| { error .prepend(PathSegment::Return) .prepend(PathSegment::Operation(operation.name.clone())) .prepend(PathSegment::Descriptor) })?; } validate_no_cycles(descriptor, &declarations) } fn declaration_map( descriptor: &InterfaceDescriptor, ) -> Result, ValidationError> { let mut declarations = HashMap::new(); for declaration in &descriptor.types { if declarations .insert(declaration.name.as_str(), declaration) .is_some() { return Err(ValidationError::new(ValidationErrorKind::DuplicateName { namespace: NameNamespace::TypeDeclaration, name: declaration.name.clone(), }) .prepend(PathSegment::Declaration(declaration.name.clone())) .prepend(PathSegment::Descriptor)); } } Ok(declarations) } fn validate_type_expr( r#type: &TypeExpr, declarations: &HashMap<&str, &TypeDeclaration>, ) -> Result<(), ValidationError> { match r#type { TypeExpr::Named { name } if !declarations.contains_key(name.as_str()) => { Err(ValidationError::new(ValidationErrorKind::UnknownType { name: name.clone(), })) } TypeExpr::Record { fields } => { let mut names = HashSet::new(); for field in fields { if !names.insert(field.name.as_str()) { return Err(ValidationError::new(ValidationErrorKind::DuplicateName { namespace: NameNamespace::Field, name: field.name.clone(), }) .prepend(PathSegment::Field(field.name.clone()))); } validate_type_expr(&field.r#type, declarations) .map_err(|error| error.prepend(PathSegment::Field(field.name.clone())))?; } Ok(()) } TypeExpr::List { items } => validate_type_expr(items, declarations), TypeExpr::Enum { cases } => validate_case_names( cases.iter().map(|case| case.name.as_str()), NameNamespace::EnumCase, ), TypeExpr::Union { cases } => { validate_case_names( cases.iter().map(|case| case.name.as_str()), NameNamespace::UnionCase, )?; for case in cases { if let Some(payload) = &case.payload { validate_type_expr(payload, declarations) .map_err(|error| error.prepend(PathSegment::Case(case.name.clone())))?; } } Ok(()) } _ => Ok(()), } } fn validate_case_names<'a>( names: impl Iterator, namespace: NameNamespace, ) -> Result<(), ValidationError> { let mut seen = HashSet::new(); for name in names { if !seen.insert(name) { return Err(ValidationError::new(ValidationErrorKind::DuplicateName { namespace, name: name.to_owned(), }) .prepend(PathSegment::Case(name.to_owned()))); } } Ok(()) } fn validate_no_cycles( descriptor: &InterfaceDescriptor, declarations: &HashMap<&str, &TypeDeclaration>, ) -> Result<(), ValidationError> { let mut state: HashMap<&str, VisitState> = HashMap::new(); let mut stack = Vec::new(); for declaration in &descriptor.types { visit_declaration( declaration.name.as_str(), declarations, &mut state, &mut stack, )?; } Ok(()) } #[derive(Clone, Copy, PartialEq, Eq)] enum VisitState { Visiting, Visited, } fn visit_declaration<'a>( name: &'a str, declarations: &HashMap<&'a str, &'a TypeDeclaration>, state: &mut HashMap<&'a str, VisitState>, stack: &mut Vec<&'a str>, ) -> Result<(), ValidationError> { match state.get(name) { Some(VisitState::Visited) => return Ok(()), Some(VisitState::Visiting) => { let start = stack.iter().position(|entry| *entry == name).unwrap_or(0); let mut cycle: Vec = stack[start..] .iter() .map(|entry| (*entry).to_owned()) .collect(); cycle.push(name.to_owned()); return Err( ValidationError::new(ValidationErrorKind::RecursiveType { cycle }) .prepend(PathSegment::Declaration(name.to_owned())) .prepend(PathSegment::Descriptor), ); } None => {} } state.insert(name, VisitState::Visiting); stack.push(name); let mut references = Vec::new(); collect_type_references(&declarations[name].definition, &mut references); for reference in references { visit_declaration(reference, declarations, state, stack)?; } stack.pop(); state.insert(name, VisitState::Visited); Ok(()) } fn collect_type_references<'a>(r#type: &'a TypeExpr, references: &mut Vec<&'a str>) { match r#type { TypeExpr::Named { name } => references.push(name), TypeExpr::Record { fields } => { for field in fields { collect_type_references(&field.r#type, references); } } TypeExpr::List { items } => collect_type_references(items, references), TypeExpr::Union { cases } => { for case in cases { if let Some(payload) = &case.payload { collect_type_references(payload, references); } } } _ => {} } } pub(crate) fn validate_root_value( descriptor: &InterfaceDescriptor, r#type: &TypeExpr, value: &Value, ) -> Result<(), ValidationError> { descriptor.validate()?; let declarations = declaration_map(descriptor)?; validate_type_expr(r#type, &declarations) .map_err(|error| error.prepend(PathSegment::Descriptor))?; validate_value(r#type, value, &declarations) .map_err(|error| error.prepend(PathSegment::Descriptor)) } fn validate_value( r#type: &TypeExpr, value: &Value, declarations: &HashMap<&str, &TypeDeclaration>, ) -> Result<(), ValidationError> { match r#type { TypeExpr::Unit => expect_kind(value, ValueKind::Unit), TypeExpr::Boolean => expect_kind(value, ValueKind::Boolean), TypeExpr::Integer => validate_integer(value), TypeExpr::Number => validate_number(value), TypeExpr::String => expect_kind(value, ValueKind::String), TypeExpr::Bytes => expect_kind(value, ValueKind::Bytes), TypeExpr::Json => validate_json(value), TypeExpr::Entry => match value { Value::String(path) => validate_path(path), _ => expect_kind(value, ValueKind::String), }, TypeExpr::Named { name } => { let Some(declaration) = declarations.get(name.as_str()) else { return Err(ValidationError::new(ValidationErrorKind::UnknownType { name: name.clone(), })); }; validate_value(&declaration.definition, value, declarations) } TypeExpr::Record { fields } => validate_record(fields, value, declarations), TypeExpr::List { items } => match value { Value::List(values) => { for (index, item) in values.iter().enumerate() { validate_value(items, item, declarations) .map_err(|error| error.prepend(PathSegment::Index(index)))?; } Ok(()) } _ => expect_kind(value, ValueKind::List), }, TypeExpr::Enum { cases } => match value { Value::String(case) if cases.iter().any(|declared| declared.name == *case) => Ok(()), Value::String(case) => { Err( ValidationError::new(ValidationErrorKind::UnknownCase { name: case.clone() }) .prepend(PathSegment::Case(case.clone())), ) } _ => expect_kind(value, ValueKind::String), }, TypeExpr::Union { cases } => validate_union(cases, value, declarations), } } fn validate_integer(value: &Value) -> Result<(), ValidationError> { match value { Value::Integer(integer) if (MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(integer) => { Ok(()) } Value::Integer(integer) => Err(ValidationError::new( ValidationErrorKind::IntegerOutOfRange { value: *integer }, )), _ => expect_kind(value, ValueKind::Integer), } } fn validate_number(value: &Value) -> Result<(), ValidationError> { match value { Value::Number(number) if number.is_finite() => Ok(()), Value::Number(_) => Err(ValidationError::new(ValidationErrorKind::NonFiniteNumber)), _ => expect_kind(value, ValueKind::Number), } } fn validate_record( fields: &[crate::FieldDeclaration], value: &Value, declarations: &HashMap<&str, &TypeDeclaration>, ) -> Result<(), ValidationError> { let Value::Record(values) = value else { return expect_kind(value, ValueKind::Record); }; for (name, field_value) in values { let Some(field) = fields.iter().find(|field| field.name == *name) else { return Err(ValidationError::new(ValidationErrorKind::UnknownField { name: name.clone(), }) .prepend(PathSegment::Field(name.clone()))); }; validate_value(&field.r#type, field_value, declarations) .map_err(|error| error.prepend(PathSegment::Field(name.clone())))?; } for field in fields { if field.required && !values.contains_key(&field.name) { return Err(ValidationError::new(ValidationErrorKind::MissingField { name: field.name.clone(), }) .prepend(PathSegment::Field(field.name.clone()))); } } Ok(()) } fn validate_union( cases: &[crate::UnionCase], value: &Value, declarations: &HashMap<&str, &TypeDeclaration>, ) -> Result<(), ValidationError> { let Value::Record(fields) = value else { return expect_kind(value, ValueKind::Record); }; for name in fields.keys() { if name != UNION_CASE_FIELD && name != UNION_VALUE_FIELD { return Err(ValidationError::new(ValidationErrorKind::UnknownField { name: name.clone(), }) .prepend(PathSegment::Field(name.clone()))); } } let Some(discriminator) = fields.get(UNION_CASE_FIELD) else { return Err(ValidationError::new(ValidationErrorKind::MissingField { name: UNION_CASE_FIELD.into(), }) .prepend(PathSegment::Field(UNION_CASE_FIELD.into()))); }; let Value::String(case_name) = discriminator else { return expect_kind(discriminator, ValueKind::String) .map_err(|error| error.prepend(PathSegment::Field(UNION_CASE_FIELD.into()))); }; let Some(case) = cases.iter().find(|case| case.name == *case_name) else { return Err(ValidationError::new(ValidationErrorKind::UnknownCase { name: case_name.clone(), }) .prepend(PathSegment::Case(case_name.clone()))); }; match (&case.payload, fields.get(UNION_VALUE_FIELD)) { (None, None) => Ok(()), (None, Some(_)) => Err( ValidationError::new(ValidationErrorKind::UnexpectedUnionPayload) .prepend(PathSegment::Field(UNION_VALUE_FIELD.into())), ), (Some(_), None) => Err( ValidationError::new(ValidationErrorKind::MissingUnionPayload) .prepend(PathSegment::Field(UNION_VALUE_FIELD.into())), ), (Some(payload), Some(payload_value)) => { validate_value(payload, payload_value, declarations) .map_err(|error| error.prepend(PathSegment::Field(UNION_VALUE_FIELD.into()))) } } } fn expect_kind(value: &Value, expected: ValueKind) -> Result<(), ValidationError> { if value.kind() == expected { Ok(()) } else { Err(ValidationError::new(ValidationErrorKind::TypeMismatch { expected, actual: value.kind(), })) } } fn validate_json(value: &Value) -> Result<(), ValidationError> { match value { Value::Integer(_) => validate_integer(value), Value::Number(_) => validate_number(value), Value::Bytes(_) => Err(ValidationError::new(ValidationErrorKind::BytesInJson)), Value::Record(fields) => { for (name, field) in fields { validate_json(field) .map_err(|error| error.prepend(PathSegment::Field(name.clone())))?; } Ok(()) } Value::List(items) => { for (index, item) in items.iter().enumerate() { validate_json(item).map_err(|error| error.prepend(PathSegment::Index(index)))?; } Ok(()) } Value::Unit | Value::Boolean(_) | Value::String(_) => Ok(()), } } pub(crate) fn validate_arguments( descriptor: &InterfaceDescriptor, operation_name: &str, arguments: &BTreeMap, ) -> Result<(), ValidationError> { descriptor.validate()?; let operation = find_operation(descriptor, operation_name)?; let declarations = declaration_map(descriptor)?; for (name, value) in arguments { let Some(parameter) = operation .parameters .iter() .find(|parameter| parameter.name == *name) else { return Err(ValidationError::new(ValidationErrorKind::UnknownParameter { name: name.clone(), }) .prepend(PathSegment::Parameter(name.clone())) .prepend(PathSegment::Operation(operation.name.clone())) .prepend(PathSegment::Descriptor)); }; validate_value(¶meter.r#type, value, &declarations).map_err(|error| { error .prepend(PathSegment::Parameter(name.clone())) .prepend(PathSegment::Operation(operation.name.clone())) .prepend(PathSegment::Descriptor) })?; } for parameter in &operation.parameters { if parameter.required && !arguments.contains_key(¶meter.name) { return Err(ValidationError::new(ValidationErrorKind::MissingParameter { name: parameter.name.clone(), }) .prepend(PathSegment::Parameter(parameter.name.clone())) .prepend(PathSegment::Operation(operation.name.clone())) .prepend(PathSegment::Descriptor)); } } Ok(()) } pub(crate) fn validate_result( descriptor: &InterfaceDescriptor, operation_name: &str, result: &Value, ) -> Result<(), ValidationError> { descriptor.validate()?; let operation = find_operation(descriptor, operation_name)?; let declarations = declaration_map(descriptor)?; validate_value(&operation.returns.r#type, result, &declarations).map_err(|error| { error .prepend(PathSegment::Return) .prepend(PathSegment::Operation(operation.name.clone())) .prepend(PathSegment::Descriptor) }) } pub(crate) fn validate_call( descriptor: &InterfaceDescriptor, request: &CallOperationRequest, ) -> Result<(), ValidationError> { validate_path(&request.target.path) .map_err(|error| error.prepend(PathSegment::Field("target".into())))?; validate_arguments(descriptor, &request.operation, &request.arguments) } fn find_operation<'a>( descriptor: &'a InterfaceDescriptor, operation_name: &str, ) -> Result<&'a crate::OperationDeclaration, ValidationError> { descriptor .operations .iter() .find(|operation| operation.name == operation_name) .ok_or_else(|| { ValidationError::new(ValidationErrorKind::UnknownOperation { name: operation_name.to_owned(), }) .prepend(PathSegment::Operation(operation_name.to_owned())) .prepend(PathSegment::Descriptor) }) } pub(crate) fn validate_observe_response( response: &ObjectObservation, request: &ObserveRequest, ) -> Result<(), ValidationError> { request.validate()?; validate_observation_node(response, &request.path, 0, request.depth) } fn validate_observation_node( node: &ObjectObservation, path: &str, depth: u32, maximum_depth: u32, ) -> Result<(), ValidationError> { node.object.validate_at_path(path)?; if depth == maximum_depth { if node.children.is_some() { return Err( ValidationError::new(ValidationErrorKind::ChildrenAtDepthBoundary { depth }) .prepend(PathSegment::Object(path.to_owned())), ); } return Ok(()); } let children = node.children.as_ref().ok_or_else(|| { ValidationError::new(ValidationErrorKind::ChildrenMissingBeforeDepth { depth }) .prepend(PathSegment::Object(path.to_owned())) })?; let mut names = HashSet::new(); for child in children { validate_object_name(&child.object.name, false) .map_err(|error| error.prepend(PathSegment::Child(child.object.name.clone())))?; if !names.insert(child.object.name.as_str()) { return Err(ValidationError::new(ValidationErrorKind::DuplicateName { namespace: NameNamespace::ChildObject, name: child.object.name.clone(), }) .prepend(PathSegment::Child(child.object.name.clone())) .prepend(PathSegment::Object(path.to_owned()))); } let child_path = if path == "/" { format!("/{}", child.object.name) } else { format!("{path}/{}", child.object.name) }; validate_observation_node(child, &child_path, depth + 1, maximum_depth)?; } Ok(()) } pub(crate) fn validate_fetch_interface_response( response: &FetchInterfaceResponse, request: &FetchInterfaceRequest, ) -> Result<(), ValidationError> { if response.interface != request.interface { return Err( ValidationError::new(ValidationErrorKind::InterfaceResponseMismatch { expected: request.interface.clone(), actual: response.interface.clone(), }) .prepend(PathSegment::Field("interface".into())), ); } response.descriptor.validate() }