use crate::backends::go::{
GoEnumRepresentation, GoStructEnumVariantField, go_adjacent_tagged_constructor,
go_data_enum_untagged_variant_matches, go_data_enum_variant_field, go_data_enum_variant_scalar_tuple_field,
go_data_enum_variant_struct, go_enum_representation, go_struct_enum_tag_field, go_struct_enum_variant_fields,
};
use crate::codegen::naming::{go_type_name, wire_variant_value};
use crate::core::ir::{EnumDef, EnumVariant, TypeRef};
use crate::e2e::escape::go_string_literal;
use super::json_values::json_to_go;
use super::setup::{GoFieldSite, GoValueContext, go_named_field_expression, go_struct_field_expression};
pub(super) fn go_enum_value_expression(
value: &serde_json::Value,
enum_def: &EnumDef,
context: GoValueContext<'_>,
site: GoFieldSite<'_>,
) -> anyhow::Result<Option<String>> {
match go_enum_representation(enum_def) {
GoEnumRepresentation::UnitString
| GoEnumRepresentation::NewtypeTupleString
| GoEnumRepresentation::RawMessage => Ok(None),
GoEnumRepresentation::AdjacentTaggedStruct => adjacent_tagged_expression(value, enum_def, context, site),
GoEnumRepresentation::TupleTaggedStruct | GoEnumRepresentation::ExternallyTaggedStruct => {
struct_union_expression(value, enum_def, context, site)
}
GoEnumRepresentation::DataInterface => data_interface_expression(value, enum_def, context, site),
}
}
fn struct_union_expression(
value: &serde_json::Value,
enum_def: &EnumDef,
context: GoValueContext<'_>,
site: GoFieldSite<'_>,
) -> anyhow::Result<Option<String>> {
let variant_fields = go_struct_enum_variant_fields(enum_def);
let Some((selected, payload)) = select_struct_union_variant(value, enum_def, &variant_fields, context) else {
return Ok(None);
};
let TypeRef::Named(payload_type) = &selected.payload.ty else {
return Ok(None);
};
let payload_expression = go_named_field_expression(payload, payload_type, context, site, true)?;
let mut assignments = Vec::new();
if let Some((tag_field, _)) = go_struct_enum_tag_field(enum_def) {
let wire_value = variant_wire_value(enum_def, selected.variant);
assignments.push(format!("{tag_field}: {}", go_string_literal(&wire_value)));
}
assignments.push(format!("{}: {payload_expression}", selected.field_name));
Ok(Some(format!(
"{}.{}{{{}}}",
context.import_alias,
go_type_name(&enum_def.name),
assignments.join(", ")
)))
}
fn select_struct_union_variant<'a, 'v>(
value: &'v serde_json::Value,
enum_def: &EnumDef,
variant_fields: &'a [GoStructEnumVariantField<'a>],
context: GoValueContext<'_>,
) -> Option<(&'a GoStructEnumVariantField<'a>, &'v serde_json::Value)> {
if let Some((_, tag_key)) = go_struct_enum_tag_field(enum_def) {
let tag = value.get(tag_key)?.as_str()?;
let selected = variant_fields
.iter()
.find(|candidate| variant_wire_value(enum_def, candidate.variant) == tag)?;
return Some((selected, value));
}
if go_enum_representation(enum_def) == GoEnumRepresentation::ExternallyTaggedStruct {
let object = value.as_object()?;
return variant_fields
.iter()
.find_map(|candidate| Some((candidate, object.get(&candidate.json_key)?)));
}
variant_fields
.iter()
.find(|candidate| payload_type_can_represent(value, &candidate.payload.ty, context))
.map(|candidate| (candidate, value))
}
fn payload_type_can_represent(value: &serde_json::Value, type_ref: &TypeRef, context: GoValueContext<'_>) -> bool {
let TypeRef::Named(name) = type_ref else {
return false;
};
if context
.type_defs
.iter()
.any(|definition| definition.name == *name && !definition.is_opaque)
{
return value.is_object();
}
let Some(enum_def) = context.enums.iter().find(|candidate| candidate.name == *name) else {
return false;
};
match go_enum_representation(enum_def) {
GoEnumRepresentation::UnitString | GoEnumRepresentation::NewtypeTupleString => value.is_string(),
GoEnumRepresentation::RawMessage => !value.is_null(),
_ => value.is_object(),
}
}
fn adjacent_tagged_expression(
value: &serde_json::Value,
enum_def: &EnumDef,
context: GoValueContext<'_>,
site: GoFieldSite<'_>,
) -> anyhow::Result<Option<String>> {
let Some((_, tag_key)) = go_struct_enum_tag_field(enum_def) else {
return Ok(None);
};
let Some(tag) = value.get(tag_key).and_then(serde_json::Value::as_str) else {
return Ok(None);
};
let Some(variant) = enum_def
.variants
.iter()
.find(|candidate| variant_wire_value(enum_def, candidate) == tag)
else {
return Ok(None);
};
let constructor = format!(
"{}.{}",
context.import_alias,
go_adjacent_tagged_constructor(enum_def, variant)
);
let Some(payload_field) = variant.fields.first() else {
return Ok(Some(format!("{constructor}()")));
};
let Some(content_key) = enum_def.serde_content.as_deref() else {
return Ok(None);
};
let Some(content) = value.get(content_key) else {
return Ok(None);
};
let Some(payload) = unaddressed_payload_expression(content, &payload_field.ty, context, site)? else {
return Ok(None);
};
Ok(Some(format!("{constructor}({payload})")))
}
fn unaddressed_payload_expression(
content: &serde_json::Value,
type_ref: &TypeRef,
context: GoValueContext<'_>,
site: GoFieldSite<'_>,
) -> anyhow::Result<Option<String>> {
match type_ref {
TypeRef::Named(name) => go_named_field_expression(content, name, context, site, false).map(Some),
TypeRef::String | TypeRef::Char | TypeRef::Path => Ok(content.as_str().map(go_string_literal)),
TypeRef::Primitive(_) => Ok((content.is_number() || content.is_boolean()).then(|| json_to_go(content))),
_ => Ok(None),
}
}
fn data_interface_expression(
value: &serde_json::Value,
enum_def: &EnumDef,
context: GoValueContext<'_>,
site: GoFieldSite<'_>,
) -> anyhow::Result<Option<String>> {
let Some(variant) = select_data_enum_variant(value, enum_def) else {
return Ok(None);
};
let struct_name = format!(
"{}.{}",
context.import_alias,
go_data_enum_variant_struct(enum_def, variant)
);
if let Some(scalar) = go_data_enum_variant_scalar_tuple_field(enum_def, variant) {
let Some(literal) = unaddressed_payload_expression(value, &scalar.ty, context, site)? else {
return Ok(None);
};
return Ok(Some(format!("{struct_name}{{Value: {literal}}}")));
}
let Some(object) = value.as_object() else {
return Ok(None);
};
let mut assignments: Vec<String> = Vec::new();
for field in &variant.fields {
let Some((field_name, json_key)) = go_data_enum_variant_field(enum_def, field) else {
continue;
};
let Some(field_value) = object.get(&json_key).or_else(|| object.get(&field.name)) else {
continue;
};
let field_pointer = format!("{}/{}", site.pointer, field.name);
let field_site = GoFieldSite {
owner_type: &enum_def.name,
field_name: &field.name,
pointer: &field_pointer,
};
let Some(expression) = go_struct_field_expression(field, field_value, context, field_site, field.optional)?
else {
continue;
};
assignments.push(format!("{field_name}: {expression}"));
}
Ok(Some(format!("{struct_name}{{{}}}", assignments.join(", "))))
}
fn select_data_enum_variant<'a>(value: &serde_json::Value, enum_def: &'a EnumDef) -> Option<&'a EnumVariant> {
if let Some(tag_key) = enum_def.serde_tag.as_deref() {
let tag = value.get(tag_key)?.as_str()?;
return enum_def
.variants
.iter()
.find(|variant| variant_wire_value(enum_def, variant) == tag);
}
if !enum_def.serde_untagged {
return None;
}
enum_def
.variants
.iter()
.find(|variant| go_data_enum_untagged_variant_matches(variant, value))
}
fn variant_wire_value(enum_def: &EnumDef, variant: &EnumVariant) -> String {
wire_variant_value(
&variant.name,
variant.serde_rename.as_deref(),
enum_def.serde_rename_all.as_deref(),
)
}