Current section
Files
Jump to
Current section
Files
src/atproto_codegen/emit/exprs.gleam
//// Per-field expressions: the Gleam type, encoder, and decoder for one
//// lexicon field type, plus the predicates the header/emission logic keys on.
import atproto_codegen/config.{type Config}
import atproto_codegen/emit/zero.{type ZeroPlan}
import atproto_codegen/lower.{
type EmittableDef, type FlatField, type FlatType, Closed, Open, TArray, TBlob,
TBool, TBytes, TCidLink, TInt, TRef, TString, TUnion, TUnknown, TUnsupported,
}
import atproto_codegen/naming
import gleam/string
import justin
pub fn escape(value: String) -> String {
value
|> string.replace("\\", "\\\\")
|> string.replace("\"", "\\\"")
}
/// Only an open union pulls in the `internal` import.
pub fn type_uses_helper(ft: FlatType) -> Bool {
case ft {
TCidLink | TBytes | TUnknown -> True
TUnion(_, Open) -> True
TUnion(_, Closed) -> False
TArray(inner) -> type_uses_helper(inner)
_ -> False
}
}
pub fn type_uses_blob(ft: FlatType) -> Bool {
case ft {
TBlob -> True
TArray(inner) -> type_uses_blob(inner)
_ -> False
}
}
/// Only an open union needs `dynamic` (Other variant, nil zero).
pub fn type_uses_unknown(ft: FlatType) -> Bool {
case ft {
TUnknown -> True
TUnion(_, Open) -> True
TUnion(_, Closed) -> False
TArray(inner) -> type_uses_unknown(inner)
_ -> False
}
}
/// The synthesized name for a property's inline union type.
pub fn union_type_name(
config: Config,
def: EmittableDef,
prop: FlatField,
) -> String {
naming.def_type_name(config.nsid_prefixes, def)
<> justin.pascal_case(prop.json_name)
}
pub fn gleam_type(
config: Config,
current: String,
union_name: String,
ft: FlatType,
) -> String {
case ft {
TString -> "String"
TInt -> "Int"
TBool -> "Bool"
TRef(ref) -> {
let r = naming.parse_ref(current, ref)
naming.qualifier(config.nsid_prefixes, current, r.nsid)
<> naming.ref_type_name(config.nsid_prefixes, r)
}
TArray(inner) ->
"List(" <> gleam_type(config, current, union_name, inner) <> ")"
TCidLink -> "String"
TBytes -> "BitArray"
TBlob -> "blob.Blob"
TUnknown -> "dynamic.Dynamic"
TUnion(_, _) -> union_name
TUnsupported(kind) -> "Dynamic_" <> kind
}
}
pub fn encoder_expr(
config: Config,
current: String,
union_name: String,
ft: FlatType,
) -> String {
case ft {
TString -> "json.string"
TInt -> "json.int"
TBool -> "json.bool"
TRef(ref) -> {
let r = naming.parse_ref(current, ref)
naming.qualifier(config.nsid_prefixes, current, r.nsid)
<> "encode_"
<> justin.snake_case(naming.ref_type_name(config.nsid_prefixes, r))
}
TArray(inner) ->
"fn(items) { json.array(items, "
<> encoder_expr(config, current, union_name, inner)
<> ") }"
TCidLink -> "internal.encode_cid_link"
TBytes -> "internal.encode_bytes"
TBlob -> "blob.encode_blob"
TUnknown -> "internal.dynamic_to_json"
TUnion(_, _) -> "encode_" <> justin.snake_case(union_name)
TUnsupported(_) -> "json.string"
}
}
pub fn decoder_expr(
config: Config,
current: String,
union_name: String,
ft: FlatType,
) -> String {
case ft {
TString -> "decode.string"
TInt -> "decode.int"
TBool -> "decode.bool"
TRef(ref) -> {
let r = naming.parse_ref(current, ref)
naming.qualifier(config.nsid_prefixes, current, r.nsid)
<> justin.snake_case(naming.ref_type_name(config.nsid_prefixes, r))
<> "_decoder()"
}
TArray(inner) ->
"decode.list(" <> decoder_expr(config, current, union_name, inner) <> ")"
TCidLink -> "internal.cid_link_decoder()"
TBytes -> "internal.bytes_decoder()"
TBlob -> "blob.blob_decoder()"
TUnknown -> "decode.dynamic"
TUnion(_, _) -> justin.snake_case(union_name) <> "_decoder()"
TUnsupported(_) -> "decode.string"
}
}
/// Zero for one field type; arrays/optionals never recurse.
pub fn zero_expr(
plan: ZeroPlan,
config: Config,
current: String,
union_name: String,
ft: FlatType,
) -> String {
case ft {
TString -> "\"\""
TInt -> "0"
TBool -> "False"
TRef(ref) -> {
let r = naming.parse_ref(current, ref)
naming.qualifier(config.nsid_prefixes, current, r.nsid)
<> justin.snake_case(naming.ref_type_name(config.nsid_prefixes, r))
<> "_zero()"
}
TArray(_) -> "[]"
TCidLink -> "\"\""
TBytes -> "<<>>"
TBlob -> "blob.Blob(\"\", \"\", 0)"
TUnknown -> "dynamic.nil()"
TUnion(_, Open) -> union_name <> "Other(dynamic.nil())"
TUnion(refs, Closed) -> {
let ref = zero.choose_fallback(plan, current, refs)
let r = naming.parse_ref(current, ref)
union_name
<> naming.ref_type_name(config.nsid_prefixes, r)
<> "("
<> zero_expr(plan, config, current, union_name, TRef(ref))
<> ")"
}
TUnsupported(_) -> "dynamic.nil()"
}
}