use edifact_rs::{
ProfileRulePack, ValidationContext, ValidationIssue, ValidationLayer, ValidationReport,
ValidationRuleContext, ValidationSeverity, Validator, from_bytes,
};
#[derive(Debug, PartialEq, Eq)]
enum OrdersViolation {
UnsupportedFunctionCode { code: String },
MissingPoReference,
}
fn build_orders_pack() -> ProfileRulePack {
let function_code_pack = ProfileRulePack::new("ORDERS-FUNCTION-CODE")
.for_message_type("ORDERS")
.with_stateless_rule_fn(|segments, issues| {
issues.extend((|| -> Option<ValidationIssue> {
let bgm = segments.iter().find(|s| s.tag == "BGM")?;
let func = bgm.get_element(2)?.get_component(0)?;
(!matches!(func, "9" | "1")).then(|| {
ValidationIssue::new(
ValidationSeverity::Error,
format!("unsupported BGM function code '{func}'"),
)
.with_rule_id("ORDERS-P001-FUNC")
.with_segment("BGM")
.with_element_index(2)
.with_suggestion("Use function code 9 (original) or 1 (cancellation)")
})
})());
});
let reference_pack = ProfileRulePack::new("ORDERS-PO-REF")
.for_message_type("ORDERS")
.with_stateless_rule_fn(|segments, issues| {
issues.extend((|| -> Option<ValidationIssue> {
let bgm = segments.iter().find(|s| s.tag == "BGM")?;
let reference = bgm.get_element(1)?.get_component(0)?;
reference.is_empty().then(|| {
ValidationIssue::new(
ValidationSeverity::Error,
"BGM purchase-order reference is empty",
)
.with_rule_id("ORDERS-P002-REF")
.with_segment("BGM")
.with_element_index(1)
.with_suggestion("Populate BGM element 1 with the buyer's PO reference number")
})
})());
});
ProfileRulePack::new("ORDERS-COMBINED")
.merge_with_override(function_code_pack)
.expect("compatible packs")
.merge_with_override(reference_pack)
.expect("compatible packs")
}
fn extract_violations(report: &ValidationReport) -> Vec<OrdersViolation> {
let mut violations = Vec::new();
for issue in report.filter_by_rule_prefix("ORDERS-P001").iter_issues() {
let code = issue.message.split('\'').nth(1).unwrap_or("?").to_owned();
violations.push(OrdersViolation::UnsupportedFunctionCode { code });
}
if report
.issues_for_rule_id("ORDERS-P002-REF")
.next()
.is_some()
{
violations.push(OrdersViolation::MissingPoReference);
}
violations
}
struct MaxSegmentValidator {
limit: usize,
}
impl Validator for MaxSegmentValidator {
fn validate_batch(
&self,
segments: &[edifact_rs::Segment<'_>],
report: &mut ValidationReport,
_context: &ValidationRuleContext<'_>,
) {
let count = segments.len();
if count > self.limit {
report.add_warning(
ValidationIssue::new(
ValidationSeverity::Warning,
format!(
"message has {count} segments; agreed maximum is {}",
self.limit
),
)
.with_rule_id("ORDERS-P099-SEGCOUNT")
.with_suggestion(format!(
"Review message structure — maximum agreed count is {}",
self.limit
)),
);
}
}
}
fn main() -> Result<(), edifact_rs::EdifactError> {
let valid_input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'UNT+3+1'";
let valid_segments: Vec<_> = from_bytes(valid_input).collect::<Result<_, _>>()?;
let report = ValidationContext::builder()
.with_profile_pack(build_orders_pack())
.with_validator(ValidationLayer::Profile, MaxSegmentValidator { limit: 100 })
.build()
.validate_lenient(&valid_segments);
assert!(
!report.has_errors(),
"valid message should produce no errors"
);
println!("valid message: no violations");
let bad_func_input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-002+5'UNT+3+1'";
let bad_func_segments: Vec<_> = from_bytes(bad_func_input).collect::<Result<_, _>>()?;
let bad_func_report = ValidationContext::builder()
.with_profile_pack(build_orders_pack())
.build()
.validate_lenient(&bad_func_segments);
let violations = extract_violations(&bad_func_report);
println!("function-code violations: {violations:?}");
assert_eq!(
violations,
vec![OrdersViolation::UnsupportedFunctionCode {
code: "5".to_owned()
}]
);
let orders_issues = bad_func_report.filter_by_rule_prefix("ORDERS-");
println!(
"ORDERS-scoped issues: {} | total: {}",
orders_issues.total_issues(),
bad_func_report.total_issues()
);
let tiny_limit_report = ValidationContext::builder()
.with_profile_pack(build_orders_pack())
.with_validator(ValidationLayer::Profile, MaxSegmentValidator { limit: 2 })
.build()
.validate_lenient(&valid_segments);
assert!(
tiny_limit_report.has_warnings(),
"expected a segment-count warning"
);
let seg_count_issues: Vec<_> = tiny_limit_report
.issues_for_rule_id("ORDERS-P099-SEGCOUNT")
.collect();
println!("segment-count issue: {}", seg_count_issues[0].message);
println!("All profile error-mapping examples passed.");
Ok(())
}