///|
priv struct Constraints {
min : @smithy.NodeNumber?
max : @smithy.NodeNumber?
min_length : @smithy.NodeNumber?
max_length : @smithy.NodeNumber?
pattern : BoundedPattern?
unique : Bool
timestamp : TimestampFormat
} derive(Debug)
///|
fn effective_trait(
shape : @smithy.Shape,
target : @smithy.Shape,
name : String,
) -> @smithy.NodeValue? {
match shape.trait_value(name) {
Some(value) => Some(value)
None => target.trait_value(name)
}
}
///|
fn constraint_bounds(
value : @smithy.NodeValue?,
id : @smithy.ShapeId,
length~ : Bool = false,
) -> (@smithy.NodeNumber?, @smithy.NodeNumber?) raise PreparationError {
guard value is Some(value) else { return (None, None) }
guard value is Object(fields) else {
raise InvalidModel(
id, "I expected an object constraint, but found another value kind.",
)
}
let read = fn(name : String) -> @smithy.NodeNumber? raise PreparationError {
match fields.get(name) {
None => None
Some(Number(number)) => {
if length &&
(
!number.is_integer() ||
number.numeric_compare(@smithy.NodeNumber::parse("0").unwrap()) < 0
) {
raise InvalidModel(
id, "I expected a nonnegative integer length bound.",
)
}
Some(number)
}
_ => raise InvalidModel(id, "I expected a numeric constraint bound.")
}
}
for name, _ in fields {
if name != "min" && name != "max" {
raise InvalidModel(id, "I found an unknown constraint bound.")
}
}
let min = read("min")
let max = read("max")
if min is None && max is None {
raise InvalidModel(id, "I expected at least one constraint bound.")
}
if min is Some(min) && max is Some(max) && min.numeric_compare(max) > 0 {
raise InvalidModel(
id, "I expected the minimum constraint bound to be no greater than the maximum.",
)
}
(min, max)
}
///|
fn annotation(
value : @smithy.NodeValue?,
id : @smithy.ShapeId,
) -> Bool raise PreparationError {
match value {
None => false
Some(Object(fields)) if fields.is_empty() => true
_ => raise InvalidModel(id, "I expected an empty annotation object.")
}
}
///|
fn prepare_constraints(
shape : @smithy.Shape,
target : @smithy.Shape,
config : Config,
) -> Constraints raise PreparationError {
let id = shape.id
let trait_ = fn(name) { effective_trait(shape, target, "smithy.api#" + name) }
if effective_trait(shape, target, "smithy.contracts#conditions") is Some(_) {
raise UnsupportedConstraint(
id, "I cannot evaluate runtime contract conditions in this validation profile.",
)
}
let range = trait_("range")
let length = trait_("length")
let pattern = trait_("pattern")
let unique = annotation(trait_("uniqueItems"), id) || target.is_legacy_set()
if range is Some(_) &&
!(target.body
is Simple(
Byte
| Short
| Integer
| Long
| Float
| Double
| BigInteger
| BigDecimal
) ||
target.body is IntEnum(_)) {
raise InvalidModel(
id, "I expected a numeric target for the range constraint.",
)
}
if length is Some(_) &&
!(target.body is Simple(String | Blob) ||
target.body is Enum(_) ||
target.body is List(_) ||
target.body is Map(..)) {
raise InvalidModel(
id, "I expected a string, blob or collection for the length constraint.",
)
}
if unique && !(target.body is List(_)) {
raise InvalidModel(id, "I expected a list for the uniqueness constraint.")
}
let (min, max) = constraint_bounds(range, id)
let (min_length, max_length) = constraint_bounds(length, id, length=true)
let compiled = match pattern {
None => None
Some(String(text)) if target.body is Simple(String) ||
target.body is Enum(_) => Some(compile_pattern(text, id))
_ =>
raise InvalidModel(id, "I expected a string pattern on a string shape.")
}
let timestamp = match trait_("timestampFormat") {
None => config.timestamp_format
Some(String("epoch-seconds")) => EpochSeconds
Some(String("date-time")) => DateTime
Some(String(_)) =>
raise UnsupportedConstraint(
id, "I cannot validate this timestamp format in the JSON value profile.",
)
_ => raise InvalidModel(id, "I expected a string timestamp format.")
}
{ min, max, min_length, max_length, pattern: compiled, unique, timestamp, }
}