feat(protocol): add logical model and validation
This commit is contained in:
@@ -0,0 +1,620 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::{
|
||||
InterfaceDescriptor, InterfaceObservation, JsonValue, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER,
|
||||
NameNamespace, Object, OperationCall, PathSegment, TypeDeclaration, TypeExpr, ValidationError,
|
||||
ValidationErrorKind, Value, ValueKind,
|
||||
};
|
||||
|
||||
pub(crate) fn validate_object(object: &Object) -> Result<(), ValidationError> {
|
||||
let mut seen = HashSet::new();
|
||||
for interface in &object.interfaces {
|
||||
if !seen.insert(interface.as_str()) {
|
||||
return Err(
|
||||
ValidationError::new(ValidationErrorKind::DuplicateInterface {
|
||||
name: interface.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Interface(interface.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn validate_observation(
|
||||
observation: &InterfaceObservation,
|
||||
) -> Result<(), ValidationError> {
|
||||
observation.object.validate()?;
|
||||
|
||||
let mut descriptors = HashMap::new();
|
||||
for descriptor in &observation.descriptors {
|
||||
if descriptors
|
||||
.insert(descriptor.name.as_str(), descriptor)
|
||||
.is_some()
|
||||
{
|
||||
return Err(ValidationError::new(ValidationErrorKind::DuplicateName {
|
||||
namespace: NameNamespace::InterfaceDescriptor,
|
||||
name: descriptor.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone())));
|
||||
}
|
||||
descriptor.validate()?;
|
||||
}
|
||||
|
||||
for interface in &observation.object.interfaces {
|
||||
if !descriptors.contains_key(interface.as_str()) {
|
||||
return Err(
|
||||
ValidationError::new(ValidationErrorKind::MissingInterfaceDescriptor {
|
||||
name: interface.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Interface(interface.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let published: HashSet<&str> = observation
|
||||
.object
|
||||
.interfaces
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.collect();
|
||||
for descriptor in &observation.descriptors {
|
||||
if !published.contains(descriptor.name.as_str()) {
|
||||
return Err(
|
||||
ValidationError::new(ValidationErrorKind::UnexpectedInterfaceDescriptor {
|
||||
name: descriptor.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn validate_descriptor(descriptor: &InterfaceDescriptor) -> Result<(), ValidationError> {
|
||||
let declarations = declaration_map(descriptor)?;
|
||||
|
||||
for declaration in &descriptor.declarations {
|
||||
validate_declaration(declaration, &declarations).map_err(|error| {
|
||||
error
|
||||
.prepend(PathSegment::Declaration(declaration.name().to_owned()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone()))
|
||||
})?;
|
||||
}
|
||||
|
||||
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::Interface(descriptor.name.clone())));
|
||||
}
|
||||
|
||||
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::Interface(descriptor.name.clone())));
|
||||
}
|
||||
validate_type_expr(¶meter.ty, &declarations).map_err(|error| {
|
||||
error
|
||||
.prepend(PathSegment::Parameter(parameter.name.clone()))
|
||||
.prepend(PathSegment::Operation(operation.name.clone()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone()))
|
||||
})?;
|
||||
}
|
||||
if let Some(returns) = &operation.returns {
|
||||
validate_type_expr(&returns.ty, &declarations).map_err(|error| {
|
||||
error
|
||||
.prepend(PathSegment::Return)
|
||||
.prepend(PathSegment::Operation(operation.name.clone()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone()))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
validate_no_cycles(descriptor, &declarations)
|
||||
}
|
||||
|
||||
fn declaration_map(
|
||||
descriptor: &InterfaceDescriptor,
|
||||
) -> Result<HashMap<&str, &TypeDeclaration>, ValidationError> {
|
||||
let mut declarations = HashMap::new();
|
||||
for declaration in &descriptor.declarations {
|
||||
if declarations
|
||||
.insert(declaration.name(), declaration)
|
||||
.is_some()
|
||||
{
|
||||
return Err(ValidationError::new(ValidationErrorKind::DuplicateName {
|
||||
namespace: NameNamespace::TypeDeclaration,
|
||||
name: declaration.name().to_owned(),
|
||||
})
|
||||
.prepend(PathSegment::Declaration(declaration.name().to_owned()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone())));
|
||||
}
|
||||
}
|
||||
Ok(declarations)
|
||||
}
|
||||
|
||||
fn validate_declaration(
|
||||
declaration: &TypeDeclaration,
|
||||
declarations: &HashMap<&str, &TypeDeclaration>,
|
||||
) -> Result<(), ValidationError> {
|
||||
match declaration {
|
||||
TypeDeclaration::Record(record) => {
|
||||
let mut names = HashSet::new();
|
||||
for field in &record.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.ty, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::Field(field.name.clone())))?;
|
||||
}
|
||||
}
|
||||
TypeDeclaration::Enum(enumeration) => {
|
||||
let mut names = HashSet::new();
|
||||
for variant in &enumeration.variants {
|
||||
if !names.insert(variant.name.as_str()) {
|
||||
return Err(ValidationError::new(ValidationErrorKind::DuplicateName {
|
||||
namespace: NameNamespace::EnumVariant,
|
||||
name: variant.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Variant(variant.name.clone())));
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDeclaration::Union(union) => {
|
||||
let mut names = HashSet::new();
|
||||
for variant in &union.variants {
|
||||
if !names.insert(variant.name.as_str()) {
|
||||
return Err(ValidationError::new(ValidationErrorKind::DuplicateName {
|
||||
namespace: NameNamespace::UnionVariant,
|
||||
name: variant.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Variant(variant.name.clone())));
|
||||
}
|
||||
validate_type_expr(&variant.ty, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::Variant(variant.name.clone())))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_type_expr(
|
||||
ty: &TypeExpr,
|
||||
declarations: &HashMap<&str, &TypeDeclaration>,
|
||||
) -> Result<(), ValidationError> {
|
||||
match ty {
|
||||
TypeExpr::List(item) => validate_type_expr(item, declarations),
|
||||
TypeExpr::Entry { key, value } => {
|
||||
validate_type_expr(key, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::EntryKey))?;
|
||||
validate_type_expr(value, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::EntryValue))
|
||||
}
|
||||
TypeExpr::Named(name) if !declarations.contains_key(name.as_str()) => {
|
||||
Err(ValidationError::new(ValidationErrorKind::UnknownType {
|
||||
name: name.clone(),
|
||||
}))
|
||||
}
|
||||
_ => 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.declarations {
|
||||
visit_declaration(
|
||||
declaration.name(),
|
||||
descriptor,
|
||||
declarations,
|
||||
&mut state,
|
||||
&mut stack,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum VisitState {
|
||||
Visiting,
|
||||
Visited,
|
||||
}
|
||||
|
||||
fn visit_declaration<'a>(
|
||||
name: &'a str,
|
||||
descriptor: &InterfaceDescriptor,
|
||||
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::Interface(descriptor.name.clone())),
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
state.insert(name, VisitState::Visiting);
|
||||
stack.push(name);
|
||||
let mut references = Vec::new();
|
||||
collect_declaration_references(declarations[name], &mut references);
|
||||
for reference in references {
|
||||
visit_declaration(reference, descriptor, declarations, state, stack)?;
|
||||
}
|
||||
stack.pop();
|
||||
state.insert(name, VisitState::Visited);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_declaration_references<'a>(
|
||||
declaration: &'a TypeDeclaration,
|
||||
references: &mut Vec<&'a str>,
|
||||
) {
|
||||
match declaration {
|
||||
TypeDeclaration::Record(record) => {
|
||||
for field in &record.fields {
|
||||
collect_type_references(&field.ty, references);
|
||||
}
|
||||
}
|
||||
TypeDeclaration::Enum(_) => {}
|
||||
TypeDeclaration::Union(union) => {
|
||||
for variant in &union.variants {
|
||||
collect_type_references(&variant.ty, references);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_type_references<'a>(ty: &'a TypeExpr, references: &mut Vec<&'a str>) {
|
||||
match ty {
|
||||
TypeExpr::List(item) => collect_type_references(item, references),
|
||||
TypeExpr::Entry { key, value } => {
|
||||
collect_type_references(key, references);
|
||||
collect_type_references(value, references);
|
||||
}
|
||||
TypeExpr::Named(name) => references.push(name),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_root_value(
|
||||
descriptor: &InterfaceDescriptor,
|
||||
ty: &TypeExpr,
|
||||
value: &Value,
|
||||
) -> Result<(), ValidationError> {
|
||||
descriptor.validate()?;
|
||||
let declarations = declaration_map(descriptor)?;
|
||||
validate_value(ty, value, &declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::Interface(descriptor.name.clone())))
|
||||
}
|
||||
|
||||
fn validate_value(
|
||||
ty: &TypeExpr,
|
||||
value: &Value,
|
||||
declarations: &HashMap<&str, &TypeDeclaration>,
|
||||
) -> Result<(), ValidationError> {
|
||||
match ty {
|
||||
TypeExpr::String => expect_kind(value, ValueKind::String),
|
||||
TypeExpr::Boolean => expect_kind(value, ValueKind::Boolean),
|
||||
TypeExpr::Integer => 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),
|
||||
},
|
||||
TypeExpr::Number => match value {
|
||||
Value::Number(number) if number.is_finite() => Ok(()),
|
||||
Value::Number(_) => Err(ValidationError::new(ValidationErrorKind::NonFiniteNumber)),
|
||||
_ => expect_kind(value, ValueKind::Number),
|
||||
},
|
||||
TypeExpr::Bytes => expect_kind(value, ValueKind::Bytes),
|
||||
TypeExpr::List(item_type) => match value {
|
||||
Value::List(items) => {
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
validate_value(item_type, item, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::Index(index)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => expect_kind(value, ValueKind::List),
|
||||
},
|
||||
TypeExpr::Entry {
|
||||
key: key_type,
|
||||
value: value_type,
|
||||
} => match value {
|
||||
Value::Entry { key, value } => {
|
||||
validate_value(key_type, key, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::EntryKey))?;
|
||||
validate_value(value_type, value, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::EntryValue))
|
||||
}
|
||||
_ => expect_kind(value, ValueKind::Entry),
|
||||
},
|
||||
TypeExpr::Json => match value {
|
||||
Value::Json(json) => validate_json(json),
|
||||
_ => expect_kind(value, ValueKind::Json),
|
||||
},
|
||||
TypeExpr::Named(name) => {
|
||||
validate_declared_value(declarations[name.as_str()], value, declarations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_declared_value(
|
||||
declaration: &TypeDeclaration,
|
||||
value: &Value,
|
||||
declarations: &HashMap<&str, &TypeDeclaration>,
|
||||
) -> Result<(), ValidationError> {
|
||||
match declaration {
|
||||
TypeDeclaration::Record(record) => match value {
|
||||
Value::Record(fields) => {
|
||||
let mut supplied = HashMap::new();
|
||||
for field in fields {
|
||||
if supplied.insert(field.name.as_str(), &field.value).is_some() {
|
||||
return Err(ValidationError::new(ValidationErrorKind::DuplicateName {
|
||||
namespace: NameNamespace::RecordValueField,
|
||||
name: field.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Field(field.name.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
for field in fields {
|
||||
let Some(declared) = record
|
||||
.fields
|
||||
.iter()
|
||||
.find(|declared| declared.name == field.name)
|
||||
else {
|
||||
return Err(ValidationError::new(ValidationErrorKind::UnknownField {
|
||||
name: field.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Field(field.name.clone())));
|
||||
};
|
||||
validate_value(&declared.ty, &field.value, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::Field(field.name.clone())))?;
|
||||
}
|
||||
|
||||
for declared in &record.fields {
|
||||
if !declared.optional && !supplied.contains_key(declared.name.as_str()) {
|
||||
return Err(ValidationError::new(ValidationErrorKind::MissingField {
|
||||
name: declared.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Field(declared.name.clone())));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => expect_kind(value, ValueKind::Record),
|
||||
},
|
||||
TypeDeclaration::Enum(enumeration) => match value {
|
||||
Value::Enum(variant)
|
||||
if enumeration
|
||||
.variants
|
||||
.iter()
|
||||
.any(|declared| declared.name == *variant) =>
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
Value::Enum(variant) => {
|
||||
Err(ValidationError::new(ValidationErrorKind::UnknownVariant {
|
||||
name: variant.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Variant(variant.clone())))
|
||||
}
|
||||
_ => expect_kind(value, ValueKind::Enum),
|
||||
},
|
||||
TypeDeclaration::Union(union) => match value {
|
||||
Value::Union { variant, value } => {
|
||||
let Some(declared) = union
|
||||
.variants
|
||||
.iter()
|
||||
.find(|declared| declared.name == *variant)
|
||||
else {
|
||||
return Err(ValidationError::new(ValidationErrorKind::UnknownVariant {
|
||||
name: variant.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Variant(variant.clone())));
|
||||
};
|
||||
validate_value(&declared.ty, value, declarations)
|
||||
.map_err(|error| error.prepend(PathSegment::Variant(variant.clone())))
|
||||
}
|
||||
_ => expect_kind(value, ValueKind::Union),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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: &JsonValue) -> Result<(), ValidationError> {
|
||||
match value {
|
||||
JsonValue::Integer(integer) if !(MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(integer) => {
|
||||
Err(ValidationError::new(
|
||||
ValidationErrorKind::IntegerOutOfRange { value: *integer },
|
||||
))
|
||||
}
|
||||
JsonValue::Number(number) if !number.is_finite() => {
|
||||
Err(ValidationError::new(ValidationErrorKind::NonFiniteNumber))
|
||||
}
|
||||
JsonValue::Array(items) => {
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
validate_json(item).map_err(|error| error.prepend(PathSegment::Index(index)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
JsonValue::Object(members) => {
|
||||
let mut names = HashSet::new();
|
||||
for member in members {
|
||||
if !names.insert(member.name.as_str()) {
|
||||
return Err(ValidationError::new(ValidationErrorKind::DuplicateName {
|
||||
namespace: NameNamespace::JsonMember,
|
||||
name: member.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::JsonMember(member.name.clone())));
|
||||
}
|
||||
validate_json(&member.value)
|
||||
.map_err(|error| error.prepend(PathSegment::JsonMember(member.name.clone())))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_call(
|
||||
descriptor: &InterfaceDescriptor,
|
||||
call: &OperationCall,
|
||||
) -> Result<(), ValidationError> {
|
||||
descriptor.validate()?;
|
||||
if call.target.interface != descriptor.name {
|
||||
return Err(
|
||||
ValidationError::new(ValidationErrorKind::InterfaceMismatch {
|
||||
expected: descriptor.name.clone(),
|
||||
actual: call.target.interface.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Interface(call.target.interface.clone())),
|
||||
);
|
||||
}
|
||||
let Some(operation) = descriptor
|
||||
.operations
|
||||
.iter()
|
||||
.find(|operation| operation.name == call.target.operation)
|
||||
else {
|
||||
return Err(ValidationError::new(ValidationErrorKind::UnknownOperation {
|
||||
name: call.target.operation.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Operation(call.target.operation.clone()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone())));
|
||||
};
|
||||
|
||||
let mut supplied = HashMap::new();
|
||||
for argument in &call.arguments {
|
||||
if supplied
|
||||
.insert(argument.name.as_str(), &argument.value)
|
||||
.is_some()
|
||||
{
|
||||
return Err(ValidationError::new(ValidationErrorKind::DuplicateName {
|
||||
namespace: NameNamespace::Argument,
|
||||
name: argument.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Parameter(argument.name.clone()))
|
||||
.prepend(PathSegment::Operation(operation.name.clone()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
let declarations = declaration_map(descriptor)?;
|
||||
for argument in &call.arguments {
|
||||
let Some(parameter) = operation
|
||||
.parameters
|
||||
.iter()
|
||||
.find(|parameter| parameter.name == argument.name)
|
||||
else {
|
||||
return Err(ValidationError::new(ValidationErrorKind::UnknownParameter {
|
||||
name: argument.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Parameter(argument.name.clone()))
|
||||
.prepend(PathSegment::Operation(operation.name.clone()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone())));
|
||||
};
|
||||
validate_value(¶meter.ty, &argument.value, &declarations).map_err(|error| {
|
||||
error
|
||||
.prepend(PathSegment::Parameter(argument.name.clone()))
|
||||
.prepend(PathSegment::Operation(operation.name.clone()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone()))
|
||||
})?;
|
||||
}
|
||||
|
||||
for parameter in &operation.parameters {
|
||||
if !parameter.optional && !supplied.contains_key(parameter.name.as_str()) {
|
||||
return Err(ValidationError::new(ValidationErrorKind::MissingParameter {
|
||||
name: parameter.name.clone(),
|
||||
})
|
||||
.prepend(PathSegment::Parameter(parameter.name.clone()))
|
||||
.prepend(PathSegment::Operation(operation.name.clone()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone())));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn validate_return(
|
||||
descriptor: &InterfaceDescriptor,
|
||||
operation_name: &str,
|
||||
value: Option<&Value>,
|
||||
) -> Result<(), ValidationError> {
|
||||
descriptor.validate()?;
|
||||
let Some(operation) = descriptor
|
||||
.operations
|
||||
.iter()
|
||||
.find(|operation| operation.name == operation_name)
|
||||
else {
|
||||
return Err(ValidationError::new(ValidationErrorKind::UnknownOperation {
|
||||
name: operation_name.to_owned(),
|
||||
})
|
||||
.prepend(PathSegment::Operation(operation_name.to_owned()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone())));
|
||||
};
|
||||
|
||||
match (&operation.returns, value) {
|
||||
(None, None) => Ok(()),
|
||||
(None, Some(_)) => Err(ValidationError::new(
|
||||
ValidationErrorKind::UnexpectedReturnValue,
|
||||
)),
|
||||
(Some(_), None) => Err(ValidationError::new(
|
||||
ValidationErrorKind::MissingReturnValue,
|
||||
)),
|
||||
(Some(returns), Some(value)) => {
|
||||
let declarations = declaration_map(descriptor)?;
|
||||
validate_value(&returns.ty, value, &declarations)
|
||||
}
|
||||
}
|
||||
.map_err(|error| {
|
||||
error
|
||||
.prepend(PathSegment::Return)
|
||||
.prepend(PathSegment::Operation(operation.name.clone()))
|
||||
.prepend(PathSegment::Interface(descriptor.name.clone()))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user