///|
fn[T] either(first : T?, second : T?) -> T? {
match first {
Some(_) => first
None => second
}
}
///|
fn data_shape(shape : @smithy.Shape) -> Bool {
match shape.body {
Operation(..) | Service(..) | Resource(..) => false
_ => shape.trait_value("smithy.api#mixin") is None
}
}
///|
fn shape_kind(body : @smithy.ShapeBody) -> ShapeKind? {
match body {
Simple(t) => Some(SimpleKind(t))
List(_) => Some(ListKind)
Map(..) => Some(MapKind)
Structure(_) => Some(StructureKind)
Union(_) => Some(UnionKind)
Enum(_) => Some(EnumKind)
IntEnum(_) => Some(IntEnumKind)
_ => None
}
}
///|
fn wire_name(
owner : @smithy.ShapeId,
text : String,
) -> WireName raise ContractError {
WireName::parse(text) catch {
InvalidIdentity(_, message) => raise InvalidDeclaration(owner, message)
}
}
///|
fn adapter_id(
owner : @smithy.ShapeId,
text : String,
) -> AdapterId raise ContractError {
AdapterId::parse(text) catch {
InvalidIdentity(_, message) => raise InvalidDeclaration(owner, message)
}
}
///|
fn annotation(shape : @smithy.Shape, id : String) -> Bool {
shape.trait_value(id) is Some(_)
}
///|
/// The first value at the highest precedence, refusing a disagreement at the same precedence.
fn[T : Eq] pick(
owner : @smithy.ShapeId,
what : String,
selected : T?,
defaults : T?,
base : Array[T],
) -> T? raise ContractError {
if selected is Some(_) {
return selected
}
if defaults is Some(_) {
return defaults
}
match base {
[] => None
[first, .. rest] => {
for other in rest {
if other != first {
raise ConflictingDeclaration(
owner,
"I found conflicting base declarations of the \{what}.",
)
}
}
Some(first)
}
}
}
///|
priv struct Draft {
id : @smithy.ShapeId
kind : ShapeKind
members : Array[MemberContract]
layout : ShapeLayout
accepts : Array[UnionLayout]
sparse : Bool
forms : Array[RawForm]
}
///|
fn adapter_signature(
owner : @smithy.ShapeId,
adapters : Map[AdapterId, AdapterSignature],
id : AdapterId,
) -> AdapterSignature raise ContractError {
match adapters.get(id) {
Some(signature) => signature
None => raise MissingAdapter(owner, id)
}
}
///|
fn scalar_output(kind : ShapeKind, timestamp_text : Bool) -> JsonKind? {
match kind {
SimpleKind(String | Blob) | EnumKind => Some(StringKind)
SimpleKind(Timestamp) =>
Some(if timestamp_text { StringKind } else { NumberKind })
SimpleKind(Boolean) => Some(BooleanKind)
SimpleKind(Document) => None
SimpleKind(_) | IntEnumKind => Some(NumberKind)
_ => None
}
}
///|
fn timestamp_text(shape : @smithy.Shape, member_shape : @smithy.Shape?) -> Bool {
let format = match
member_shape.bind(m => m.trait_value("smithy.api#timestampFormat")) {
Some(value) => Some(value)
None => shape.trait_value("smithy.api#timestampFormat")
}
match format {
Some(String("date-time" | "http-date")) => true
_ => false
}
}
///|
/// Prepare the detached wire contract of `root` under `profile`, or under the base rules when no profile
/// is selected. `adapters` declares the scalar adapters that the host has registered.
pub fn WireContract::prepare(
model : @smithy.Model,
root : @smithy.ShapeId,
profile~ : ProfileId? = None,
adapters~ : Array[AdapterSignature] = [],
max_shapes~ : Int32 = 100000,
) -> WireContract raise ContractError {
guard model.shapes.get(root) is Some(root_shape) &&
data_shape(root_shape) &&
!(root_shape.body is Member(_)) else {
raise InvalidTarget(
root, "I expected an existing data shape, not a member, as the codec root.",
)
}
let declared = declared_profiles(model)
let declaration = match profile {
None => None
Some(id) =>
match declared.get(id) {
Some(d) => Some(d)
None => raise UnknownProfile(id)
}
}
let rule_set = declaration.map(d => d.rule_set.to_string())
let defaults = match declaration {
None => empty_rule()
Some(d) =>
if d.rule_set == d.id {
d.defaults
} else {
declared.get(d.rule_set).unwrap().defaults
}
}
let catalog : Map[AdapterId, AdapterSignature] = Map([])
for signature in adapters {
catalog[signature.id] = signature
}
let drafts : Map[@smithy.ShapeId, Draft] = Map([])
let order : Array[@smithy.ShapeId] = []
let stack = [root]
while stack.pop() is Some(id) {
if drafts.contains(id) {
continue
}
if drafts.length() >= max_shapes {
raise PreparationLimit(
id,
"I reached the preparation limit of \{max_shapes} shapes.",
)
}
guard model.shapes.get(id) is Some(shape) &&
data_shape(shape) &&
shape_kind(shape.body) is Some(kind) else {
raise InvalidDeclaration(
id, "I expected a reachable data shape, but found a missing or nondata target.",
)
}
let encoding = shape_encoding(shape, declared)
let selected = rule_set
.bind(name => encoding.profiles.get(name))
.unwrap_or(empty_rule())
let base = encoding.base.unwrap_or(empty_rule())
let shape_rules = [selected, base]
for rule in shape_rules {
if rule.name is Some(_) ||
rule.tag is Some(_) ||
rule.aliases is Some(_) ||
rule.absent is Some(_) {
raise InvalidDeclaration(
id, "Member names, tags, aliases and absence policies belong on members, not on the shape.",
)
}
if rule.union_ is Some(_) && kind != UnionKind {
raise InvalidDeclaration(id, "A union rule needs a union shape.")
}
if rule.tuple is Some(_) && kind != StructureKind {
raise InvalidDeclaration(id, "A tuple rule needs a structure shape.")
}
if rule.dictionary is Some(_) && kind != MapKind {
raise InvalidDeclaration(id, "A dictionary rule needs a map shape.")
}
if rule.forms is Some(_) && !(kind is (UnionKind | StructureKind)) {
raise InvalidDeclaration(id, "Forms need a union or structure shape.")
}
if rule.adapter is Some(_) &&
!(kind is (SimpleKind(_) | EnumKind | IntEnumKind)) {
raise InvalidDeclaration(id, "A scalar adapter needs a simple shape.")
}
}
if annotation(shape, untagged_trait) && kind != UnionKind {
raise InvalidDeclaration(id, "The untagged trait needs a union shape.")
}
// Members.
let member_ids = match shape.body {
Structure(ids) | Union(ids) => ids.copy()
List(member_id) => [member_id]
Map(key~, value~) => [key, value]
_ => []
}
let members : Array[MemberContract] = []
for member_id in member_ids {
guard member_id.root() == id &&
member_id.member_name() is Some(name) &&
model.shapes.get(member_id) is Some(member_shape) &&
member_shape.body is Member(target~) &&
model.shapes.get(target) is Some(target_shape) &&
data_shape(target_shape) &&
!(target_shape.body is Member(_)) else {
raise InvalidDeclaration(
id, "I expected declared members that target existing data shapes.",
)
}
let member_encoding = shape_encoding(member_shape, declared)
let member_selected = rule_set
.bind(name => member_encoding.profiles.get(name))
.unwrap_or(empty_rule())
let member_base = member_encoding.base.unwrap_or(empty_rule())
for rule in [member_selected, member_base] {
if rule.union_ is Some(_) ||
rule.tuple is Some(_) ||
rule.dictionary is Some(_) ||
rule.forms is Some(_) {
raise InvalidDeclaration(
member_id, "Union, tuple, dictionary and form rules belong on shapes, not on members.",
)
}
}
let json_name = match member_shape.trait_value("smithy.api#jsonName") {
None => []
Some(String(text)) => [text]
Some(_) =>
raise InvalidDeclaration(
member_id, "I expected a string JSON member name.",
)
}
let base_names = match member_base.name {
None => json_name
Some(text) => json_name + [text]
}
let wire = wire_name(
member_id,
pick(member_id, "member name", member_selected.name, None, base_names).unwrap_or(
name,
),
)
let tag = match
pick(
member_id,
"member tag",
member_selected.tag,
None,
member_base.tag.map(t => [t]).unwrap_or([]),
) {
Some(text) => wire_name(member_id, text)
None => wire
}
let aliases = either(member_selected.aliases, member_base.aliases)
.unwrap_or([])
.map(text => wire_name(member_id, text))
let adapter = match
pick(
member_id,
"member adapter",
member_selected.adapter,
None,
member_base.adapter.map(t => [t]).unwrap_or([]),
) {
Some(text) => Some(adapter_id(member_id, text))
None => None
}
let target_kind = shape_kind(target_shape.body).unwrap()
if adapter is Some(adapter) {
guard target_kind is (SimpleKind(_) | EnumKind | IntEnumKind) &&
!(target_kind is SimpleKind(Document)) else {
raise InvalidDeclaration(
member_id, "A scalar adapter needs a member that targets a simple shape.",
)
}
let signature = adapter_signature(member_id, catalog, adapter)
guard scalar_output(
target_kind,
timestamp_text(target_shape, Some(member_shape)),
) ==
Some(signature.output) else {
raise InvalidDeclaration(
member_id,
"Adapter \{adapter} produces a \{signature.output} value, which the target cannot hold.",
)
}
}
let empty_as_absent = annotation(member_shape, empty_as_absent_trait)
let required = annotation(member_shape, "smithy.api#required")
let null_when_absent = match
either(member_selected.absent, member_base.absent) {
None | Some("omit") => false
Some("null") => true
Some(other) =>
raise UnsupportedDeclaration(
member_id,
"I do not implement the absence policy \{other.escape()}.",
)
}
if null_when_absent && (required || kind != StructureKind) {
raise InvalidDeclaration(
member_id, "The null absence policy needs an optional structure member.",
)
}
if empty_as_absent &&
(required || !(target_kind is (StructureKind | ListKind | MapKind))) {
raise InvalidDeclaration(
member_id, "emptyAsAbsent needs an optional member that targets a structure, list or map.",
)
}
members.push({
name,
member_id,
target,
required,
wire,
aliases,
tag,
adapter,
empty_as_absent,
null_when_absent,
flatten: member_selected.flatten || member_base.flatten,
})
stack.push(target)
}
// Wire names are unique within an object layout.
if kind is (StructureKind | UnionKind) {
let names : Map[String, String] = Map([])
for m in members {
let spellings = [m.wire] + m.aliases
let seen = if kind == UnionKind {
[m.tag] + m.aliases
} else {
spellings
}
for spelling in seen {
let text = spelling.to_string()
if names.get(text) is Some(other) {
raise InvalidDeclaration(
id,
"Members \{other} and \{m.name} use the same wire name \{text.escape()}.",
)
}
names[text] = m.name
}
}
}
let sparse = annotation(shape, "smithy.api#sparse")
let accepts : Array[UnionLayout] = []
let layout = match kind {
StructureKind =>
match either(selected.tuple, base.tuple) {
None => ObjectStructure
Some(t) => TupleStructure(tuple_layout(id, members, t))
}
UnionKind => {
let base_unions : Array[RawUnion] = []
if base.union_ is Some(u) {
base_unions.push(u)
}
if annotation(shape, untagged_trait) {
base_unions.push({
style: "untagged",
tag: None,
content: None,
unit: None,
accept: [],
})
}
let rule = match selected.union_ {
Some(u) => Some(u)
None =>
match defaults.union_ {
Some(u) => Some(u)
None =>
match base_unions {
[] => None
[first, .. rest] => {
for other in rest {
if other.style != first.style {
raise ConflictingDeclaration(
id,
"I found conflicting base union styles \{first.style} and \{other.style}.",
)
}
}
Some(first)
}
}
}
}
match rule {
Some(r) =>
for alternative in r.accept {
let layout = union_layout(id, members, Some(alternative))
guard layout
is (External | Internal(..) | Adjacent(..) | TagArray(..)) else {
raise InvalidDeclaration(
id, "An accepted union layout must be external, internal, adjacent or a tag array.",
)
}
if layout is TagArray(..) &&
(
r.style == "tagArray" ||
accepts.iter().any(a => a is TagArray(..))
) {
raise InvalidDeclaration(
id, "A union can read only one tag-array layout.",
)
}
accepts.push(layout)
}
None => ()
}
UnionShape(union_layout(id, members, rule))
}
ListKind => ListShape
MapKind => {
let rule = either(
selected.dictionary,
either(defaults.dictionary, base.dictionary),
)
Dictionary(dictionary_layout(id, members, rule, catalog))
}
_ => {
let adapter = match
pick(
id,
"scalar adapter",
selected.adapter,
None,
base.adapter.map(t => [t]).unwrap_or([]),
) {
Some(text) => Some(adapter_id(id, text))
None => None
}
if adapter is Some(adapter) {
let signature = adapter_signature(id, catalog, adapter)
guard scalar_output(kind, timestamp_text(shape, None)) ==
Some(signature.output) else {
raise InvalidDeclaration(
id,
"Adapter \{adapter} produces a \{signature.output} value, which the shape cannot hold.",
)
}
}
Scalar(adapter)
}
}
drafts[id] = {
id,
kind,
members,
layout,
accepts,
sparse,
forms: either(selected.forms, base.forms).unwrap_or([]),
}
order.push(id)
}
// Second pass: decisions that read other shapes.
let shapes : Map[@smithy.ShapeId, ShapeContract] = Map([])
for id in order {
let draft = drafts.get(id).unwrap()
for alternative in draft.accepts {
if alternative is Internal(tag~) {
check_internal(id, draft, drafts, model, tag)
}
}
if draft.kind == StructureKind {
check_flatten(draft, drafts)
}
let layout = match draft.layout {
UnionShape(Untagged(_)) =>
UnionShape(Untagged(untagged_kinds(draft, drafts, catalog, model)))
UnionShape(Internal(tag~)) => {
check_internal(id, draft, drafts, model, tag)
draft.layout
}
TupleStructure(t) => {
if t.rest is Some(rest) {
guard drafts.get(draft.members[rest].target) is Some(target) &&
target.kind == ListKind else {
raise InvalidDeclaration(
id,
"The rest member \{draft.members[rest].name} needs a list target.",
)
}
}
draft.layout
}
other => other
}
let forms = prepare_forms(draft, layout, drafts, catalog, model)
shapes[id] = {
id,
kind: draft.kind,
members: draft.members,
layout,
accepts: draft.accepts,
sparse: draft.sparse,
forms,
}
}
{
root,
profile,
writer: declaration.map(d => d.writer).unwrap_or(Canonical),
unknown_members: declaration
.map(d => d.unknown_members)
.unwrap_or(RejectUnknown),
fingerprint: fingerprint(model, root, order),
shapes,
}
}
///|
fn is_unit(model : @smithy.Model, id : @smithy.ShapeId) -> Bool {
match model.shapes.get(id) {
Some(shape) =>
shape.trait_value("smithy.api#unitType") is Some(_) ||
(
shape.body is Structure(ids) &&
ids.is_empty() &&
id.to_string() == "smithy.api#Unit"
)
None => false
}
}
///|
fn tuple_layout(
id : @smithy.ShapeId,
members : Array[MemberContract],
rule : RawTuple,
) -> TupleLayout raise ContractError {
let index = fn(name : String) raise ContractError {
for i, m in members {
if m.name == name {
return i
}
}
raise InvalidDeclaration(
id,
"The tuple names \{name.escape()}, which is not a member.",
)
}
let fixed : Array[Int32] = []
let used : Set[Int32] = Set([])
for name in rule.members {
let i = index(name)
if used.contains(i) {
raise InvalidDeclaration(
id,
"The tuple lists member \{name} at two positions.",
)
}
used.add(i)
fixed.push(i)
}
let rest = match rule.rest {
None => None
Some(name) => {
let i = index(name)
if used.contains(i) {
raise InvalidDeclaration(
id,
"The rest member \{name} also has a fixed position.",
)
}
used.add(i)
Some(i)
}
}
for i, m in members {
if !used.contains(i) {
raise InvalidDeclaration(id, "Member \{m.name} has no tuple position.")
}
}
let optional = match rule.optional {
None => None
Some("omit") => Some(OmitTail)
Some("null") => Some(NullTail)
Some(other) =>
raise UnsupportedDeclaration(
id,
"I do not implement the optional-position policy \{other.escape()}.",
)
}
let mut seen_optional = false
for i in fixed {
let m = members[i]
if !m.required {
if optional is None {
raise InvalidDeclaration(
id,
"Optional member \{m.name} has a position, so the tuple needs an optional policy.",
)
}
seen_optional = true
} else if seen_optional && optional == Some(OmitTail) {
raise InvalidDeclaration(
id,
"Required member \{m.name} follows an optional position, which leaves a hole.",
)
}
}
if rest is Some(_) && seen_optional && optional == Some(OmitTail) {
raise InvalidDeclaration(
id, "A rest list cannot follow omitted optional positions.",
)
}
{ fixed, rest, optional, }
}
///|
fn union_layout(
id : @smithy.ShapeId,
members : Array[MemberContract],
rule : RawUnion?,
) -> UnionLayout raise ContractError {
guard rule is Some(rule) else { return External }
let check_unused = fn(names : Array[(String, String?)]) raise ContractError {
for entry in names {
if entry.1 is Some(_) {
raise InvalidDeclaration(
id,
"The \{rule.style} union style has no \{entry.0} setting.",
)
}
}
}
match rule.style {
"external" => {
check_unused([
("tag", rule.tag),
("content", rule.content),
("unit", rule.unit),
])
External
}
"internal" => {
check_unused([("content", rule.content), ("unit", rule.unit)])
guard rule.tag is Some(tag) else {
raise InvalidDeclaration(
id, "An internally tagged union needs a tag field.",
)
}
Internal(tag=wire_name(id, tag))
}
"adjacent" => {
check_unused([("unit", rule.unit)])
guard rule.tag is Some(tag) && rule.content is Some(content) else {
raise InvalidDeclaration(
id, "An adjacently tagged union needs tag and content fields.",
)
}
guard tag != content else {
raise InvalidDeclaration(
id, "An adjacently tagged union needs distinct tag and content fields.",
)
}
Adjacent(tag=wire_name(id, tag), content=wire_name(id, content))
}
"tagArray" => {
check_unused([("tag", rule.tag), ("content", rule.content)])
let tag_only_unit = match rule.unit {
None | Some("tagOnly") => true
Some("slot") => false
Some(other) =>
raise UnsupportedDeclaration(
id,
"I do not implement the tag-array unit mode \{other.escape()}.",
)
}
TagArray(tag_only_unit~)
}
"untagged" => {
check_unused([
("tag", rule.tag),
("content", rule.content),
("unit", rule.unit),
])
ignore(members)
Untagged([])
}
other =>
raise UnsupportedDeclaration(
id,
"I do not implement the union style \{other.escape()}.",
)
}
}
///|
fn dictionary_layout(
id : @smithy.ShapeId,
members : Array[MemberContract],
rule : RawDictionary?,
catalog : Map[AdapterId, AdapterSignature],
) -> DictionaryLayout raise ContractError {
if members.length() == 2 && members[0].adapter is Some(_) {
raise InvalidDeclaration(
id, "Declare a map key adapter with the dictionary rule, not on the key member.",
)
}
guard rule is Some(rule) else {
return {
entries: false,
accept_other: false,
key_adapter: None,
order: LexicalKeys,
}
}
let entries = match rule.layout {
"object" => false
"entries" => true
other =>
raise UnsupportedDeclaration(
id,
"I do not implement the dictionary layout \{other.escape()}.",
)
}
let accept_other = match rule.accept {
[] => false
["object"] if entries => true
["entries"] if !entries => true
_ =>
raise InvalidDeclaration(
id, "A dictionary can accept only the other layout.",
)
}
let key_adapter = rule.key.map(text => adapter_id(id, text))
let order = match rule.order {
None | Some("key") => LexicalKeys
Some("adapter") => AdapterKeys
Some("preserve") => PreservedKeys
Some(other) =>
raise UnsupportedDeclaration(
id,
"I do not implement the dictionary order \{other.escape()}.",
)
}
match key_adapter {
Some(adapter) => {
let signature = adapter_signature(id, catalog, adapter)
guard signature.output == StringKind else {
raise InvalidDeclaration(
id,
"Key adapter \{adapter} must produce a string key.",
)
}
if (!entries || accept_other) && !signature.inputs.contains(StringKind) {
raise InvalidDeclaration(
id,
"Key adapter \{adapter} must read strings in an object dictionary.",
)
}
if order == AdapterKeys && !signature.orders_keys {
raise InvalidDeclaration(id, "Adapter \{adapter} does not order keys.")
}
}
None =>
if order == AdapterKeys {
raise InvalidDeclaration(id, "Adapter key order needs a key adapter.")
}
}
{ entries, accept_other, key_adapter, order, }
}
///|
/// The JSON kinds that the reader of a shape accepts, through its layout and its forms.
fn accepted_kinds(
target : @smithy.ShapeId,
member_id : @smithy.ShapeId?,
adapter : AdapterId?,
drafts : Map[@smithy.ShapeId, Draft],
catalog : Map[AdapterId, AdapterSignature],
model : @smithy.Model,
visiting : Set[@smithy.ShapeId],
) -> Array[JsonKind] raise ContractError {
let draft = drafts.get(target).unwrap()
let adapter = match adapter {
Some(a) => Some(a)
None =>
match draft.layout {
Scalar(a) => a
_ => None
}
}
if adapter is Some(adapter) {
return adapter_signature(target, catalog, adapter).inputs.copy()
}
let out : Array[JsonKind] = []
let add = fn(kind : JsonKind) { if !out.contains(kind) { out.push(kind) } }
for form in draft.forms {
match JsonKind::parse(form.kind) {
Some(kind) => add(kind)
None => ()
}
}
match draft.layout {
ObjectStructure => add(ObjectKind)
TupleStructure(_) | ListShape => add(ArrayKind)
Dictionary(d) => {
add(if d.entries { ArrayKind } else { ObjectKind })
if d.accept_other {
add(if d.entries { ObjectKind } else { ArrayKind })
}
}
UnionShape(External | Internal(..) | Adjacent(..)) => add(ObjectKind)
UnionShape(TagArray(..)) => add(ArrayKind)
UnionShape(Untagged(_)) => {
if visiting.contains(target) {
raise InvalidDeclaration(
target, "An untagged union cannot contain itself without a tag.",
)
}
visiting.add(target)
for m in draft.members {
for
kind in accepted_kinds(
m.target,
Some(m.member_id),
m.adapter,
drafts,
catalog,
model,
visiting,
) {
add(kind)
}
}
visiting.remove(target)
}
Scalar(_) =>
match draft.kind {
SimpleKind(Document) => {
add(NullKind)
add(BooleanKind)
add(NumberKind)
add(StringKind)
add(ArrayKind)
add(ObjectKind)
}
kind =>
match
scalar_output(
kind,
timestamp_text(
model.shapes.get(target).unwrap(),
member_id.bind(m => model.shapes.get(m)),
),
) {
Some(k) => add(k)
None => ()
}
}
}
for alternative in draft.accepts {
for kind in layout_kinds(UnionShape(alternative)) {
add(kind)
}
}
out
}
///|
fn untagged_kinds(
draft : Draft,
drafts : Map[@smithy.ShapeId, Draft],
catalog : Map[AdapterId, AdapterSignature],
model : @smithy.Model,
) -> Array[(JsonKind, Int32)] raise ContractError {
let out : Array[(JsonKind, Int32)] = []
for i, m in draft.members {
if is_unit(model, m.target) {
raise InvalidDeclaration(
draft.id,
"Untagged member \{m.name} has no value to select it.",
)
}
let kinds = accepted_kinds(
m.target,
Some(m.member_id),
m.adapter,
drafts,
catalog,
model,
Set([draft.id]),
)
for kind in kinds {
for entry in out {
if entry.0 == kind {
raise InvalidDeclaration(
draft.id,
"Untagged members \{draft.members[entry.1].name} and \{m.name} both accept JSON \{kind} values.",
)
}
}
out.push((kind, i))
}
}
out
}
///|
fn layout_kinds(layout : ShapeLayout) -> Array[JsonKind] {
match layout {
ObjectStructure | UnionShape(External | Internal(..) | Adjacent(..)) =>
[ObjectKind]
Dictionary(d) =>
if d.accept_other {
[ArrayKind, ObjectKind]
} else {
[if d.entries { ArrayKind } else { ObjectKind }]
}
TupleStructure(_) | ListShape | UnionShape(TagArray(..)) => [ArrayKind]
UnionShape(Untagged(kinds)) => kinds.map(entry => entry.0)
Scalar(_) => []
}
}
///|
fn prepare_forms(
draft : Draft,
layout : ShapeLayout,
drafts : Map[@smithy.ShapeId, Draft],
catalog : Map[AdapterId, AdapterSignature],
model : @smithy.Model,
) -> Array[Form] raise ContractError {
let id = draft.id
let out : Array[Form] = []
for raw in draft.forms {
guard JsonKind::parse(raw.kind) is Some(kind) else {
raise UnsupportedDeclaration(
id,
"I do not implement the form reader kind \{raw.kind.escape()}.",
)
}
let adapter = raw.adapter.map(text => adapter_id(id, text))
if adapter is Some(a) {
let signature = adapter_signature(id, catalog, a)
guard signature.inputs.contains(kind) else {
raise InvalidDeclaration(
id,
"Form guard adapter \{a} does not read \{kind} values.",
)
}
}
let field = raw.field.map(text => wire_name(id, text))
let absent_field = raw.absent_field.map(text => wire_name(id, text))
if (field is Some(_) || absent_field is Some(_)) && kind != ObjectKind {
raise InvalidDeclaration(id, "A field marker needs an object form.")
}
// The members that the form binds belong to the payload of a union member, or to the structure.
let (selected_member, payload) = match draft.kind {
UnionKind => {
guard raw.union_member is Some(name) &&
draft.members.search_by(m => m.name == name) is Some(i) else {
raise InvalidDeclaration(id, "A union form needs a union member.")
}
(Some(i), drafts.get(draft.members[i].target).unwrap())
}
_ => {
if raw.union_member is Some(_) {
raise InvalidDeclaration(
id, "A structure form selects no union member.",
)
}
(None, draft)
}
}
let index = fn(name : String) raise ContractError {
guard payload.kind == StructureKind &&
payload.members.search_by(m => m.name == name) is Some(i) else {
raise InvalidDeclaration(
id,
"The form names \{name.escape()}, which is not a payload member.",
)
}
i
}
let binding = match (raw.bind, raw.positions) {
(Some(_), Some(_)) =>
raise InvalidDeclaration(
id, "A form binds either one member or positions.",
)
(Some(name), None) => {
if raw.rest is Some(_) {
raise InvalidDeclaration(id, "A rest member needs positions.")
}
let i = index(name)
let accepted = accepted_kinds(
payload.members[i].target,
Some(payload.members[i].member_id),
payload.members[i].adapter,
drafts,
catalog,
model,
Set([]),
)
guard accepted.contains(kind) else {
raise InvalidDeclaration(
id,
"Member \{name} does not accept the form's \{kind} values.",
)
}
Whole(i)
}
(None, Some(names)) => {
guard kind == ArrayKind else {
raise InvalidDeclaration(
id, "A positional form needs an array reader.",
)
}
let fixed : Array[Int32] = []
for name in names {
let i = index(name)
if fixed.contains(i) {
raise InvalidDeclaration(id, "The form lists member \{name} twice.")
}
fixed.push(i)
}
let rest = match raw.rest {
None => None
Some(name) => {
let i = index(name)
guard !fixed.contains(i) &&
drafts.get(payload.members[i].target).unwrap().kind == ListKind else {
raise InvalidDeclaration(
id,
"The form's rest member \{name} needs a list target and no fixed position.",
)
}
Some(i)
}
}
Positional(fixed~, rest~)
}
(None, None) =>
if selected_member is Some(_) {
let accepted = accepted_kinds(
payload.id,
Some(draft.members[selected_member.unwrap()].member_id),
draft.members[selected_member.unwrap()].adapter,
drafts,
catalog,
model,
Set([]),
)
guard accepted.contains(kind) else {
raise InvalidDeclaration(
id,
"The payload does not accept the form's \{kind} values.",
)
}
Whole(-1)
} else {
raise InvalidDeclaration(
id, "A form needs a bound member or positions.",
)
}
}
let bound : Array[Int32] = match binding {
Whole(i) =>
if i >= 0 {
[i]
} else {
Array::makei(payload.members.length(), i => i)
}
Positional(fixed~, rest~) =>
match rest {
Some(r) => fixed + [r]
None => fixed
}
}
// A reader leaves unbound members absent, so they must be optional.
if payload.kind == StructureKind {
for i, m in payload.members {
if m.required && !bound.contains(i) {
raise InvalidDeclaration(
id,
"The form leaves required member \{m.name} without a value.",
)
}
}
}
let writer = match raw.write {
None => None
Some((empty, present)) => {
let empty = empty.map(index)
let present = present.map(index)
if payload.kind == StructureKind {
for i, m in payload.members {
if !bound.contains(i) && !empty.contains(i) {
raise InvalidDeclaration(
id,
"The form's writer would drop member \{m.name}; list it as empty.",
)
}
}
}
Some({ empty, present, })
}
}
out.push({
union_member: selected_member,
reader: { kind, adapter, field, absent: absent_field, },
binding,
writer,
})
}
// Reader rules must not overlap without a guard that can tell them apart.
let guarded = fn(form : Form) {
form.reader.adapter is Some(_) ||
form.reader.field is Some(_) ||
form.reader.absent is Some(_)
}
for i, form in out {
if (layout_kinds(layout) +
draft.accepts.map(a => layout_kinds(UnionShape(a))).flatten()).contains(
form.reader.kind,
) &&
!guarded(form) {
raise InvalidDeclaration(
id,
"A \{form.reader.kind} form overlaps the shape's own \{form.reader.kind} layout without a guard.",
)
}
for j in 0.. Unit raise ContractError {
for m in draft.members {
let target = drafts.get(m.target).unwrap()
if is_unit(model, m.target) {
continue
}
guard target.layout is ObjectStructure else {
raise InvalidDeclaration(
id,
"Internally tagged member \{m.name} needs an object structure payload.",
)
}
for field in target.members {
if field.wire == tag || field.aliases.contains(tag) {
raise InvalidDeclaration(
id,
"The discriminator \{tag} collides with field \{field.name} of member \{m.name}.",
)
}
}
}
}
///|
/// A flattened member reads the fields that no sibling claims as one external union.
fn check_flatten(
draft : Draft,
drafts : Map[@smithy.ShapeId, Draft],
) -> Unit raise ContractError {
let flattened = draft.members.filter(m => m.flatten)
if flattened.is_empty() {
return
}
guard flattened.length() == 1 && draft.layout is ObjectStructure else {
raise InvalidDeclaration(
draft.id,
"An object structure can flatten at most one member.",
)
}
let m = flattened[0]
let target = drafts.get(m.target).unwrap()
guard target.layout is UnionShape(External) && target.forms.is_empty() else {
raise InvalidDeclaration(
draft.id,
"Flattened member \{m.name} needs an external union without forms.",
)
}
for alternative in target.members {
for tag in [alternative.tag] + alternative.aliases {
for sibling in draft.members {
if !sibling.flatten &&
(sibling.wire == tag || sibling.aliases.contains(tag)) {
raise InvalidDeclaration(
draft.id,
"Flattened tag \{tag} collides with member \{sibling.name}.",
)
}
}
}
}
}