moon_zod/core/errors.mbt

117 lines
3.1 KiB
Plaintext

///|
/// 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[String])
MissingRequired(String)
InvalidKey(String)
InvalidElement(String, Int)
InvalidValue(Array[Json])
Custom
} derive(Debug, Eq)
///|
/// 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
}
///|
/// Resolve an error message using the error map priority chain.
/// Priority: error_map (contextual) → pre-resolved message.
pub fn finalize_issue(raw : RawIssue, params : ParseParams) -> ValidationError {
let message = match params.error_map {
Some(map) =>
match map(raw.code, raw.path, raw.input) {
Some(m) => if m.is_empty() { raw.message } else { m }
None => raw.message
}
None => raw.message
}
ValidationError::{ code: raw.code, path: raw.path, message, got: raw.input }
}
///|
/// 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 path = format_path(path_stack)
for rule in rules {
if !(rule.check)(json) {
out.push(RawIssue::{
code: rule.code,
path,
message: rule.message,
input: json,
})
}
}
}
///|
/// 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"
DefaultType(_, _) => "default"
LazyType(_) => "lazy"
DiscriminatedUnionType(_, _) => "discriminated_union"
PipeType(_, _, _) => "pipe"
}
}