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
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ Traceback
txmonitor
txns
typenum
uncategorized
unfinalized
unixfs
unlinkat
Expand Down
2 changes: 1 addition & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ members = [
"cbork-cddl-parser",
"cbork-utils",
"catalyst-voting",
"catalyst-voting",
"catalyst-types",
"immutable-ledger",
"vote-tx-v1",
"vote-tx-v2",
Expand Down
3 changes: 2 additions & 1 deletion rust/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ COPY_SRC:
Cargo.toml clippy.toml deny.toml rustfmt.toml \
.cargo .config \
c509-certificate \
catalyst-types \
cardano-blockchain-types \
cardano-chain-follower \
catalyst-voting vote-tx-v1 vote-tx-v2 \
Expand Down Expand Up @@ -55,7 +56,7 @@ build:
DO rust-ci+EXECUTE \
--cmd="/scripts/std_build.py" \
--args1="--libs=c509-certificate --libs=cardano-blockchain-types --libs=cardano-chain-follower --libs=hermes-ipfs" \
--args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser --libs=cbork-utils" \
--args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser --libs=cbork-utils --libs=catalyst-types" \
--args3="--libs=catalyst-voting --libs=immutable-ledger --libs=vote-tx-v1 --libs=vote-tx-v2" \
--args4="--bins=cbork/cbork --libs=rbac-registration --libs=signed_doc" \
--args5="--cov_report=$HOME/build/coverage-report.info" \
Expand Down
3 changes: 2 additions & 1 deletion rust/catalyst-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ workspace = true
name = "catalyst_types"

[dependencies]
anyhow = "1.0.89"
orx-concurrent-vec = "3.1.0"
serde = { version = "1.0.217", features = ["derive"] }
3 changes: 3 additions & 0 deletions rust/catalyst-types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Catalyst Generic Types

pub mod problem_report;
339 changes: 339 additions & 0 deletions rust/catalyst-types/src/problem_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
//! Problem Report type
//!
//! Problem reports are "soft errors" that indicate an issue with the type that holds
//! them. They are not "hard errors" that prevent processing, but are intended to capture
//! a list of issues related that may be fixed by the user.

use std::sync::Arc;

use orx_concurrent_vec::ConcurrentVec;
use serde::{ser::SerializeSeq, Serialize};

/// The kind of problem being reported
#[derive(Serialize, Clone)]
#[serde(tag = "type")]
enum Kind {
/// Expected and Required field is missing
MissingField {
/// Name of the missing field
field: String,
},
/// Unknown and unexpected field was detected
UnknownField {
/// field name
field: String,
/// the value of the field
value: String,
},
/// Expected Field contains invalid value (Field Name, Found Value, Constraints)
InvalidValue {
/// Name of the field with an invalid value
field: String,
/// The detected invalid value
value: String,
/// The constraint of what is expected for a valid value
constraint: String,
},
/// Expected Field was encoded incorrectly
InvalidEncoding {
/// Name of the invalidly encoded field
field: String,
/// Detected encoding
encoded: String,
/// Expected encoding
expected: String,
},
/// Problem with functional validation, typically cross field validation
FunctionalValidation {
/// Explanation of the failed or problematic validation
explanation: String,
},
/// An uncategorized problem was encountered. Use only for rare problems, otherwise
/// make a new problem kind.
Other {
/// A description of the problem
description: String,
},
}

/// Problem Report Entry
#[derive(Serialize, Clone)]
struct Entry {
/// The kind of problem we are recording.
kind: Kind,
/// Any extra context information we want to add.
context: String,
}

/// The Problem Report list
#[derive(Clone)]
struct Report(ConcurrentVec<Entry>);

impl Serialize for Report {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for e in self.0.iter_cloned() {
seq.serialize_element(&e)?;
}
seq.end()
}
}

/// Problem Report
#[derive(Clone, Serialize)]
pub struct ProblemReport {
/// What context does the whole report have
context: Arc<String>,
/// The report itself
// Note, we use this because it allows:
// 1. Cheap copy of this struct.
// 2. Ergonomic Inner mutability.
// 3. Safety for the Problem Report to be used across threads
report: Report,
}

impl ProblemReport {
/// Creates a new `ProblemReport` with the given context string.
///
/// # Arguments
/// * `context`: A reference to a string slice that is used as the context for the
/// problem report.
///
/// # Returns
/// A new instance of `ProblemReport`.
///
/// # Examples
/// ```rust
/// let report = ProblemReport::new("RBAC Registration Decoding");
/// ```
#[must_use]
pub fn new(context: &str) -> Self {
Self {
context: Arc::new(context.to_string()),
report: Report(ConcurrentVec::new()),
}
}

/// Determines if the problem report contains any issues.
///
/// This method checks whether there are any problems recorded in the report by
/// examining the length of the internal `report` field. If the report is empty,
/// it returns `false`, indicating that there are no problems. Otherwise, it
/// returns `true`.
///
/// # Returns
/// A boolean value:
/// - `true` if the problem report contains one or more issues.
/// - `false` if the problem report is empty and has no issues.
///
/// # Examples
/// ```rust
/// let report = ProblemReport::new("Example context");
/// assert_eq!(report.problematic(), false); // Initially, there are no problems.
/// ```
#[must_use]
pub fn problematic(&self) -> bool {
!self.report.0.is_empty()
}

/// Add an entry to the report
fn add_entry(&self, kind: Kind, context: &str) {
self.report.0.push(Entry {
kind,
context: context.to_owned(),
});
}

/// Report that a field was missing in the problem report.
///
/// This method adds an entry to the problem report indicating that a specified field
/// is absent, along with any additional context provided.
///
/// # Arguments
///
/// * `field_name`: A string slice representing the name of the missing field.
/// * `context`: A string slice providing additional context or information about
/// where and why this field is missing.
///
/// # Example
///
/// ```rust
/// // Assuming you have a ProblemReport instance `report`
/// report.missing_field("name", "In the JSON payload for user creation");
/// ```
pub fn missing_field(&self, field_name: &str, context: &str) {
self.add_entry(
Kind::MissingField {
field: field_name.to_owned(),
},
context,
);
}

/// Reports that an unknown and unexpected field was encountered in the problem
/// report.
///
/// This method adds an entry to the problem report indicating that a specified field
/// was found but is not recognized or expected, along with its value and any
/// additional context provided.
///
/// # Arguments
///
/// * `field_name`: A string slice representing the name of the unknown field.
/// * `value`: A string slice representing the value of the unknown field.
/// * `context`: A string slice providing additional context or information about
/// where and why this field is unexpected.
///
/// # Example
///
/// ```rust
/// // Assuming you have a ProblemReport instance `report`
/// report.unknown_field(
/// "unsupported_option",
/// "true",
/// "In the JSON configuration file",
/// );
/// ```
pub fn unknown_field(&self, field_name: &str, value: &str, context: &str) {
self.add_entry(
Kind::UnknownField {
field: field_name.to_owned(),
value: value.to_owned(),
},
context,
);
}

/// Reports that a field has an invalid value in the problem report.
///
/// This method adds an entry to the problem report indicating that a specified field
/// contains a value which does not meet the required constraints, along with any
/// additional context provided.
///
/// # Arguments
///
/// * `field_name`: A string slice representing the name of the field with the invalid
/// value.
/// * `found`: A string slice representing the actual value found in the field that is
/// deemed invalid.
/// * `constraint`: A string slice representing the constraint or expected format for
/// the field's value.
/// * `context`: A string slice providing additional context or information about
/// where and why this field has an invalid value.
///
/// # Example
///
/// ```rust
/// // Assuming you have a ProblemReport instance `report`
/// report.invalid_value(
/// "age",
/// "300",
/// "must be between 18 and 99",
/// "During user registration",
/// );
/// ```
pub fn invalid_value(&self, field_name: &str, found: &str, constraint: &str, context: &str) {
self.add_entry(
Kind::InvalidValue {
field: field_name.to_owned(),
value: found.to_owned(),
constraint: constraint.to_owned(),
},
context,
);
}

/// Reports that a field has an invalid encoding in the problem report.
///
/// This method adds an entry to the problem report indicating that a specified field
/// contains data which is encoded using a format that does not match the expected or
/// required encoding, along with any additional context provided.
///
/// # Arguments
///
/// * `field_name`: A string slice representing the name of the field with the invalid
/// encoding.
/// * `detected_encoding`: A string slice representing the detected encoding of the
/// data in the field.
/// * `expected_encoding`: A string slice representing the expected or required
/// encoding for the field's data.
/// * `context`: A string slice providing additional context or information about
/// where and why this field has an invalid encoding.
///
/// # Example
///
/// ```rust
/// // Assuming you have a ProblemReport instance `report`
/// report.invalid_encoding("data", "UTF-8", "ASCII", "During data import");
/// ```
pub fn invalid_encoding(
&self, field_name: &str, detected_encoding: &str, expected_encoding: &str, context: &str,
) {
self.add_entry(
Kind::InvalidEncoding {
field: field_name.to_owned(),
encoded: detected_encoding.to_owned(),
expected: expected_encoding.to_owned(),
},
context,
);
}

/// Reports an invalid validation or cross-field validation error in the problem
/// report.
///
/// This method adds an entry to the problem report indicating that there is a
/// functional validation issue, typically involving multiple fields or data points
/// not meeting specific validation criteria, along with any additional context
/// provided.
///
/// # Arguments
///
/// * `explanation`: A string slice providing a detailed explanation of why the
/// validation failed.
/// * `context`: A string slice providing additional context or information about
/// where and why this functional validation error occurred.
///
/// # Example
///
/// ```rust
/// // Assuming you have a ProblemReport instance `report`
/// report.functional_validation(
/// "End date cannot be before start date",
/// "During contract creation",
/// );
/// ```
pub fn functional_validation(&self, explanation: &str, context: &str) {
self.add_entry(
Kind::FunctionalValidation {
explanation: explanation.to_owned(),
},
context,
);
}

/// Reports an uncategorized problem with the given description and context.
///
/// This method is intended for use in rare situations where a specific type of
/// problem has not been identified or defined. Using this method frequently can
/// lead to disorganized reporting and difficulty in analyzing problems. For
/// better clarity and organization, consider creating more distinct categories of
/// problems to report using methods that specifically handle those types (e.g.,
/// `other_problem`, `technical_issue`, etc.).
///
/// # Parameters:
/// - `description`: A brief description of the problem. This should clearly convey
/// what went wrong or what caused the issue.
/// - `context`: Additional information that might help in understanding the context
/// or environment where the problem occurred. This could include details about the
/// system, user actions, or any other relevant data.
pub fn other(&self, description: &str, context: &str) {
self.add_entry(
Kind::Other {
description: description.to_owned(),
},
context,
);
}
}
Loading