///|
pub(all) enum ValidationSeverity {
ValidationWarning
ValidationError
} derive(Eq, Debug)
///|
pub(all) struct ValidationIssue {
code : String
message : String
severity : ValidationSeverity
} derive(Eq, Debug)
///|
pub(all) struct EventValidationPolicy {
require_topic : Bool
require_correlation_id : Bool
allow_empty_tags : Bool
min_priority : Int
max_priority : Int
max_payload_length : Int
} derive(Eq, Debug)
///|
pub(all) struct EventValidation {
event : Event
issues : Array[ValidationIssue]
} derive(Eq, Debug)
///|
pub(all) struct ValidationBatch {
results : Array[EventValidation]
valid : Int
invalid : Int
warnings : Int
errors : Int
} derive(Eq, Debug)
///|
pub fn EventValidationPolicy::default() -> EventValidationPolicy {
{
require_topic: false,
require_correlation_id: false,
allow_empty_tags: false,
min_priority: 0,
max_priority: 100,
max_payload_length: 0,
}
}
///|
fn validation_issue(
code : String,
message : String,
severity : ValidationSeverity,
) -> ValidationIssue {
{ code, message, severity }
}
///|
fn has_duplicate_tags(tags : Array[String]) -> Bool {
let seen : Array[String] = []
for tag in tags {
if seen.contains(tag) {
return true
}
seen.push(tag)
}
false
}
///|
pub fn validate_event(
event : Event,
policy? : EventValidationPolicy = EventValidationPolicy::default(),
) -> EventValidation {
let issues : Array[ValidationIssue] = []
if event.name == "" {
issues.push(
validation_issue(
"event.name.empty",
"event name must not be empty",
ValidationError,
),
)
}
if policy.require_topic && event.topic == "" {
issues.push(
validation_issue(
"event.topic.required",
"topic is required",
ValidationError,
),
)
}
if event.topic != "" &&
(
validate_topic_pattern(event.topic) != PatternValid ||
event.topic.contains("*") ||
event.topic.contains("#")
) {
issues.push(
validation_issue(
"event.topic.invalid",
"topic contains empty or wildcard segments",
ValidationError,
),
)
}
if policy.require_correlation_id && event.correlation_id == "" {
issues.push(
validation_issue(
"event.correlation.required",
"correlation id is required",
ValidationError,
),
)
}
if event.priority < policy.min_priority ||
event.priority > policy.max_priority {
issues.push(
validation_issue(
"event.priority.range",
"priority is outside the accepted range",
ValidationError,
),
)
}
if policy.max_payload_length > 0 &&
event.payload.length() > policy.max_payload_length {
issues.push(
validation_issue(
"event.payload.too_large",
"payload exceeds configured length",
ValidationError,
),
)
}
if !policy.allow_empty_tags && event.tags.contains("") {
issues.push(
validation_issue(
"event.tag.empty",
"empty tags are not allowed",
ValidationWarning,
),
)
}
if has_duplicate_tags(event.tags) {
issues.push(
validation_issue(
"event.tag.duplicate",
"duplicate tags should be removed",
ValidationWarning,
),
)
}
{ event, issues }
}
///|
pub fn EventValidation::is_valid(self : EventValidation) -> Bool {
for issue in self.issues {
if issue.severity == ValidationError {
return false
}
}
true
}
///|
pub fn EventValidation::error_count(self : EventValidation) -> Int {
let mut count = 0
for issue in self.issues {
if issue.severity == ValidationError {
count = count + 1
}
}
count
}
///|
pub fn EventValidation::warning_count(self : EventValidation) -> Int {
self.issues.length() - self.error_count()
}
///|
pub fn validate_events(
events : Array[Event],
policy? : EventValidationPolicy = EventValidationPolicy::default(),
) -> ValidationBatch {
let results : Array[EventValidation] = []
let mut valid = 0
let mut invalid = 0
let mut warnings = 0
let mut errors = 0
for event in events {
let result = validate_event(event, policy~)
if result.is_valid() {
valid = valid + 1
} else {
invalid = invalid + 1
}
warnings = warnings + result.warning_count()
errors = errors + result.error_count()
results.push(result)
}
{ results, valid, invalid, warnings, errors }
}
///|
pub fn validation_middleware(policy : EventValidationPolicy) -> MiddlewareFn {
(event, _) => {
let result = validate_event(event, policy~)
if result.is_valid() {
MiddlewareContinue(event)
} else {
MiddlewareHalt(
"event validation failed with " +
result.error_count().to_string() +
" error(s)",
)
}
}
}