///|
/// Machine-readable issue code for structured error classification.
pub(all) enum IssueCode {
InvalidType(String)
TooBig(String, Double, Bool)
TooSmall(String, Double, Bool)
InvalidFormat(String)
NotMultipleOf(Double)
UnrecognizedKeys(Array[String])
InvalidUnion(Array[Array[RawIssue]])
MissingRequired(String)
InvalidKey(String)
InvalidElement(String, Int32)
InvalidValue(Array[Json])
Custom
} derive(Debug, Eq)
///|
#deprecated
pub extend IssueCode with @debug.Debug::{to_repr}
///|
#deprecated
pub extend IssueCode with Eq::{not_equal, equal}
///|
/// Error map function: contextual override for error messages.
/// Returns `Some(msg)` to override, `None` to fall through to the pre-resolved message.
pub type ErrorMap = (IssueCode, String, Json) -> String?
///|
/// Parameters for `Schema::safe_parse`.
pub(all) struct ParseParams {
path : String
error_map : ErrorMap?
}
///|
pub fn ParseParams::default() -> ParseParams {
ParseParams::{ path: "", error_map: None, }
}
///|
/// A raw validation issue with the message pre-resolved at the generation site.
/// No Schema reference needed — messages are resolved immediately from
/// rule.message / schema.invalid_type_error / schema.required_error / hardcoded default.
pub(all) struct RawIssue {
code : IssueCode
path : String
message : String
input : Json
path_segments : Array[PathSegment]
explicit_message : Bool
} derive(Debug, Eq)
///|
/// Resolve an error message using the error map priority chain.
/// Priority: explicit Schema/rule message → error_map → default message.
pub fn finalize_issue(raw : RawIssue, params : ParseParams) -> ValidationError {
let resolved = resolve_raw_issue(raw, params)
ValidationError::{
code: resolved.code,
path: resolved.path,
path_segments: resolved.path_segments,
message: resolved.message,
got: resolved.input,
}
}
///|
fn resolve_raw_issue(raw : RawIssue, params : ParseParams) -> RawIssue {
let code = match raw.code {
InvalidUnion(branches) =>
InvalidUnion(
branches.map(fn(branch) {
branch.map(fn(issue) { resolve_raw_issue(issue, params) })
}),
)
other => other
}
let message = if raw.explicit_message {
raw.message
} else {
match params.error_map {
Some(map) => map(code, raw.path, raw.input).unwrap_or(raw.message)
None => raw.message
}
}
{ ..raw, code, message, }
}
///|
/// Convert raw issues to validation errors using finalize_issue.
pub fn finalize_issues(
raw_issues : Array[RawIssue],
params : ParseParams,
) -> Array[ValidationError] {
raw_issues.map(fn(raw) { finalize_issue(raw, params) })
}
///|
/// Collect raw issues from rule failures.
pub fn collect_raw_errors(
out : Array[RawIssue],
path_stack : Array[String],
json : Json,
rules : Array[Rule],
) -> Unit {
let _ = collect_node_errors(out, path_stack, json, rules)
}
///|
fn collect_node_errors(
out : Array[RawIssue],
path_stack : Array[String],
json : Json,
rules : Array[Rule],
) -> Bool {
for rule in rules {
if !(rule.check)(json) {
let code = match (rule.constraint, json) {
(Integer | SafeInteger, Number(value, ..)) =>
if value != value.floor() {
InvalidType("int")
} else if value > 0.0 {
TooBig("int", 9007199254740991.0, true)
} else {
TooSmall("int", -9007199254740991.0, true)
}
(ExactLength(origin, length), value) => {
let actual = match value {
String(s) => ecma_code_point_length(s)
Array(items) => items.length()
_ => length
}
if actual < length {
TooSmall(origin, length.to_double(), true)
} else {
TooBig(origin, length.to_double(), true)
}
}
_ => rule.code
}
out.push(RawIssue::{
code,
path: format_path(path_stack),
path_segments: stack_segments(path_stack),
explicit_message: rule.explicit_message,
message: rule.message,
input: json,
})
if code is InvalidType(_) {
return true
}
}
}
false
}
///|
/// Map SchemaType to its origin string for IssueCode payloads.
pub fn type_origin(t : SchemaType) -> String {
match t {
StringType => "string"
NumberType => "number"
BooleanType => "boolean"
NullType => "null"
AnyType => "any"
UnknownType => "unknown"
ObjectType(_, _) => "object"
ArrayType(_) => "array"
TupleType(_) => "tuple"
EnumType(_) => "enum"
UnionType(_) => "union"
IntersectionType(_) => "intersection"
LiteralType(_) => "literal"
TransformType(_, _) => "transform"
PreprocessType(_, _) => "preprocess"
OptionalType(_) => "optional"
NullableType(_) => "nullable"
PrefaultType(_, _) => "prefault"
DefaultType(_, _) => "default"
LazyType(_) => "lazy"
DiscriminatedUnionType(_, _) => "discriminated_union"
PipeType(_, _, _) => "pipe"
}
}