674 lines
23 KiB
Rust
674 lines
23 KiB
Rust
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<HashMap<&str, &TypeDeclaration>, 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<Item = &'a str>,
|
|
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<String> = 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<String, Value>,
|
|
) -> 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()
|
|
}
|