///|
pub enum Severity {
Error
Warning
} derive(Debug, Eq)
///|
pub struct ValidationIssue {
severity : Severity
path : String
message : String
} derive(Debug, Eq)
///|
pub struct ValidationReport {
ok : Bool
issues : Array[ValidationIssue]
} derive(Debug, Eq)
///|
pub struct LogisticsProfile {
has_sscc : Bool
has_gtin : Bool
has_lot : Bool
has_serial : Bool
has_expiration : Bool
} derive(Debug, Eq)
///|
pub fn GS1Message::validate(self : GS1Message) -> ValidationReport {
let issues : Array[ValidationIssue] = []
for idx, elem in self.elements {
if !elem.is_valid() {
issues.push({
severity: Error,
path: elem.code(),
message: "AI \{elem.code()} value does not match \{elem.title()} constraints",
})
}
if is_date_ai(elem.code()) && !is_valid_yymmdd(elem.value) {
issues.push({
severity: Error,
path: elem.code(),
message: "AI \{elem.code()} date must use YYMMDD with a valid month and day",
})
}
if has_previous_code(self.elements, idx, elem.code()) {
issues.push({
severity: Warning,
path: elem.code(),
message: "AI \{elem.code()} appears more than once",
})
}
}
{ ok: issues.is_empty(), issues }
}
///|
pub fn GS1Message::profile(self : GS1Message) -> LogisticsProfile {
let mut has_sscc = false
let mut has_gtin = false
let mut has_lot = false
let mut has_serial = false
let mut has_expiration = false
for elem in self.elements {
match elem.code() {
"00" => has_sscc = true
"01" | "02" => has_gtin = true
"10" => has_lot = true
"21" => has_serial = true
"17" => has_expiration = true
_ => ()
}
}
{ has_sscc, has_gtin, has_lot, has_serial, has_expiration }
}
///|
fn is_date_ai(code : String) -> Bool {
code == "11" ||
code == "12" ||
code == "13" ||
code == "15" ||
code == "16" ||
code == "17"
}
///|
fn is_valid_yymmdd(value : String) -> Bool {
if value.length() != 6 || !value.all(fn(ch) { ch.is_ascii_digit() }) {
false
} else {
let month = two_digits(value, 2)
let day = two_digits(value, 4)
month >= 1 && month <= 12 && day >= 1 && day <= days_in_month(month)
}
}
///|
fn two_digits(value : String, pos : Int) -> Int {
(value[pos].to_int() - ('0' : UInt16).to_int()) * 10 +
value[pos + 1].to_int() -
('0' : UInt16).to_int()
}
///|
fn days_in_month(month : Int) -> Int {
match month {
2 => 29
4 | 6 | 9 | 11 => 30
_ => 31
}
}
///|
fn has_previous_code(
elements : Array[GS1Element],
idx : Int,
code : String,
) -> Bool {
for i = 0; i < idx; i = i + 1 {
if elements[i].code() == code {
return true
}
} nobreak {
false
}
}
///|
pub fn ValidationReport::summary(self : ValidationReport) -> String {
if self.ok {
"ok"
} else {
self.issues.map(fn(issue) { "\{issue.path}: \{issue.message}" }).join("; ")
}
}