///|
/// A prepared conversion context for protocol converters that need the complete
/// schema document plus schemas for individual members or filtered structures.
/// The encoded model and contextual names are prepared once, not per binding.
pub struct SchemaIndex {
shapes : Map[String, Json]
names : Map[String, String]
config : Config
document : Json
definitions : Map[String, @smithy.ShapeId]
}
///|
pub fn SchemaIndex::new(
model : @smithy.Model,
service~ : String = "",
config~ : Config = Config::new(),
) -> SchemaIndex raise {
let document = convert(model, service~, config~)
let ast = @syntax.encode_json_ast(model.to_json_ast())
guard ast is { "shapes": Object(shapes), .. } else {
raise InvalidConfiguration("Model has no shape table")
}
let names = schema_names(shapes, service, config)
let connected = connected_shapes(service, shapes)
if service == "" {
for id, _ in shapes {
connected[id] = true
}
}
deconflict_names(shapes, connected, names, service, config)
let definitions : Map[String, @smithy.ShapeId] = Map([])
for id, _ in connected {
if @smithy.ShapeId::parse(id) is Some(shape_id) &&
names.get(id) is Some(name) &&
shapes.get(id) is Some(shape) &&
is_definition_shape(shape, config) {
definitions[name] = shape_id
}
}
{ shapes, names, config, document, definitions, }
}
///|
/// Resolve a contextual component name within the selected service closure.
pub fn SchemaIndex::definition(
self : SchemaIndex,
name : String,
) -> @smithy.ShapeId? {
self.definitions.get(name)
}
///|
pub fn SchemaIndex::document(self : SchemaIndex) -> Json {
self.document
}
///|
pub fn SchemaIndex::name(self : SchemaIndex, id : @smithy.ShapeId) -> String {
self.names.get(id.root().to_string()).unwrap_or(id.name())
}
///|
/// Convert a value shape inline, retaining the shared contextual references.
pub fn SchemaIndex::shape(
self : SchemaIndex,
id : @smithy.ShapeId,
config~ : Config? = None,
) -> Json raise JsonSchemaError {
guard self.shapes.get(id.to_string()) is Some(shape) else {
raise InvalidConfiguration("Unknown schema shape `" + id.to_string() + "`")
}
shape_schema(
shape,
self.shapes,
[id.to_string()],
config.unwrap_or(self.config),
self.names,
)
}
///|
/// Convert a member using the same inline/reference rules as a full document.
pub fn SchemaIndex::schema_for_member(
self : SchemaIndex,
id : @smithy.ShapeId,
config~ : Config? = None,
) -> Json raise JsonSchemaError {
guard id.member_name() is Some(name) &&
self.shapes.get(id.root().to_string()) is Some(Object(parent)) else {
raise InvalidConfiguration("Unknown schema member `" + id.to_string() + "`")
}
let entries = match parent.get("members") {
Some(Object(members)) => members
_ => parent
}
guard entries.get(name) is Some(entry) && member_target(entry) is Some(_) else {
raise InvalidConfiguration("Unknown schema member `" + id.to_string() + "`")
}
member_schema(
entry,
self.shapes,
[],
config.unwrap_or(self.config),
self.names,
)
}
///|
/// Convert only the selected members of a structure. Filtering occurs before
/// required/property generation, so omitted HTTP bindings cannot leave stale
/// required properties in synthesized document payloads.
pub fn SchemaIndex::structure(
self : SchemaIndex,
id : @smithy.ShapeId,
selected : Array[@smithy.ShapeId],
) -> Json raise JsonSchemaError {
guard self.shapes.get(id.to_string()) is Some(Object(fields)) &&
fields.get("type") == Some(Json::string("structure")) else {
raise InvalidConfiguration(
"Expected schema structure `" + id.to_string() + "`",
)
}
let original = match fields.get("members") {
Some(Object(members)) => members
_ => Map([])
}
let members : Map[String, Json] = Map([])
for selected_id in selected {
guard selected_id.root() == id &&
selected_id.member_name() is Some(name) &&
original.get(name) is Some(value) else {
raise InvalidConfiguration(
"Selected member does not belong to `" + id.to_string() + "`",
)
}
members[name] = value
}
let filtered : Map[String, Json] = Map([])
for key, value in fields {
filtered[key] = value
}
filtered["members"] = Json::object(members)
shape_schema(
Json::object(filtered),
self.shapes,
[id.to_string()],
self.config,
self.names,
)
}