///|
pub(all) suberror PreparationError {
InvalidTarget(@smithy.ShapeId, String)
InvalidConfiguration(String)
InvalidModel(@smithy.ShapeId, String)
UnsupportedConstraint(@smithy.ShapeId, String)
} derive(Debug)
///|
priv struct PreparedMember {
name : String
id : @smithy.ShapeId
required : Bool
} derive(Debug)
///|
priv struct PreparedShape {
id : @smithy.ShapeId
body : @smithy.ShapeBody
constraints : Constraints
members : Array[PreparedMember]
by_name : Map[String, PreparedMember]
enum_values : Array[@smithy.NodeValue]
sparse : Bool
} derive(Debug)
///|
/// A private snapshot of the reachable semantic contract, reusable across values.
pub struct PreparedValidator {
priv target : @smithy.ShapeId
priv shapes : Map[@smithy.ShapeId, PreparedShape]
priv config : Config
} derive(Debug)
///|
fn data_shape(shape : @smithy.Shape) -> Bool {
match shape.body {
Operation(..) | Service(..) | Resource(..) => false
_ => shape.trait_value("smithy.api#mixin") is None
}
}
///|
fn collection_member(
model : @smithy.Model,
container : @smithy.ShapeId,
member_id : @smithy.ShapeId,
name : String,
) -> Unit raise PreparationError {
guard container.with_member(name) == Some(member_id) &&
model.shapes.get(member_id) is Some(member_) &&
member_.body is Member(_) else {
raise InvalidModel(
container, "I expected a declared collection member belonging to its containing shape.",
)
}
}
///|
pub fn PreparedValidator::prepare(
model : @smithy.Model,
target : @smithy.ShapeId,
config~ : Config = Config::new(),
) -> PreparedValidator raise PreparationError {
guard model.shapes.get(target) is Some(root) && data_shape(root) else {
raise InvalidTarget(
target, "I expected an existing data shape or data member as the validation target.",
)
}
let shapes : Map[@smithy.ShapeId, PreparedShape] = Map([])
let stack = [target]
while stack.pop() is Some(id) {
if shapes.contains(id) {
continue
}
if shapes.length() >= config.limits.work {
raise InvalidModel(id, "I reached the preparation work limit.")
}
guard model.shapes.get(id) is Some(shape) && data_shape(shape) else {
raise InvalidModel(
id, "I expected a reachable data shape, but found a missing or nondata target.",
)
}
let effective = match shape.body {
Member(target=member_target) => {
guard member_target.member_name() is None &&
model.shapes.get(member_target) is Some(resolved) &&
data_shape(resolved) &&
!(resolved.body is Member(_)) else {
raise InvalidModel(
id, "I expected the member to target an existing data shape.",
)
}
resolved
}
_ => shape
}
let body = match effective.body {
Structure(ids) => @smithy.Structure(ids.copy())
Union(ids) => @smithy.Union(ids.copy())
Enum(ids) => @smithy.Enum(ids.copy())
IntEnum(ids) => @smithy.IntEnum(ids.copy())
other => other
}
let members : Array[PreparedMember] = []
let enum_values : Array[@smithy.NodeValue] = []
match body {
Structure(ids) | Union(ids) => {
let names : Set[String] = Set([])
for member_id in ids {
guard member_id.root() == effective.id &&
member_id.member_name() is Some(name) &&
model.shapes.get(member_id) is Some(member_shape) &&
member_shape.body is Member(_) else {
raise InvalidModel(
id, "I expected a declared member belonging to its containing shape.",
)
}
let name = if config.member_names == JsonName {
match member_shape.trait_value("smithy.api#jsonName") {
None => name
Some(String(wire)) => wire
_ =>
raise InvalidModel(
member_id, "I expected a string JSON member name.",
)
}
} else {
name
}
guard !names.contains(name) else {
raise InvalidModel(
id, "I found conflicting serialized member names.",
)
}
names.add(name)
members.push({
name,
id: member_id,
required: annotation(
member_shape.trait_value("smithy.api#required"),
member_id,
),
})
stack.push(member_id)
}
}
List(member_id) => {
collection_member(model, effective.id, member_id, "member")
stack.push(member_id)
}
Map(key~, value~) => {
collection_member(model, effective.id, key, "key")
collection_member(model, effective.id, value, "value")
stack.push(key)
stack.push(value)
}
Enum(ids) | IntEnum(ids) => {
guard !ids.is_empty() else {
raise InvalidModel(
id, "I expected at least one declared enum member.",
)
}
for member_id in ids {
guard member_id.root() == effective.id &&
member_id.member_name() is Some(_) &&
model.shapes.get(member_id) is Some(member_shape) else {
raise InvalidModel(id, "I expected a declared enum member.")
}
let value = member_shape.trait_value("smithy.api#enumValue")
match (body, value) {
(Enum(_), Some(String(value))) =>
enum_values.push(@smithy.String(value))
(IntEnum(_), Some(Number(value))) if value.is_integer() =>
enum_values.push(@smithy.Number(value))
_ =>
raise InvalidModel(
member_id, "I expected a scalar enum value of the declared enum kind.",
)
}
}
}
_ => ()
}
if effective_trait(shape, effective, "smithy.api#enum") is Some(value) {
guard body is Simple(String) && value is Array(entries) else {
raise InvalidModel(id, "I expected string enum entries.")
}
for entry in entries {
guard entry is Object(fields) &&
fields.get("value") is Some(String(text)) else {
raise InvalidModel(id, "I expected a string enum entry value.")
}
enum_values.push(@smithy.String(text))
}
}
let by_name : Map[String, PreparedMember] = Map([])
for member_ in members {
by_name[member_.name] = member_
}
shapes[id] = {
id,
body,
members,
by_name,
enum_values,
constraints: prepare_constraints(shape, effective, config),
sparse: annotation(
effective_trait(shape, effective, "smithy.api#sparse"),
id,
),
}
}
{ target, shapes, config, }
}