Sign in

    smithy-validation

    Direct application value validation against Smithy shapes

    Download zip
    Author
    Version
    0.5.0
    License
    Apache-2.0
    Last updated
    2 days ago
    Downloads
    17

    #Direct value validation

    Validate application values against assembled Smithy data shapes through a reusable library and the smithy validate-value command. This module depends on the Smithy model and core libraries; optional format integrations belong in separate modules.

    #Library

    ///|
    test {
    let loaded = @smithy.Loader::new()
    .add_idl(
    "model.smithy", "$version: \"2\"\nnamespace example\n@range(min: 1, max: 100)\ninteger Count\nstructure Input { @required count: Count }",
    )
    .assemble()
    let prepared = @validation.PreparedValidator::prepare(
    loaded.model.unwrap(),
    @smithy.ShapeId::parse("example#Input").unwrap(),
    )
    assert_true(
    prepared
    .validate_json(@validation.parse_json_value("{\"count\":2}"))
    .is_valid(),
    )
    let result = prepared.validate_json(
    @validation.parse_json_value("{\"count\":0}"),
    )
    assert_false(result.is_valid())
    assert_eq(result.findings[0].code, @validation.Range)
    assert_eq(result.findings[0].path.pointer(), "/count")
    assert_eq(result.findings[0].shape.to_string(), "example#Input$count")
    }

    Prepare once and validate repeatedly. Preparation snapshots the selected shape's reachable contract, including member trait overrides. Later edits to the source model do not change it. Targets may be data shapes or members. Services, operations, resources, mixins and missing targets fail preparation. NodeValue inputs use prepared.validate(value) directly. Other types implement ValueView.

    Use parse_json_value for text input. It rejects duplicate decoded keys, limits nesting to 128 containers and input to 16 MiB of UTF-8, and preserves every numeric token. A caller-supplied Json can preserve only numbers the caller retained: ordinary core JSON parsing may already have rounded decimals. A valid numeric repr is authoritative; without one, only the supplied finite Double value is available.

    #CLI

    smithy validate-value --shape example#Input --value request.json model.smithy smithy validate-value --shape example#Input --value - --diagnostics json model.smithy

    Text success is quiet; text findings go to stderr. JSON diagnostics go to stdout with valid, complete and findings. Invalid models/values, malformed input, I/O errors and incomplete checks exit 1. Invalid usage, targets and unsupported configurations exit 2. Existing smithy validate continues to check model definitions. Stdin may appear once across model and value inputs.

    #Policies

    ConcernDefault and explicit choices
    Unknown structure fieldsReject; unknown_members=Ignore or CLI --unknown-members ignore permits them. Unknown union alternatives always fail.
    Member namesSmithy names; member_names=JsonName or CLI --member-names json uses jsonName. Preparation rejects collisions.
    NullDistinct from absence. Optional members may be absent but must match their target when present. Document and sparse list/map values permit null.
    Defaults/coercionNo mutation, default insertion or string-to-number coercion. Defaults do not excuse an absent required member.
    ConstraintsExact range, Unicode scalar string length, decoded blob byte length, collection length, pattern, uniqueness and enum checks. Each explicit member trait replaces that same target trait as a whole.
    NumbersExact mathematical integers and byte/short/integer/long bounds. BigInteger/BigDecimal remain exact. Float/double magnitude is checked without rounding input.
    Nonnumeric floatsOpt-in nonnumeric_floats=true permits exactly NaN, Infinity, -Infinity strings, subject to range rules.
    BlobCanonical padded RFC 4648 base64, including zero padding bits.
    TimestampExact epoch-seconds number; timestamp_format=DateTime or effective timestampFormat("date-time") selects a calendar-checked string with an offset. HTTP-date and leap-second notation are unsupported.
    Runtime contractsReachable smithy.contracts#conditions raises UnsupportedConstraint. Assembly still validates the expression definition.

    The portable pattern profile supports literals, classes/ranges, groups, alternation, repetitions, whole-input anchors and common digit/word/whitespace escapes. Dot excludes line terminators. Lookaround, backreferences, flags, Unicode properties, complement escapes inside classes and unsupported core regex constructs fail preparation. Matching searches unless anchored. This is not the full ECMA-262 language.

    Preparation limits patterns to 4,096 source UTF-16 units, 32 nested groups and 1,024 expanded expression units, counting repetition expansion and alternatives. Matching charges eight times the expanded estimate times the input UTF-16 length plus one against the call's work budget. Patterns exceeding the preparation caps raise UnsupportedConstraint; exhausted matching work returns LimitExceeded before executing the pattern.

    #Findings, adapters and limits

    Findings carry a stable code, Error/Advisory severity, typed instance path, semantic shape/member ID and message. pointer() renders JSON Pointer; retain steps() to distinguish repeated fields and map keys. LocatedObject adapters preserve original source positions. An optional base_path lets an RPC parameter finding remain /params/0 instead of becoming /params/count.

    Limits::new defaults to depth 128, 100,000 value/constraint/equality work units and 100 findings. Positive configurable maxima are depth 1,024, 10,000,000 work units and 10,000 findings. The last finding slot is reserved for LimitExceeded. Exhaustion sets complete=false; is_valid() requires completion and no Error findings. Iterative walking/equality also checks document values, ignored fields and cyclic inputs. Adapters themselves are trusted code.

    Native scalar, annotation and sexp values are explicitly unsupported by the JSON profile. The neutral view retains exact decimals, floats, timestamps, symbols and bytes for later opt-in profiles. Ion and WIT remain separate integrations outside this library's dependency graph.

    Run mise run test:validation-consumer to audit resolved packaged dependencies and run an isolated example on all four targets. See design and the shared RPC contract.

    ValueView

    pub(open) trait ValueView {
    fn inspect(Self) -> ValueNode[Self]
    }

    Read one value without copying or converting its descendants.
    impl ValueView for Json

    ConfigurationError

    pub(all) suberror ConfigurationError {
    ConfigurationError(String)
    } derive(
    Debug
    )

    PreparationError

    pub(all) suberror PreparationError {
    InvalidTarget(
    ShapeId
    , String)
    InvalidConfiguration(String)
    InvalidModel(
    ShapeId
    , String)
    UnsupportedConstraint(
    ShapeId
    , String)
    } derive(
    Debug
    )

    ValueInputError

    pub(all) suberror ValueInputError {
    InvalidJson(Int, String)
    DuplicateJsonKey(Int)
    JsonInputLimit(Int, String)
    } derive(
    Debug
    )

    Input offsets use UTF-16 code units, matching MoonBit string locations.

    Config

    pub struct Config {
    limits : Limits
    unknown_members : UnknownMembers
    member_names : MemberNames
    timestamp_format : TimestampFormat
    nonnumeric_floats : Bool
    } derive(Eq,
    Debug
    )

    Config::new

    fn Config::new(limits? : Limits, unknown_members? : UnknownMembers, member_names? : MemberNames, timestamp_format? : TimestampFormat, nonnumeric_floats? : Bool) -> Config

    Finding

    pub(all) struct Finding {
    code : FindingCode
    severity : FindingSeverity
    path : InstancePath
    shape :
    ShapeId

    message : String
    } derive(Eq,
    Debug
    )

    impl ToJson for Finding

    FindingCode

    pub(all) enum FindingCode {
    TypeMismatch
    RequiredMember
    UnknownMember
    DuplicateMember
    InvalidNumber
    NumericBounds
    Range
    Length
    Pattern
    UniqueItems
    EnumValue
    UnionMember
    InvalidEncoding
    InvalidTimestamp
    UnsupportedValue
    LimitExceeded
    } derive(Eq,
    Debug
    )

    FindingCode::to_string

    fn FindingCode::to_string(self : FindingCode) -> String

    FindingSeverity

    pub(all) enum FindingSeverity {
    Error
    Advisory
    } derive(Eq,
    Debug
    )

    InstancePath

    pub struct InstancePath {
    // private fields
    } derive(Eq,
    Debug
    )

    InstancePath::append

    fn InstancePath::append(self : InstancePath, relative : InstancePath) -> InstancePath

    InstancePath::field

    fn InstancePath::field(self : InstancePath, name : String, occurrence? : Int) -> InstancePath raise ConfigurationError

    InstancePath::index

    fn InstancePath::index(self : InstancePath, index : Int) -> InstancePath raise ConfigurationError

    InstancePath::map_key

    fn InstancePath::map_key(self : InstancePath, name : String, occurrence? : Int) -> InstancePath raise ConfigurationError

    InstancePath::pointer

    fn InstancePath::pointer(self : InstancePath) -> String

    InstancePath::root

    InstancePath::steps

    InstanceStep

    pub(all) enum InstanceStep {
    Field(String, Int)
    Index(Int)
    MapKey(String, Int)
    } derive(Eq,
    Debug
    )

    A source position. Occurrences distinguish repeated native object fields.

    Limits

    pub struct Limits {
    depth : Int
    work : Int
    findings : Int
    } derive(Eq,
    Debug
    )

    Limits::new

    fn Limits::new(depth? : Int, work? : Int, findings? : Int) -> Limits raise ConfigurationError

    LocatedField

    pub struct LocatedField[V] {
    name : String
    value : V
    source : InstancePath
    }

    LocatedField::new

    fn[V] LocatedField::new(name : String, value : V, source : InstancePath) -> LocatedField[V]

    MemberNames

    pub(all) enum MemberNames {
    Smithy
    JsonName
    } derive(Eq,
    Debug
    )

    NativeKind

    pub(all) enum NativeKind {
    NullKind
    BoolKind
    IntKind
    FloatKind
    DecimalKind
    TimestampKind
    SymbolKind
    StringKind
    BlobKind
    ClobKind
    StructKind
    ListKind
    SexpKind
    } derive(Eq,
    Debug
    )

    NativeOffset

    pub(all) enum NativeOffset {
    KnownOffsetMinutes(Int)
    UnknownOffset
    } derive(Eq,
    Debug
    )

    NativeScalar

    pub(all) enum NativeScalar {
    TypedNull(NativeKind)
    Float(Double)
    Decimal(
    NodeNumber
    )
    Timestamp(NativeTimestamp)
    Symbol(String?)
    Blob(Bytes)
    Clob(Bytes)
    } derive(
    Debug
    )

    NativeTimestamp

    pub struct NativeTimestamp {
    year : Int
    month : Int?
    day : Int?
    hour : Int?
    minute : Int?
    second : Int?
    fraction :
    NodeNumber
    ?
    offset : NativeOffset
    } derive(
    Debug
    )

    A native timestamp's precision and offset, independent of any format library.

    NativeTimestamp::day

    fn NativeTimestamp::day(self : NativeTimestamp) -> Int?

    NativeTimestamp::fraction

    NativeTimestamp::hour

    fn NativeTimestamp::hour(self : NativeTimestamp) -> Int?

    NativeTimestamp::minute

    fn NativeTimestamp::minute(self : NativeTimestamp) -> Int?

    NativeTimestamp::month

    fn NativeTimestamp::month(self : NativeTimestamp) -> Int?

    NativeTimestamp::new

    fn NativeTimestamp::new(year : Int, month? : Int?, day? : Int?, hour? : Int?, minute? : Int?, second? : Int?, fraction? :
    NodeNumber
    ?, offset? : NativeOffset) -> NativeTimestamp raise ConfigurationError

    NativeTimestamp::offset

    NativeTimestamp::second

    fn NativeTimestamp::second(self : NativeTimestamp) -> Int?

    NativeTimestamp::year

    fn NativeTimestamp::year(self : NativeTimestamp) -> Int

    PreparedValidator

    pub struct PreparedValidator {
    // private fields
    } derive(
    Debug
    )

    A private snapshot of the reachable semantic contract, reusable across values.

    PreparedValidator::prepare

    PreparedValidator::validate

    fn[V : ValueView] PreparedValidator::validate(self : PreparedValidator, value : V, base_path? : InstancePath) -> ValidationResult

    Validate a value using a read-only adapter. No input or prepared state is mutated.

    PreparedValidator::validate_json

    fn PreparedValidator::validate_json(self : PreparedValidator, value : Json, base_path? : InstancePath) -> ValidationResult

    TimestampFormat

    pub(all) enum TimestampFormat {
    EpochSeconds
    DateTime
    } derive(Eq,
    Debug
    )

    UnknownMembers

    pub(all) enum UnknownMembers {
    Reject
    Ignore
    } derive(Eq,
    Debug
    )

    ValidationResult

    pub(all) struct ValidationResult {
    findings : Array[Finding]
    complete : Bool
    } derive(Eq,
    Debug
    )

    ValidationResult::is_valid

    fn ValidationResult::is_valid(self : ValidationResult) -> Bool

    ValueNode

    pub(all) enum ValueNode[V] {
    Null
    Boolean(Bool)
    Number(
    NodeNumber
    )
    InvalidNumber
    String(String)
    Array(Array[V])
    Object(Array[(String, V)])
    LocatedObject(Array[LocatedField[V]])
    Native(NativeScalar)
    Sexp(Array[V])
    Annotated(Array[String?], V)
    }

    parse_json_value

    fn parse_json_value(text : String) -> Json raise ValueInputError

    Parse one bounded JSON value, rejecting duplicate decoded object names.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io