///|
priv struct GoContext {
model : @smithy.Model
description : @jsonrpc.Description
selected : @jsonrpc.PeerSelection
module_name : String
package_name : String
provider : @codegen.SymbolProvider
shapes : Array[@smithy.ShapeId]
values : Map[@smithy.ShapeId, @validation.StructureContract]
timestamp_format : @validation.TimestampFormat
}
///|
fn exported(text : String) -> String {
let out = StringBuilder()
let mut upper = true
let all_upper = text.to_array().all(c => !(c >= 'a' && c <= 'z'))
for c in text.to_array() {
if c == '_' {
upper = true
continue
}
if upper && c >= 'a' && c <= 'z' {
out.write_char((c.to_int() - 32).unsafe_to_char())
} else if !upper && all_upper && c >= 'A' && c <= 'Z' {
out.write_char((c.to_int() + 32).unsafe_to_char())
} else {
out.write_char(c)
}
upper = false
}
out.to_string()
}
///|
fn symbol(name : String, path~ : String = "") -> @codegen.Symbol {
@codegen.Symbol::new(
@codegen.SymbolName::parse(name).unwrap(),
ns=if path == "" {
@codegen.Namespace::root()
} else {
@codegen.Namespace::parse(path, separator="/").unwrap()
},
)
}
///|
fn GoContext::shape(
self : GoContext,
id : @smithy.ShapeId,
) -> @smithy.Shape raise @codegen.CodegenError {
match self.model.shapes.get(id) {
Some(shape) => shape
None =>
raise @codegen.StateError(
"I could not find shape `\{id}` in the Go generator model.",
)
}
}
///|
fn GoContext::target(
self : GoContext,
id : @smithy.ShapeId,
) -> @smithy.ShapeId raise @codegen.CodegenError {
match self.shape(id).body {
Member(target~) => target
_ =>
raise @codegen.StateError(
"I expected member shape `\{id}` in the Go generator model.",
)
}
}
///|
fn GoContext::type_symbol(
self : GoContext,
id : @smithy.ShapeId,
) -> @codegen.TemplateValue raise @codegen.CodegenError {
let shape = self.value_shape(id)
if shape.body is Simple(Timestamp) {
let format = self.timestamp_format_for(id)
if shape.id.namespace() == "smithy.api" ||
format != self.timestamp_format_for(shape.id) {
let type_ = primitive_symbol(
Timestamp,
self.module_name,
timestamp_format=format,
)
return Call(w => w.write_inline("$T", args=[Sym(type_)]))
}
}
if shape.id.namespace() == "smithy.api" {
match shape.body {
Simple(Blob) =>
return Call(w => w.write_inline("[]$T", args=[Sym(symbol("byte"))]))
Structure(_) => return Call(w => w.write_inline("struct{}"))
_ => ()
}
}
let type_ = self.provider.to_symbol(shape)
Call(w => w.write_inline("$T", args=[Sym(type_)]))
}
///|
/// Resolve one member boundary. Semantic member targets are value shapes.
fn GoContext::value_shape(
self : GoContext,
id : @smithy.ShapeId,
) -> @smithy.Shape raise @codegen.CodegenError {
let shape = self.shape(id)
match shape.body {
Member(target~) => self.shape(target)
_ => shape
}
}
///|
/// Core value directives use member, target, then prepared default precedence.
fn GoContext::timestamp_format_for(
self : GoContext,
id : @smithy.ShapeId,
) -> @validation.TimestampFormat raise @codegen.CodegenError {
let source = self.shape(id)
let target = self.value_shape(id)
let trait_ = match source.trait_value("smithy.api#timestampFormat") {
Some(value) => Some(value)
None => target.trait_value("smithy.api#timestampFormat")
}
match trait_ {
None => self.timestamp_format
Some(String("date-time")) => @validation.DateTime
Some(String("epoch-seconds")) => @validation.EpochSeconds
_ =>
raise @codegen.InvalidSettings(
"I cannot map the JSON timestamp format of construct `\{id}` to a Go type.",
)
}
}
///|
fn GoContext::wire_name(self : GoContext, id : @smithy.ShapeId) -> String {
for _, value in self.values {
for member_ in value.members {
if member_.shape == id {
return member_.wire_name
}
}
}
id.member_name().unwrap_or(id.name())
}
///|
fn GoContext::field(
self : GoContext,
id : @smithy.ShapeId,
) -> String raise @codegen.CodegenError {
self.provider.to_member_name(self.shape(id))
}
///|
fn GoContext::writer(
self : GoContext,
) -> @codegen.CodeWriter raise @codegen.CodegenError {
let writer = @codegen.CodeWriter::new(
imports=@codegen.ImportPolicy::by_package(
qualify=(prefix, sym) => prefix.to_string() + "." + sym.name.to_string(),
qualifier=ns => {
@codegen.SymbolName::parse(
ns.text.split("/").to_array().last().unwrap().to_owned(),
).unwrap()
},
locals_block_qualifiers=true,
),
)
for id in self.shapes {
if self.model.shapes.get(id) is Some(shape) &&
!(shape.body is Member(..)) &&
id.namespace() != "smithy.api" {
let name = self.provider.to_symbol(shape).name.to_string()
writer.reserve_name(@codegen.SymbolName::parse(name).unwrap())
}
}
writer
}
///|
fn GoContext::render(self : GoContext, writer : @codegen.CodeWriter) -> String {
let out = StringBuilder()
out.write_string(
"// Code generated by go-jsonrpc-codegen. DO NOT EDIT.\n\npackage \{self.package_name}\n",
)
let imports = writer.package_imports()
if !imports.is_empty() {
out.write_string("\nimport (\n")
for entry in imports {
out.write_string(
"\t\{entry.qualifier.to_string()} \{quote(entry.ns.text)}\n",
)
}
out.write_string(")\n")
}
out.write_char('\n')
out.write_string(writer.to_string())
out.to_string()
}
///|
fn quote(text : String) -> String {
Json::string(text).stringify()
}
///|
fn create_context(
model : @smithy.Model,
description : @jsonrpc.Description,
peer : @rpc.PeerName,
module_name : String,
package_name : String,
) -> GoContext raise @codegen.CodegenError {
guard description.peer(peer) is Some(selected) else {
raise @codegen.InvalidSettings(
"I could not find peer `\{peer.to_string()}` in the prepared RPC description.",
)
}
let services : Array[@smithy.ShapeId] = []
let shape_set : Set[@smithy.ShapeId] = Set([])
let values : Map[@smithy.ShapeId, @validation.StructureContract] = Map([])
for peer in description.peers {
if peer.accepts is Some(service) {
services.push(service.service)
for id in service.shapes {
shape_set.add(id)
}
for value in service.values {
if values.get(value.shape) is Some(previous) && previous != value {
raise @codegen.InvalidSettings(
"I found different prepared value policies for shape `\{value.shape}` in the Go peer.",
)
}
values[value.shape] = value
}
}
}
let names = @codegen.ServiceNames::for_services(model, services)
let timestamp_format = description.peers
.iter()
.find_first(p => p.accepts != None)
.bind(p => p.accepts)
.map(s => s.value_config.timestamp_format)
.unwrap_or(@validation.EpochSeconds)
let provider = @codegen.SymbolProvider::new(
shape => {
if shape.id.namespace() == "smithy.api" {
match shape.body {
Simple(kind) =>
return primitive_symbol(kind, module_name, timestamp_format~)
Structure(_) => return symbol("Unit")
_ => ()
}
}
let name = exported(names.shape_name(shape.id))
if !go_identifier(name) {
raise @codegen.InvalidSettings(
"I could not use Go name `\{name}` for shape `\{shape.id}`.",
)
}
symbol(name)
},
to_member_name=shape => {
exported(shape.id.member_name().unwrap_or(shape.id.name()))
},
).cached()
let shapes = shape_set.to_array()
shapes.sort_by((a, b) => a.to_string().lexical_compare(b.to_string()))
let used : Map[String, @smithy.ShapeId] = Map([])
for id in shapes {
guard model.shapes.get(id) is Some(shape) &&
!(shape.body is Member(..)) &&
id.namespace() != "smithy.api" else {
continue
}
let name = provider.to_symbol(shape).name.to_string()
if ["Peer", "Handlers", "NewPeer", "Contract"].contains(name) ||
used.contains(name) {
raise @codegen.InvalidSettings(
"I found a Go declaration name conflict for shape `\{id}` at `\{name}`.",
)
}
used[name] = id
let members = match shape.body {
Structure(ids) | Union(ids) | Enum(ids) | IntEnum(ids) => ids
_ => []
}
let fields : Set[String] = Set([])
for member_ in members {
let field = provider.to_member_name(model.shapes[member_])
if !go_identifier(field) || fields.contains(field) {
raise @codegen.InvalidSettings(
"I found a Go member name conflict for member `\{member_}` at `\{field}`.",
)
}
fields.add(field)
}
}
{
model,
description,
selected,
module_name,
package_name,
provider,
shapes,
values,
timestamp_format,
}
}
///|
fn primitive_symbol(
kind : @smithy.SimpleType,
module_name : String,
timestamp_format~ : @validation.TimestampFormat = @validation.EpochSeconds,
) -> @codegen.Symbol {
match kind {
Blob => symbol("byte")
Boolean => symbol("bool")
Document => symbol("RawMessage", path="encoding/json")
String => symbol("string")
Byte => symbol("int8")
Short => symbol("int16")
Integer => symbol("int32")
Long => symbol("int64")
Float => symbol("float32")
Double => symbol("float64")
BigInteger => symbol("Integer", path=module_name + "/rpc")
BigDecimal => symbol("Number", path=module_name + "/rpc")
Timestamp =>
if timestamp_format == @validation.DateTime {
symbol("string")
} else {
symbol("Number", path=module_name + "/rpc")
}
}
}