///|
/// Directed model containment edges, without traits or member-to-container edges.
fn neighbors(shape : @smithy.Shape) -> Array[@smithy.ShapeId] {
match shape.body {
Member(target~) => [target]
Operation(input~, output~, errors~) => [input, output] + errors
Service(operations~, resources~, errors~, ..) =>
operations + resources + errors
Resource(
identifiers~,
properties~,
create~,
put~,
read~,
update~,
delete~,
list~,
operations~,
collectionOperations~,
resources~
) => {
let out = identifiers.values().to_array() +
properties.values().to_array() +
operations +
collectionOperations +
resources
for id in [create, put, read, update, delete, list] {
if id is Some(id) {
out.push(id)
}
}
out
}
_ => members(shape)
}
}
///|
fn closure(
model : @smithy.Model,
start : @smithy.ShapeId,
) -> Set[@smithy.ShapeId] {
let seen : Set[@smithy.ShapeId] = Set([])
let stack = [start]
while stack.pop() is Some(id) {
if seen.contains(id) {
continue
}
seen.add(id)
if model.shapes.get(id) is Some(shape) {
stack.append(neighbors(shape))
}
}
seen
}
///|
/// EntityShape.getOperations contains explicitly bound operations; resource
/// lifecycle operations are separate relationships in the reference model.
fn entity_ids(
shape : @smithy.Shape,
) -> (Array[@smithy.ShapeId], Array[@smithy.ShapeId])? {
match shape.body {
Service(operations~, resources~, ..)
| Resource(operations~, resources~, ..) => Some((operations, resources))
_ => None
}
}
///|
fn contained_operations(
model : @smithy.Model,
start : @smithy.ShapeId,
) -> Set[@smithy.ShapeId] {
let seen : Set[@smithy.ShapeId] = Set([])
let operations : Set[@smithy.ShapeId] = Set([])
let stack = [start]
while stack.pop() is Some(id) {
if seen.contains(id) {
continue
}
seen.add(id)
if model.shapes.get(id) is Some(shape) {
match shape.body {
Service(operations=ops, resources~, ..) => {
for op in ops {
operations.add(op)
}
stack.append(resources)
}
Resource(
create~,
put~,
read~,
update~,
delete~,
list~,
operations=ops,
collectionOperations~,
resources~,
..
) => {
for op in ops + collectionOperations {
operations.add(op)
}
for op in [create, put, read, update, delete, list] {
if op is Some(op) {
operations.add(op)
}
}
stack.append(resources)
}
_ => ()
}
}
}
operations
}
///|
fn entity_bindings(
d : Differences,
change : ShapeChange,
events : Array[ValidationEvent],
) -> Unit {
guard entity_ids(change.old_shape) is Some((old_ops, old_resources)) &&
entity_ids(change.new_shape) is Some((new_ops, new_resources)) else {
return
}
let shape = change.new_shape
let service = shape.body is Service(..)
let parent = if service { "Service" } else { "Resource" }
for
(noun, old_ids, new_ids) in [
("Operation", old_ops, new_ops),
("Resource", old_resources, new_resources),
] {
for id in new_ids {
if !old_ids.contains(id) {
emit(
events,
"Added" + noun + "Binding.To" + parent + "." + id.name(),
"NOTE",
shape,
noun +
" binding of " +
tick(id.to_string()) +
" was added to the " +
kind(shape) +
" shape, " +
tick(shape.id.to_string()),
)
}
}
for id in old_ids {
if !new_ids.contains(id) {
let moved = service &&
noun == "Operation" &&
contained_operations(d.new_model, shape.id).contains(id)
emit(
events,
"Removed" + noun + "Binding.From" + parent + "." + id.name(),
if moved {
"NOTE"
} else {
"ERROR"
},
shape,
noun +
" binding of " +
tick(id.to_string()) +
" was removed from " +
kind(shape) +
" shape, " +
tick(shape.id.to_string()) +
(if moved {
", but the operation remains in the service closure through a resource"
} else {
""
}),
)
}
}
}
}