use super::field_skip::FieldSkip;
use crate::e2e::field_access::FieldResolver;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum UnionLoweringTarget {
Dart,
KotlinAndroid,
KotlinJvm,
Java,
Swift,
}
pub(crate) fn lacks_scalar_wire_accessor(
field_resolver: &FieldResolver,
field: &str,
target: UnionLoweringTarget,
) -> bool {
match target {
UnionLoweringTarget::KotlinJvm | UnionLoweringTarget::Java => {
field_resolver.java_enum_emits_get_value(field) == Some(false)
}
UnionLoweringTarget::Dart | UnionLoweringTarget::KotlinAndroid | UnionLoweringTarget::Swift => {
field_resolver.ir_enum_is_data_carrying(field) == Some(true)
}
}
}
pub(crate) fn payload_union_skip_line(
indent: &str,
comment_open: &str,
field_resolver: &FieldResolver,
field: Option<&str>,
target: UnionLoweringTarget,
) -> Option<String> {
let field = field.filter(|f| !f.is_empty())?;
if !lacks_scalar_wire_accessor(field_resolver, field, target) {
return None;
}
Some(format!(
"{indent}{comment_open} skipped: {}",
FieldSkip::PayloadUnionHasNoScalarWireAccessor.message(field)
))
}
#[cfg(test)]
mod tests {
use super::{UnionLoweringTarget, lacks_scalar_wire_accessor, payload_union_skip_line};
use crate::core::ir::{EnumDef, EnumVariant, FieldDef, TypeDef, TypeRef};
use crate::e2e::codegen::field_skip::{FieldSkip, SkipClass};
use crate::e2e::field_access::FieldResolver;
use std::collections::{HashMap, HashSet};
fn field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
..FieldDef::default()
}
}
fn resolver() -> FieldResolver {
let types = vec![TypeDef {
name: "Envelope".to_string(),
fields: vec![
field("unit", TypeRef::Named("DataNodeKind".to_string())),
field("untagged", TypeRef::Named("StageOutput".to_string())),
field("external", TypeRef::Named("Payload".to_string())),
],
..TypeDef::default()
}];
let enums = vec![
EnumDef {
name: "DataNodeKind".to_string(),
variants: vec![
EnumVariant {
name: "KeyValue".to_string(),
..EnumVariant::default()
},
EnumVariant {
name: "Sequence".to_string(),
..EnumVariant::default()
},
],
..EnumDef::default()
},
EnumDef {
name: "StageOutput".to_string(),
variants: vec![EnumVariant {
name: "Text".to_string(),
fields: vec![field("_0", TypeRef::String)],
is_tuple: true,
..EnumVariant::default()
}],
serde_untagged: true,
..EnumDef::default()
},
EnumDef {
name: "Payload".to_string(),
variants: vec![EnumVariant {
name: "Blob".to_string(),
fields: vec![field("_0", TypeRef::String)],
is_tuple: true,
..EnumVariant::default()
}],
..EnumDef::default()
},
];
FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.with_ir_enum_map(
FieldResolver::ir_enum_fields(&types, &enums),
Some("Envelope".to_string()),
)
.with_java_wrapper_enum_names(
enums
.iter()
.filter(|enum_def| !crate::backends::java::gen_bindings::emits_get_value(enum_def))
.map(|enum_def| enum_def.name.clone())
.collect(),
)
}
#[test]
fn the_predicate_answers_per_target() {
let resolver = resolver();
let expected = [
("unit", UnionLoweringTarget::Dart, false),
("unit", UnionLoweringTarget::KotlinAndroid, false),
("unit", UnionLoweringTarget::KotlinJvm, false),
("unit", UnionLoweringTarget::Java, false),
("unit", UnionLoweringTarget::Swift, false),
("untagged", UnionLoweringTarget::Dart, true),
("untagged", UnionLoweringTarget::KotlinAndroid, true),
("untagged", UnionLoweringTarget::KotlinJvm, true),
("untagged", UnionLoweringTarget::Java, true),
("untagged", UnionLoweringTarget::Swift, true),
("external", UnionLoweringTarget::Dart, true),
("external", UnionLoweringTarget::KotlinAndroid, true),
("external", UnionLoweringTarget::KotlinJvm, false),
("external", UnionLoweringTarget::Java, false),
("external", UnionLoweringTarget::Swift, true),
];
for (field, target, want) in expected {
assert_eq!(
lacks_scalar_wire_accessor(&resolver, field, target),
want,
"field {field} on {target:?}"
);
}
}
#[test]
fn an_unresolved_field_is_never_refused() {
let bare = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
);
for target in [
UnionLoweringTarget::Dart,
UnionLoweringTarget::KotlinAndroid,
UnionLoweringTarget::KotlinJvm,
UnionLoweringTarget::Java,
UnionLoweringTarget::Swift,
] {
assert_eq!(
payload_union_skip_line(" ", "//", &bare, Some("untagged"), target),
None,
"{target:?}"
);
}
}
#[test]
fn a_missing_or_empty_field_yields_no_line() {
let resolver = resolver();
assert_eq!(
payload_union_skip_line(" ", "//", &resolver, None, UnionLoweringTarget::Dart),
None
);
assert_eq!(
payload_union_skip_line(" ", "//", &resolver, Some(""), UnionLoweringTarget::Dart),
None
);
}
#[test]
fn the_rendered_line_is_exact_and_recognised() {
let resolver = resolver();
let line = payload_union_skip_line(
" ",
"//",
&resolver,
Some("untagged"),
UnionLoweringTarget::Swift,
)
.expect("a payload union must be refused");
assert_eq!(
line,
" // skipped: enum field 'untagged' is a payload-carrying union with no scalar \
wire accessor in this binding"
);
assert_eq!(
FieldSkip::extract_classified(&line),
Some(("untagged", FieldSkip::PayloadUnionHasNoScalarWireAccessor)),
"got: {line}"
);
assert_eq!(
FieldSkip::PayloadUnionHasNoScalarWireAccessor.class(),
SkipClass::GeneratorGap,
"a consumer cannot close this from their own alef.toml, so it must never be fatal"
);
}
}