///|
fn value_blob_contract(
schema : Map[String, Json],
traits : Map[String, Json],
) -> Unit {
// Smithy length measures decoded bytes. Applying string minLength/maxLength
// would reject valid base64, so retain byte bounds explicitly as metadata.
schema.remove("minLength") |> ignore
schema.remove("maxLength") |> ignore
schema["contentEncoding"] = Json::string("base64")
schema["pattern"] = Json::string(
"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?(?![\\s\\S])",
)
if traits.get("smithy.api#length") is Some(length) {
schema["x-smithy-byteLength"] = length
}
}
///|
fn value_overrides(traits : Map[String, Json]) -> Bool {
[
"smithy.api#range", "smithy.api#length", "smithy.api#pattern", "smithy.api#enum",
"smithy.api#uniqueItems", "smithy.api#timestampFormat",
]
.iter()
.any(key => traits.contains(key))
}
///|
fn value_member_id(active : Array[String], name : String) -> String {
active.last().unwrap_or("") + "$" + name
}
///|
fn value_member_entries(shapes : Map[String, Json]) -> Array[(String, Json)] {
let entries = []
for id, shape in shapes {
guard shape is Object(fields) else { continue }
if fields.get("members") is Some(Object(members)) {
for name, entry in members {
if member_target(entry) is Some(_) {
entries.push((id + "$" + name, entry))
}
}
} else {
for name in ["key", "value", "member"] {
if fields.get(name) is Some(entry) && member_target(entry) is Some(_) {
entries.push((id + "$" + name, entry))
}
}
}
}
entries.sort_by((a, b) => a.0.lexical_compare(b.0))
entries
}
///|
/// Give constrained member variants their own definitions so recursion never
/// expands a member contract on the call stack. Allocate against every root
/// name, including user names that resemble a generated variant.
fn value_member_names(
shapes : Map[String, Json],
connected : Map[String, Bool],
names : Map[String, String],
service : String,
) -> Unit {
let used : Set[String] = Set([])
for _, name in names {
used.add(name)
}
for (id, entry) in value_member_entries(shapes) {
let root = id.split("$").next().unwrap().to_owned()
if service != "" && !connected.contains(root) {
continue
}
guard entry is { "traits": Object(traits), .. } &&
value_overrides(traits) &&
member_target(entry) is Some(target) &&
shapes.contains(target) else {
continue
}
let base = names.get(root).unwrap_or(root) +
"_" +
id.split("$").to_array()[1].to_owned()
let mut name = base
let mut suffix = 2
while used.contains(name) {
name = base + "_" + suffix.to_string()
suffix += 1
}
names[id] = name
used.add(name)
}
}
///|
/// Optional value-contract semantics. The reference-compatible converter keeps
/// its historical defaults unless a protocol exporter explicitly opts in.
fn value_numeric_bounds(schema : Map[String, Json], kind : String) -> Unit {
let bounds = match kind {
"byte" => Some(("-128", "127"))
"short" => Some(("-32768", "32767"))
"integer" => Some(("-2147483648", "2147483647"))
"long" => Some(("-9223372036854775808", "9223372036854775807"))
"float" =>
Some(
(
"-340282346638528859811704183484516925440", "340282346638528859811704183484516925440",
),
)
"double" =>
Some(
(
"-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368",
"179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368",
),
)
_ => None
}
if ["byte", "short", "integer", "long", "bigInteger", "intEnum"].contains(
kind,
) {
schema["type"] = Json::string("integer")
}
if bounds is Some((minimum, maximum)) {
for
(key, text, lower) in [
("minimum", minimum, true),
("maximum", maximum, false),
] {
let bound = @smithy.NodeNumber::parse(text).unwrap()
let current = schema
.get(key)
.bind(value => @smithy.NodeNumber::parse(value.stringify()))
if current is None ||
(
current is Some(value) &&
(if lower {
value.numeric_compare(bound) < 0
} else {
value.numeric_compare(bound) > 0
})
) {
schema[key] = bound.to_json()
}
}
}
}
///|
fn value_collection_contract(
schema : Map[String, Json],
shape : Json,
kind : String,
) -> Unit {
if kind == "structure" || kind == "union" {
schema["additionalProperties"] = Json::boolean(false)
if schema.get("properties") is None {
schema["properties"] = Json::object(Map([]))
}
}
if kind == "union" {
schema["type"] = Json::string("object")
schema["minProperties"] = Json::number(1)
schema["maxProperties"] = Json::number(1)
// oneOf branches describe cardinality; the outer property table supplies
// the complete allowed member set for additionalProperties:false.
let properties : Map[String, Json] = Map([])
if schema.get("oneOf") is Some(Array(options)) {
for option in options {
if option is { "properties": Object(entries), .. } {
for name, value in entries {
properties[name] = value
}
}
}
}
schema["properties"] = Json::object(properties)
}
if shape is { "traits": { "smithy.api#sparse": _, .. }, .. } {
let keyword = if kind == "list" { "items" } else { "additionalProperties" }
if schema.get(keyword) is Some(value) {
schema[keyword] = Json::object({
"anyOf": Json::array([
value,
Json::object({ "type": Json::string("null") }),
]),
})
}
}
}
///|
fn value_prelude_kind(target : String) -> String {
match target {
"smithy.api#String" => "string"
"smithy.api#Blob" => "blob"
"smithy.api#Boolean" | "smithy.api#PrimitiveBoolean" => "boolean"
"smithy.api#Byte" | "smithy.api#PrimitiveByte" => "byte"
"smithy.api#Short" | "smithy.api#PrimitiveShort" => "short"
"smithy.api#Integer" | "smithy.api#PrimitiveInteger" => "integer"
"smithy.api#Long" | "smithy.api#PrimitiveLong" => "long"
"smithy.api#Float" | "smithy.api#PrimitiveFloat" => "float"
"smithy.api#Double" | "smithy.api#PrimitiveDouble" => "double"
"smithy.api#BigInteger" => "bigInteger"
"smithy.api#BigDecimal" => "bigDecimal"
"smithy.api#Timestamp" => "timestamp"
_ => "document"
}
}