///|
priv suberror BuildError {
InvalidShape(String)
}
///|
fn require_id(text : String) -> ShapeId raise BuildError {
match ShapeId::parse(text) {
Some(id) => id
None =>
raise InvalidShape("I expected an absolute shape ID, but found \{text}.")
}
}
///|
fn Assembly::lower_traits(
self : Assembly,
traits : Map[String, Json]?,
) -> Map[ShapeId, NodeValue] raise {
let out : Map[ShapeId, NodeValue] = Map([])
for id, value in traits.unwrap_or(Map([])) {
let value = if value is Null {
match self.kinds.get(id) {
Some("structure" | "map") => Json::empty_object()
Some("list" | "set") => Json::array([])
_ => value
}
} else {
value
}
out[require_id(id)] = NodeValue::from_json(value)
}
out
}
///|
fn references(
values : Array[@syntax.JsonReference]?,
) -> Array[ShapeId] raise BuildError {
values.unwrap_or([]).map(r => require_id(r.target))
}
///|
fn reference_map(
values : Map[String, @syntax.JsonReference]?,
) -> Map[String, ShapeId] raise BuildError {
let out : Map[String, ShapeId] = Map([])
for name, value in values.unwrap_or(Map([])) {
out[name] = require_id(value.target)
}
out
}
///|
fn Assembly::lower_member(
self : Assembly,
shapes : Map[ShapeId, Shape],
raw : RawShape,
name : String,
member_ : @syntax.JsonMember,
inherited : Map[String, Json],
flattened : Map[String, RawShape],
) -> ShapeId raise {
let id = require_id(raw.id + "$" + name)
let local = self.raw.get(raw.id).unwrap_or(raw)
let local_fields = object(
raw_members(local.fields).get(name).unwrap_or(Json::empty_object()),
)
let inherited_fields = object(
raw_members(inherited).get(name).unwrap_or(Json::empty_object()),
)
let traits = self.lower_traits(member_.traits)
let local_keys = copy(object_field(local_fields, "traits"))
for application in self.applications {
if application.target == id.to_string() {
override(local_keys, application.traits)
}
}
let introduced_traits : Map[ShapeId, NodeValue] = Map([])
for key, _ in local_keys {
let trait_id = require_id(key)
if traits.get(trait_id) is Some(value) {
introduced_traits[trait_id] = value
}
}
let mixins : Array[ShapeId] = []
for reference_ in array_field(local.fields, "mixins") {
if string_field(object(reference_), "target") is Some(base_id) &&
flattened.get(base_id) is Some(base) &&
raw_members(base.fields).contains(name) {
mixins.push(require_id(base_id + "$" + name))
}
}
shapes[id] = {
id,
body: Member(target=require_id(member_.target)),
traits,
mixins,
provenance: Some({
introduced_traits,
inherited_traits: self.lower_traits(
Some(object_field(inherited_fields, "traits")),
),
introduced_fields: node_fields(local_fields),
inherited_fields: node_fields(inherited_fields),
legacy_set: false,
}),
source: self.locations
.get(id.to_string())
.unwrap_or({
..raw.source,
path: raw.source.path + "/members/" + pointer(name),
}),
}
id
}
///|
fn Assembly::lower_members(
self : Assembly,
shapes : Map[ShapeId, Shape],
raw : RawShape,
members : Map[String, @syntax.JsonMember]?,
inherited : Map[String, Json],
flattened : Map[String, RawShape],
) -> Array[ShapeId] raise {
let out = []
for name, member_ in members.unwrap_or(Map([])) {
out.push(
self.lower_member(shapes, raw, name, member_, inherited, flattened),
)
}
out
}
///|
fn Assembly::lower(
self : Assembly,
raw : RawShape,
shapes : Map[ShapeId, Shape],
flattened : Map[String, RawShape],
) -> Unit raise {
let local = self.raw.get(raw.id).unwrap_or(raw)
let inherited = self.inherited_fields(local, flattened)
let ast = @syntax.decode_json_ast({
"smithy": "2.0",
"shapes": Json::object(Map([(raw.id, Json::object(raw.fields))])),
})
let shape = ast.shapes.unwrap().get(raw.id).unwrap()
let id = require_id(raw.id)
let body : ShapeBody = match shape.kind {
Simple(type_) => Simple(type_)
List(member_) | Set(member_) => {
if shape.kind is Set(_) {
self.problem(
"SM-MODEL-015",
"I found deprecated set shape \{id}; I recommend a list with uniqueItems.",
raw.source,
shape=id.to_string(),
event_id="ModelDeprecation",
severity=Warning,
)
}
guard member_ is Some(member_) else {
raise InvalidShape(
"I expected the member of list \{id}, but found no member.",
)
}
List(
self.lower_member(shapes, raw, "member", member_, inherited, flattened),
)
}
Map(key~, value~) => {
guard key is Some(key) && value is Some(value) else {
raise InvalidShape(
"I expected both key and value members of map \{id}.",
)
}
Map(
key=self.lower_member(shapes, raw, "key", key, inherited, flattened),
value=self.lower_member(
shapes, raw, "value", value, inherited, flattened,
),
)
}
Structure(members) =>
Structure(self.lower_members(shapes, raw, members, inherited, flattened))
Union(members) =>
Union(self.lower_members(shapes, raw, members, inherited, flattened))
Enum(members) =>
Enum(self.lower_members(shapes, raw, members, inherited, flattened))
IntEnum(members) =>
IntEnum(self.lower_members(shapes, raw, members, inherited, flattened))
Operation(operation) =>
Operation(
input=require_id(
operation.input.map(r => r.target).unwrap_or("smithy.api#Unit"),
),
output=require_id(
operation.output.map(r => r.target).unwrap_or("smithy.api#Unit"),
),
errors=references(operation.errors),
)
Service(service) => {
let rename : Map[ShapeId, String] = Map([])
for key, value in service.rename.unwrap_or(Map([])) {
rename[require_id(key)] = value
}
Service(
version=service.version.unwrap_or(""),
operations=references(service.operations),
resources=references(service.resources),
errors=references(service.errors),
rename~,
)
}
Resource(resource) =>
Resource(
identifiers=reference_map(resource.identifiers),
properties=reference_map(resource.properties),
create=resource.create.map(r => require_id(r.target)),
put=resource.put.map(r => require_id(r.target)),
read=resource.read.map(r => require_id(r.target)),
update=resource.update.map(r => require_id(r.target)),
delete=resource.delete.map(r => require_id(r.target)),
list=resource.list.map(r => require_id(r.target)),
operations=references(resource.operations),
collectionOperations=references(resource.collectionOperations),
resources=references(resource.resources),
)
Apply => raise InvalidShape("I found an unprocessed apply entry \{id}.")
}
let traits = self.lower_traits(shape.traits)
if shape.kind is Set(_) {
traits[require_id("smithy.api#uniqueItems")] = Object(Map([]))
}
shapes[id] = {
id,
body,
traits,
mixins: references(shape.mixins),
provenance: Some({
introduced_traits: self.lower_traits(
Some(object_field(local.fields, "traits")),
),
inherited_traits: self.lower_traits(
Some(object_field(inherited, "traits")),
),
introduced_fields: node_fields(local.fields),
inherited_fields: node_fields(inherited),
legacy_set: shape.kind is Set(_),
}),
source: raw.source,
}
}
///|
/// Compare duplicate shape definitions independent of traits and empty maps,
/// after inheritance has supplied each definition's structural properties.
fn structural(fields : Map[String, Json]) -> Json {
let out = copy(fields)
out.remove("traits") |> ignore
out.remove("mixins") |> ignore
let members = raw_members(fields)
let cleaned : Map[String, Json] = Map([])
for name, member_ in members {
let value = copy(object(member_))
value.remove("traits") |> ignore
cleaned[name] = Json::object(value)
}
set_members(out, cleaned)
for
key in [
"errors", "operations", "resources", "collectionOperations", "identifiers",
"properties", "rename",
] {
if out.get(key) == Some(Json::array([])) ||
out.get(key) == Some(Json::empty_object()) {
out.remove(key) |> ignore
}
}
if string_field(out, "type") == Some("operation") {
if !out.contains("input") {
out["input"] = reference("smithy.api#Unit")
}
if !out.contains("output") {
out["output"] = reference("smithy.api#Unit")
}
}
Json::object(out)
}
///|
fn Assembly::finish(self : Assembly) -> LoadResult {
self.apply_roots()
let flattened = self.flatten()
for (a, b) in self.duplicates {
let a = flattened.get(a.id).unwrap_or(a)
let b = self.flatten_one(b, flattened)
if is_builtin_source(a.source.file) && !is_builtin_source(b.source.file) {
let shape_traits_equal = try
NodeValue::from_json(Json::object(object_field(a.fields, "traits"))) ==
NodeValue::from_json(Json::object(object_field(b.fields, "traits")))
catch {
_ => false
}
let a_members = raw_members(a.fields)
let b_members = raw_members(b.fields)
let member_traits_equal = a_members
.iter()
.all(entry => {
let left = object_field(object(entry.1), "traits")
let right = object_field(
object(b_members.get(entry.0).unwrap_or(Json::null())),
"traits",
)
try
NodeValue::from_json(Json::object(left)) ==
NodeValue::from_json(Json::object(right))
catch {
_ => false
}
})
if structural(a.fields) != structural(b.fields) ||
!shape_traits_equal ||
!member_traits_equal {
self.problem(
"SM-MODEL-014",
"I cannot redefine the built-in shape \{b.id}.",
b.source,
shape=b.id,
)
}
continue
}
if structural(a.fields) != structural(b.fields) {
self.problem(
"SM-MODEL-003",
"I found conflicting definitions of shape \{b.id}.",
b.source,
shape=b.id,
)
}
}
let shapes : Map[ShapeId, Shape] = Map([])
for _, raw in flattened {
self.lower(raw, shapes, flattened) catch {
InvalidShape(message) =>
self.problem("SM-MODEL-010", message, raw.source, shape=raw.id)
@json.JsonDecodeError((path, message)) =>
self.problem(
"SM-MODEL-010",
message,
{ ..raw.source, path: path.to_string(), },
shape=raw.id,
event_id=if path.to_string().contains("/rename/") {
"Service"
} else {
"Model"
},
)
InvalidNode(message) =>
self.problem("SM-MODEL-001", message, raw.source, shape=raw.id)
error =>
self.problem(
"SM-MODEL-001",
"I could not assemble \{raw.id}: \{error}.",
raw.source,
shape=raw.id,
)
}
}
let model : Model = { shapes, metadata: self.metadata, }
self.validate(model)
let failed = self.diagnostics.iter().any(d => d.severity == Error)
{
model: if failed {
None
} else {
Some(model)
},
diagnostics: self.diagnostics,
validation_findings: model.collect_validation_events(
self.diagnostics.map(diagnostic_event),
),
}
}