Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions rust/signed_doc/src/metadata/document_refs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

mod doc_locator;
mod doc_ref;
use std::fmt::Display;
use std::{fmt::Display, ops::Deref};

use catalyst_types::uuid::{CborContext, UuidV7};
use cbork_utils::{array::Array, decode_context::DecodeCtx};
Expand All @@ -17,6 +17,14 @@ use crate::CompatibilityPolicy;
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
pub struct DocumentRefs(Vec<DocumentRef>);

impl Deref for DocumentRefs {
type Target = Vec<DocumentRef>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DocumentRefs {
/// Returns true if provided `cbor` bytes is a valid old format.
/// ```cddl
Expand Down Expand Up @@ -50,14 +58,6 @@ pub enum DocRefError {
HexDecode(String),
}

impl DocumentRefs {
/// Get a list of document reference instance.
#[must_use]
pub fn doc_refs(&self) -> &Vec<DocumentRef> {
&self.0
}
}

impl From<Vec<DocumentRef>> for DocumentRefs {
fn from(value: Vec<DocumentRef>) -> Self {
DocumentRefs(value)
Expand Down
1 change: 0 additions & 1 deletion rust/signed_doc/src/validator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

pub(crate) mod json_schema;
pub(crate) mod rules;
pub(crate) mod utils;

use std::{
collections::HashMap,
Expand Down
108 changes: 98 additions & 10 deletions rust/signed_doc/src/validator/rules/doc_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use catalyst_types::problem_report::ProblemReport;

use crate::{
providers::CatalystSignedDocumentProvider, validator::utils::validate_doc_refs,
CatalystSignedDocument, DocType,
providers::CatalystSignedDocumentProvider, CatalystSignedDocument, DocType, DocumentRef,
DocumentRefs,
};

/// `ref` field validation rule
Expand Down Expand Up @@ -36,11 +36,16 @@ impl RefRule {
optional,
} = self
{
if let Some(doc_ref) = doc.doc_meta().doc_ref() {
let ref_validator = |ref_doc: CatalystSignedDocument| {
referenced_doc_check(&ref_doc, exp_ref_types, "ref", doc.report())
};
return validate_doc_refs(doc_ref, provider, doc.report(), ref_validator).await;
if let Some(doc_refs) = doc.doc_meta().doc_ref() {
return doc_refs_check(
doc_refs,
exp_ref_types,
"ref",
provider,
doc.report(),
|_| true,
)
.await;
} else if !optional {
doc.report()
.missing_field("ref", &format!("{context}, document must have ref field"));
Expand All @@ -62,15 +67,98 @@ impl RefRule {
}
}

/// A generic implementation of the referenced document validation.
pub(crate) fn referenced_doc_check(
/// Validate all the document references by the defined validation rules,
/// plus conducting additional validations with the provided `validator`.
/// Document all possible error in doc report (no fail fast)
pub(crate) async fn doc_refs_check<Provider, Validator>(
doc_refs: &DocumentRefs,
exp_ref_types: &[DocType],
field_name: &str,
provider: &Provider,
report: &ProblemReport,
validator: Validator,
) -> anyhow::Result<bool>
where
Provider: CatalystSignedDocumentProvider,
Validator: Fn(&CatalystSignedDocument) -> bool,
{
let mut all_valid = true;

for dr in doc_refs.iter() {
if let Some(ref ref_doc) = provider.try_get_doc(dr).await? {
let is_valid = referenced_doc_type_check(ref_doc, exp_ref_types, field_name, report)
&& referenced_doc_id_and_ver_check(ref_doc, dr, field_name, report)
&& validator(ref_doc);

if !is_valid {
all_valid = false;
}
} else {
report.functional_validation(
&format!("Cannot retrieve a document {dr}"),
&format!("Referenced document validation for the `{field_name}` field"),
);
all_valid = false;
}
}
Ok(all_valid)
}

/// Validation check that the provided `ref_doc` is a correct referenced document found by
/// `original_doc_ref`
fn referenced_doc_id_and_ver_check(
ref_doc: &CatalystSignedDocument,
original_doc_ref: &DocumentRef,
field_name: &str,
report: &ProblemReport,
) -> bool {
let Ok(id) = ref_doc.doc_id() else {
report.missing_field(
"id",
&format!("Referenced document validation for the `{field_name}` field"),
);
return false;
};

let Ok(ver) = ref_doc.doc_ver() else {
report.missing_field(
"ver",
&format!("Referenced document validation for the `{field_name}` field"),
);
return false;
};

// id and version must match the values in ref doc
if &id != original_doc_ref.id() && &ver != original_doc_ref.ver() {
report.invalid_value(
"id and version",
&format!("id: {id}, ver: {ver}"),
&format!(
"id: {}, ver: {}",
original_doc_ref.id(),
original_doc_ref.ver()
),
&format!("Referenced document validation for the `{field_name}` field"),
);
return false;
}

true
}

/// Validation check that the provided `ref_doc` has an expected `type` field value from
/// the allowed `exp_ref_types` list
fn referenced_doc_type_check(
ref_doc: &CatalystSignedDocument,
exp_ref_types: &[DocType],
field_name: &str,
report: &ProblemReport,
) -> bool {
let Ok(ref_doc_type) = ref_doc.doc_type() else {
report.missing_field("type", "Referenced document must have type field");
report.missing_field(
"type",
&format!("Document reference validation for the `{field_name}` field"),
);
return false;
};

Expand Down
73 changes: 42 additions & 31 deletions rust/signed_doc/src/validator/rules/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use catalyst_types::problem_report::ProblemReport;
use futures::FutureExt;

use crate::{
providers::CatalystSignedDocumentProvider,
validator::{rules::doc_ref::referenced_doc_check, utils::validate_doc_refs},
providers::CatalystSignedDocumentProvider, validator::rules::doc_ref::doc_refs_check,
CatalystSignedDocument, DocType, DocumentRefs,
};

Expand Down Expand Up @@ -41,12 +40,15 @@ impl ParametersRule {
} = self
{
if let Some(parameters_ref) = doc.doc_meta().parameters() {
let parameters_validator = |ref_doc: CatalystSignedDocument| {
referenced_doc_check(&ref_doc, exp_parameters_type, "parameters", doc.report())
};
let parameters_check =
validate_doc_refs(parameters_ref, provider, doc.report(), parameters_validator)
.boxed();
let parameters_check = doc_refs_check(
parameters_ref,
exp_parameters_type,
"parameters",
provider,
doc.report(),
|_| true,
)
.boxed();

let template_link_check = link_check(
doc.doc_meta().template(),
Expand Down Expand Up @@ -125,32 +127,41 @@ where
return Ok(true);
};

let link_validator = |ref_doc: CatalystSignedDocument| {
let Some(ref_doc_parameters) = ref_doc.doc_meta().parameters() else {
report.missing_field(
"parameters",
&format!(
"Referenced document via {field_name} must have `parameters` field. Referenced Document: {ref_doc}"
),
);
return false;
};
let mut all_valid = true;

if exp_parameters != ref_doc_parameters {
report.invalid_value(
"parameters",
&format!("Reference doc param: {ref_doc_parameters}",),
&format!("Doc param: {exp_parameters}"),
&format!(
"Referenced document via {field_name} `parameters` field must match. Referenced Document: {ref_doc}"
),
for dr in ref_field.iter() {
if let Some(ref ref_doc) = provider.try_get_doc(dr).await? {
let Some(ref_doc_parameters) = ref_doc.doc_meta().parameters() else {
report.missing_field(
"parameters",
&format!(
"Referenced document via {field_name} must have `parameters` field. Referenced Document: {ref_doc}"
),
);
all_valid = false;
continue;
};

if exp_parameters != ref_doc_parameters {
report.invalid_value(
"parameters",
&format!("Reference doc param: {ref_doc_parameters}",),
&format!("Doc param: {exp_parameters}"),
&format!(
"Referenced document via {field_name} `parameters` field must match. Referenced Document: {ref_doc}"
),
);
all_valid = false;
}
} else {
report.functional_validation(
&format!("Cannot retrieve a document {dr}"),
&format!("Referenced document link validation for the `{field_name}` field"),
);
return false;
all_valid = false;
}
true
};

validate_doc_refs(ref_field, provider, report, link_validator).await
}
Ok(all_valid)
}

#[cfg(test)]
Expand Down
26 changes: 12 additions & 14 deletions rust/signed_doc/src/validator/rules/reply.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
//! `reply` rule type impl.

use super::doc_ref::referenced_doc_check;
use crate::{
providers::CatalystSignedDocumentProvider, validator::utils::validate_doc_refs,
providers::CatalystSignedDocumentProvider, validator::rules::doc_ref::doc_refs_check,
CatalystSignedDocument, DocType,
};

Expand Down Expand Up @@ -37,17 +36,7 @@ impl ReplyRule {
} = self
{
if let Some(reply_ref) = doc.doc_meta().reply() {
let reply_validator = |ref_doc: CatalystSignedDocument| {
// Validate type
if !referenced_doc_check(
&ref_doc,
std::slice::from_ref(exp_reply_type),
"reply",
doc.report(),
) {
return false;
}

let reply_validator = |ref_doc: &CatalystSignedDocument| {
// Get `ref` from both the doc and the ref doc
let Some(ref_doc_dr) = ref_doc.doc_meta().doc_ref() else {
doc.report()
Expand All @@ -73,7 +62,16 @@ impl ReplyRule {
}
true
};
return validate_doc_refs(reply_ref, provider, doc.report(), reply_validator).await;

return doc_refs_check(
reply_ref,
std::slice::from_ref(exp_reply_type),
"reply",
provider,
doc.report(),
reply_validator,
)
.await;
} else if !optional {
doc.report().missing_field(
"reply",
Expand Down
28 changes: 14 additions & 14 deletions rust/signed_doc/src/validator/rules/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

use std::fmt::Write;

use super::doc_ref::referenced_doc_check;
use crate::{
metadata::ContentType,
providers::CatalystSignedDocumentProvider,
validator::{json_schema, utils::validate_doc_refs},
validator::{json_schema, rules::doc_ref::doc_refs_check},
CatalystSignedDocument, DocType,
};

Expand Down Expand Up @@ -49,15 +48,8 @@ impl ContentRule {
.missing_field("template", &format!("{context}, doc"));
return Ok(false);
};
let template_validator = |template_doc: CatalystSignedDocument| {
if !referenced_doc_check(
&template_doc,
std::slice::from_ref(exp_template_type),
"template",
doc.report(),
) {
return false;
}

let template_validator = |template_doc: &CatalystSignedDocument| {
let Ok(template_content_type) = template_doc.doc_content_type() else {
doc.report().missing_field(
"content-type",
Expand All @@ -66,7 +58,7 @@ impl ContentRule {
return false;
};
match template_content_type {
ContentType::Json => templated_json_schema_check(doc, &template_doc),
ContentType::Json => templated_json_schema_check(doc, template_doc),
ContentType::Cddl
| ContentType::Cbor
| ContentType::JsonSchema
Expand All @@ -83,8 +75,16 @@ impl ContentRule {
},
}
};
return validate_doc_refs(template_ref, provider, doc.report(), template_validator)
.await;

return doc_refs_check(
template_ref,
std::slice::from_ref(exp_template_type),
"template",
provider,
doc.report(),
template_validator,
)
.await;
}
if let Self::Static(content_schema) = self {
if let Some(template) = doc.doc_meta().template() {
Expand Down
Loading
Loading