///|
fn Model::node_validation_events(
self : Model,
value : NodeValue,
target : ShapeId,
owner : Shape?,
event_id : String,
severity : String,
allow_constraint_errors~ : Bool = false,
) -> Array[ValidationEvent] {
let events : Array[ValidationEvent] = []
let stack = [(target, value, "")]
let seen : Set[String] = Set([])
let constraint_severity = if allow_constraint_errors {
"WARNING"
} else {
severity
}
while stack.pop() is Some((target, value, path)) {
let key = target.to_string() + "|" + path
if seen.contains(key) {
continue
}
seen.add(key)
guard self.shapes.get(target) is Some(shape) else { continue }
let effective = match shape.body {
Member(target~) => self.shapes.get(target).unwrap_or(shape)
_ => shape
}
let emit = fn(suffix : String, level : String, message : String) {
events.push(
validation_event(
event_id + suffix,
level,
owner,
"Error validating value" + path + ": " + message,
),
)
}
if shape.trait_value("smithy.api#range") is Some(range) &&
value is Number(n) {
for bound in ["min", "max"] {
if validation_field(range, bound) is Some(Number(limit)) &&
(
(bound == "min" && n.numeric_compare(limit) < 0) ||
(bound == "max" && n.numeric_compare(limit) > 0)
) {
let level = if event_id == "DefaultTrait" &&
n.coefficient == BigInt::from_int(0) {
"WARNING"
} else {
constraint_severity
}
emit(
if shape.body is Member(..) {
".Member.InvalidRange"
} else {
".Target.InvalidRange"
},
level,
"Value provided for `" +
target.to_string() +
"` violates its " +
bound +
" range.",
)
}
}
}
if shape.trait_value("smithy.api#range") is Some(range) &&
value is String(text) {
if text == "NaN" {
emit(
"", constraint_severity, "A value constrained by @range must be a number, but found NaN.",
)
}
if (text == "-Infinity" && validation_field(range, "min") is Some(_)) ||
(text == "Infinity" && validation_field(range, "max") is Some(_)) {
emit(
if shape.body is Member(..) {
".Member.InvalidRange"
} else {
".Target.InvalidRange"
},
constraint_severity,
"Infinite value violates its range constraint.",
)
}
}
if shape.trait_value("smithy.api#length") is Some(length) {
let count = match value {
String(text) => Some(text.length())
Array(values) => Some(values.length())
Object(values) => Some(values.length())
_ => None
}
if count is Some(count) {
let n = NodeNumber::parse(count.to_string()).unwrap()
for bound in ["min", "max"] {
if validation_field(length, bound) is Some(Number(limit)) &&
(
(bound == "min" && n.numeric_compare(limit) < 0) ||
(bound == "max" && n.numeric_compare(limit) > 0)
) {
emit(
"",
constraint_severity,
"Value provided for `" +
target.to_string() +
"` violates its " +
bound +
" length.",
)
}
}
}
}
if effective.body is Simple(String) && value is String(text) {
if shape.trait_value("smithy.api#pattern") is Some(String(pattern)) {
let matches = if shape.id.to_string() == "smithy.api#Identifier" {
identifier(text)
} else {
let pattern = pattern
.replace_all(old="\\d", new="[0-9]")
.replace_all(old="\\w", new="[a-zA-Z0-9_]")
try (@string.Regex::Regex(pattern).execute(text) is Some(_)) catch {
_ => true
}
}
if !matches {
emit(
"",
constraint_severity,
"String value provided for `" +
target.to_string() +
"` must match regular expression: " +
pattern,
)
}
}
if shape.trait_value("smithy.api#idRef") is Some(config) {
let valid = match ShapeId::parse(text) {
None => false
Some(id) =>
match self.shapes.get(id) {
None =>
validation_field(config, "failWhenMissing") !=
Some(Boolean(true))
Some(_) => {
let selector = validation_string(config, "selector").unwrap_or(
"*",
)
Selector::parse(selector).select_ids(self).contains(text) catch {
_ => false
}
}
}
}
if !valid {
emit(
"",
severity,
validation_string(config, "errorMessage").unwrap_or(
"Shape ID `" + text + "` does not satisfy its idRef constraint.",
),
)
}
}
if shape.trait_value("smithy.api#enum") is Some(Array(values)) &&
!values
.iter()
.any(entry => validation_field(entry, "value") == Some(value)) {
emit(
"",
severity,
"String value provided for `" +
target.to_string() +
"` is not an enum value.",
)
}
}
if shape.trait_value("smithy.api#uniqueItems") is Some(_) &&
value is Array(values) {
let mut duplicate = false
for i = 0; i < values.length(); i = i + 1 {
for j = 0; j < i; j = j + 1 {
if values[i] == values[j] {
duplicate = true
}
}
}
if duplicate {
emit("", severity, "Collection contains duplicate values.")
}
}
let valid = match (shape.body, value) {
(Member(target~), value) => {
stack.push((target, value, path))
true
}
(Simple(Document), _) => true
(Simple(Boolean), Boolean(_)) => true
(Simple(String | Blob), String(_)) => true
(Simple(Timestamp), String(_) | Number(_)) => true
(Simple(Float | Double), Number(_)) => true
(Simple(Float | Double), String("NaN" | "Infinity" | "-Infinity")) => true
(Simple(BigDecimal), Number(_)) => true
(Simple(BigInteger), Number(n)) => n.is_integer()
(Simple(Byte), Number(n)) => n.in_range("-128", "127")
(Simple(Short), Number(n)) => n.in_range("-32768", "32767")
(Simple(Integer), Number(n)) => n.in_range("-2147483648", "2147483647")
(Simple(Long), Number(n)) =>
n.in_range("-9223372036854775808", "9223372036854775807")
(List(member_), Array(values)) => {
for i, value in values {
stack.push((member_, value, path + "/" + i.to_string()))
}
true
}
(Map(key~, value=member_), Object(values)) => {
for name, value in values {
stack.push(
(key, String(name), path + "/" + pointer(name) + " (map-key)"),
)
stack.push((member_, value, path + "/" + pointer(name)))
}
true
}
(Structure(members) | Union(members), Object(values)) => {
if shape.body is Union(_) && values.length() > 1 {
emit(
"", severity, "Union values can contain a value for only a single member.",
)
continue
}
let declared : Set[String] = Set([])
for member_ in members {
guard self.shapes.get(member_) is Some(member_shape) &&
member_.member is Some(name) else {
continue
}
declared.add(name)
match values.get(name) {
Some(value) =>
stack.push((member_, value, path + "/" + pointer(name)))
None =>
if member_shape.trait_value("smithy.api#required") is Some(_) {
emit(
"",
constraint_severity,
"Missing required structure member `" +
name +
"` for `" +
target.to_string() +
"`.",
)
}
}
}
for name, _ in values {
if !declared.contains(name) {
emit(
".UnknownMember." + target.to_string() + "." + name,
if shape.body is Union(_) {
severity
} else {
"WARNING"
},
"Member `" +
name +
"` does not exist in `" +
target.to_string() +
"`.",
)
}
}
!(shape.body is Union(_) && values.length() > 1)
}
(Enum(members) | IntEnum(members), _) =>
members
.iter()
.any(id => {
self.shapes.get(id).bind(s => s.trait_value("smithy.api#enumValue")) ==
Some(value)
})
_ => false
}
if !valid {
emit(
"",
severity,
"Expected " +
shape.body.keyword() +
" value for `" +
target.to_string() +
"`.",
)
}
}
events
}