///|
/// One supported release interval of a bundle, with its semantic root and allowed profiles. The first
/// profile is the default.
pub struct ReleaseBinding {
interval : ReleaseInterval
root : @smithy.ShapeId
profiles : Array[ProfileId]
} derive(Debug)
///|
/// A versioned document bundle declared with the jsonBundle trait.
pub struct BundleDeclaration {
id : BundleId
declared_on : @smithy.ShapeId
version_field : WireName
version_adapter : AdapterId
releases : Array[ReleaseBinding]
} derive(Debug)
///|
/// The binding that supports `release`, if any.
pub fn BundleDeclaration::binding(
self : BundleDeclaration,
release : Release,
) -> ReleaseBinding? {
for binding in self.releases {
if binding.interval.contains(release) {
return Some(binding)
}
}
None
}
///|
fn parse_bundle(
model : @smithy.Model,
owner : @smithy.ShapeId,
value : @smithy.NodeValue,
profiles : Map[ProfileId, ProfileDeclaration],
) -> BundleDeclaration raise ContractError {
let f = fields(owner, value, "jsonBundle trait", ["id", "version", "releases"])
guard text_field(owner, f, "id", "jsonBundle trait") is Some(id) else {
raise InvalidDeclaration(owner, "I expected a bundle identifier.")
}
let id = BundleId::parse(id) catch {
InvalidIdentity(_, message) => raise InvalidDeclaration(owner, message)
}
guard f.get("version") is Some(version) else {
raise InvalidDeclaration(owner, "I expected the bundle version location.")
}
let v = fields(owner, version, "bundle version", ["field", "adapter"])
guard text_field(owner, v, "field", "bundle version") is Some(field) &&
text_field(owner, v, "adapter", "bundle version") is Some(adapter) else {
raise InvalidDeclaration(
owner, "I expected the version field and its adapter.",
)
}
guard f.get("releases") is Some(Array(items)) && !items.is_empty() else {
raise InvalidDeclaration(owner, "I expected release bindings.")
}
let releases : Array[ReleaseBinding] = []
for item in items {
let r = fields(owner, item, "release binding", [
"since", "until", "root", "profiles",
])
guard text_field(owner, r, "since", "release binding") is Some(since) &&
text_field(owner, r, "until", "release binding") is Some(until) &&
text_field(owner, r, "root", "release binding") is Some(root) &&
texts_field(owner, r, "profiles", "release binding") is Some(names) &&
!names.is_empty() else {
raise InvalidDeclaration(
owner, "I expected since, until, root and profiles in each release binding.",
)
}
let interval = ReleaseInterval::new(
Release::parse(since),
Release::parse(until),
) catch {
InvalidIdentity(_, message) => raise InvalidDeclaration(owner, message)
}
guard @smithy.ShapeId::parse(root) is Some(root) &&
model.shapes.get(root) is Some(root_shape) &&
root_shape.body is (Structure(_) | Union(_)) else {
raise InvalidDeclaration(
owner,
"I expected release root \{root.escape()} to be a structure or union.",
)
}
let ids : Array[ProfileId] = []
for name in names {
let profile = ProfileId::parse(name) catch {
InvalidIdentity(_, message) => raise InvalidDeclaration(owner, message)
}
guard profiles.contains(profile) else {
raise InvalidDeclaration(
owner,
"Release \{interval} names the undeclared profile \{profile}.",
)
}
ids.push(profile)
}
for previous in releases {
if previous.interval.overlaps(interval) {
raise InvalidDeclaration(
owner,
"Release intervals \{previous.interval} and \{interval} overlap.",
)
}
}
releases.push({ interval, root, profiles: ids, })
}
{
id,
declared_on: owner,
version_field: wire_name(owner, field),
version_adapter: adapter_id(owner, adapter),
releases,
}
}
///|
/// Every bundle declared in the model, by identifier.
pub fn declared_bundles(
model : @smithy.Model,
) -> Map[BundleId, BundleDeclaration] raise ContractError {
let profiles = declared_profiles(model)
let out : Map[BundleId, BundleDeclaration] = Map([])
let ids = model.shapes.keys().to_array()
ids.sort_by((a, b) => a.to_string().compare(b.to_string()))
for id in ids {
let shape = model.shapes.get(id).unwrap()
guard shape.trait_value(bundle_trait) is Some(value) else { continue }
let bundle = parse_bundle(model, id, value, profiles)
if out.get(bundle.id) is Some(previous) {
raise ConflictingDeclaration(
id,
"Bundle \{bundle.id} is already declared on \{previous.declared_on}.",
)
}
out[bundle.id] = bundle
}
out
}