use std::io::{self, Result, Write};
use heck::ToLowerCamelCase;
use indoc::writedoc;
use super::BincodePlugin;
use crate::generation::{
BINCODE_NAMESPACE, CodeGeneratorConfig, Feature, PackageLocation, SERDE_NAMESPACE,
indent::{IndentWrite, IndentedWriter, Newlines},
kotlin::Kotlin,
plugin::{EmitContext, EmitterPlugin, RuntimeFile},
};
use crate::reflection::format::{ContainerFormat, Format, Named, VariantFormat};
fn resolve_package(config: &CodeGeneratorConfig, namespace: &str, default: &str) -> String {
config
.external_packages
.get(namespace)
.and_then(|pkg| {
if let PackageLocation::Path(path) = &pkg.location {
Some(path.clone())
} else {
None
}
})
.unwrap_or_else(|| default.to_string())
}
const FEATURE_LIST_OF_T: &str = r"fun <T> List<T>.serialize(
serializer: Serializer,
serializeElement: Serializer.(T) -> Unit,
) {
serializer.serialize_len(size.toLong())
forEach { element ->
serializer.serializeElement(element)
}
}
fun <T> Deserializer.deserializeListOf(deserializeElement: (Deserializer) -> T): List<T> {
val length = deserialize_len()
val list = mutableListOf<T>()
repeat(length.toInt()) {
list.add(deserializeElement(this))
}
return list
}
";
const FEATURE_MAP_OF_T: &str = r"fun <K, V> Map<K, V>.serialize(
serializer: Serializer,
serializeEntry: Serializer.(K, V) -> Unit,
) {
serializer.serialize_len(size.toLong())
forEach { (key, value) ->
serializer.serializeEntry(key, value)
}
}
fun <K, V> Deserializer.deserializeMapOf(deserializeEntry: (Deserializer) -> Pair<K, V>): Map<K, V> {
val length = deserialize_len()
val map = mutableMapOf<K, V>()
repeat(length.toInt()) {
val (key, value) = deserializeEntry(this)
map[key] = value
}
return map
}
";
const FEATURE_OPTION_OF_T: &str = r"fun <T> T?.serializeOptionOf(
serializer: Serializer,
serializeElement: Serializer.(T) -> Unit,
) {
if (this != null) {
serializer.serialize_option_tag(true)
serializer.serializeElement(this)
} else {
serializer.serialize_option_tag(false)
}
}
fun <T> Deserializer.deserializeOptionOf(deserializeElement: (Deserializer) -> T): T? {
val tag = deserialize_option_tag()
return if (tag) {
deserializeElement(this)
} else {
null
}
}
";
const FEATURE_SET_OF_T: &str = r"fun <T> Set<T>.serialize(
serializer: Serializer,
serializeElement: Serializer.(T) -> Unit,
) {
serializer.serialize_len(size.toLong())
forEach { element ->
serializer.serializeElement(element)
}
}
fun <T> Deserializer.deserializeSetOf(deserializeElement: (Deserializer) -> T): Set<T> {
val length = deserialize_len()
val set = mutableSetOf<T>()
repeat(length.toInt()) {
set.add(deserializeElement(this))
}
return set
}
";
const FEATURE_UUID: &str = r#"fun UUID.serialize(serializer: Serializer) {
val bytes = ByteArray(16)
val msb = mostSignificantBits
val lsb = leastSignificantBits
for (i in 0..7) {
bytes[i] = (msb ushr (56 - i * 8) and 0xff).toByte()
bytes[8 + i] = (lsb ushr (56 - i * 8) and 0xff).toByte()
}
serializer.serialize_bytes(Bytes(bytes))
}
fun Deserializer.deserializeUuid(): UUID {
val bytes = deserialize_bytes().content
if (bytes.size != 16) {
throw DeserializationError("UUID must be 16 bytes, got ${bytes.size}")
}
var msb = 0L
var lsb = 0L
for (i in 0..7) {
msb = (msb shl 8) or (bytes[i].toLong() and 0xff)
lsb = (lsb shl 8) or (bytes[8 + i].toLong() and 0xff)
}
return UUID(msb, lsb)
}
"#;
fn write_bincode_serialize<W: Write>(w: &mut W) -> Result<()> {
writedoc!(
w,
r"
fun bincodeSerialize(): ByteArray {{
val serializer = BincodeSerializer()
serialize(serializer)
return serializer.get_bytes()
}}
"
)
}
fn write_bincode_deserialize<W: Write>(w: &mut W, name: &str) -> Result<()> {
writedoc!(
w,
r#"
@Throws(DeserializationError::class)
fun bincodeDeserialize(input: ByteArray?): {name} {{
if (input == null) {{
throw DeserializationError("Cannot deserialize null array")
}}
val deserializer = BincodeDeserializer(input)
val value = deserialize(deserializer)
if (deserializer.get_buffer_offset() < input.size) {{
throw DeserializationError("Some input bytes were not read")
}}
return value
}}
"#
)
}
fn write_serialize<W: IndentWrite>(
w: &mut W,
field_name: &str,
format: &Format,
level: usize,
) -> Result<()> {
match format {
Format::Unit => writeln!(w, "serializer.serialize_unit({field_name})"),
Format::Bool => writeln!(w, "serializer.serialize_bool({field_name})"),
Format::I8 => writeln!(w, "serializer.serialize_i8({field_name})"),
Format::I16 => writeln!(w, "serializer.serialize_i16({field_name})"),
Format::I32 => writeln!(w, "serializer.serialize_i32({field_name})"),
Format::I64 => writeln!(w, "serializer.serialize_i64({field_name})"),
Format::I128 => writeln!(w, "serializer.serialize_i128({field_name})"),
Format::U8 => writeln!(w, "serializer.serialize_u8({field_name})"),
Format::U16 => writeln!(w, "serializer.serialize_u16({field_name})"),
Format::U32 => writeln!(w, "serializer.serialize_u32({field_name})"),
Format::U64 => writeln!(w, "serializer.serialize_u64({field_name})"),
Format::U128 => writeln!(w, "serializer.serialize_u128({field_name})"),
Format::F32 => writeln!(w, "serializer.serialize_f32({field_name})"),
Format::F64 => writeln!(w, "serializer.serialize_f64({field_name})"),
Format::Char => writeln!(w, "serializer.serialize_char({field_name})"),
Format::Str => writeln!(w, "serializer.serialize_str({field_name})"),
Format::Bytes => writeln!(w, "serializer.serialize_bytes({field_name})"),
Format::Uuid => writeln!(w, "{field_name}.serialize(serializer)"),
Format::Option(inner_format) => {
write!(w, "{field_name}.serializeOptionOf(serializer) ")?;
write_serialize_lambda(w, inner_format, level)?;
Ok(())
}
Format::Seq(inner_format) | Format::Set(inner_format) => {
write!(w, "{field_name}.serialize(serializer) ")?;
write_serialize_lambda(w, inner_format, level)?;
Ok(())
}
Format::Map { key, value } => {
write!(w, "{field_name}.serialize(serializer) ")?;
write_map_serialize_lambda(w, key, value, level)?;
Ok(())
}
Format::TypeName(..) | Format::TupleArray { .. } => {
writeln!(w, "{field_name}.serialize(serializer)")
}
Format::Tuple(formats) => {
let len = formats.len();
match len {
0 => writeln!(w, "serializer.serialize_unit({field_name})"),
1 => write_serialize(w, field_name, &formats[0], level),
2 => {
write_serialize(w, &format!("{field_name}.first"), &formats[0], level)?;
write_serialize(w, &format!("{field_name}.second"), &formats[1], level)
}
3 => {
write_serialize(w, &format!("{field_name}.first"), &formats[0], level)?;
write_serialize(w, &format!("{field_name}.second"), &formats[1], level)?;
write_serialize(w, &format!("{field_name}.third"), &formats[2], level)
}
_ => {
for (i, format) in formats.iter().enumerate() {
write_serialize(
w,
&format!("{field_name}.component{}()", i + 1),
format,
level,
)?;
}
Ok(())
}
}
}
Format::Variable(_variable) => unreachable!("placeholders should not get this far"),
}
}
fn write_serialize_lambda<W: IndentWrite>(w: &mut W, format: &Format, level: usize) -> Result<()> {
if format.is_leaf() {
let mut w = w.block(Newlines::BOTH)?;
write_serialize(&mut w, "it", format, level + 1)
} else {
let param_name = format!("level{}", level + 1);
let mut w = w.block(Newlines::CLOSE)?;
writeln!(w, " {param_name} ->")?;
write_serialize(&mut w, ¶m_name, format, level + 1)
}
}
fn write_map_serialize_lambda<W: IndentWrite>(
w: &mut W,
key_format: &Format,
value_format: &Format,
level: usize,
) -> Result<()> {
let mut w = w.block(Newlines::CLOSE)?;
writeln!(w, " key, value ->")?;
write_serialize(&mut w, "key", key_format, level + 1)?;
write_serialize(&mut w, "value", value_format, level + 1)
}
#[allow(clippy::too_many_lines)]
fn write_deserialize<W: IndentWrite>(
w: &mut W,
field_name: Option<&str>,
format: &Format,
newline: bool,
) -> Result<()> {
let mut indented = false;
if let Some(field_name) = field_name {
write!(w, "val {field_name} =")?;
if matches!(
format,
Format::Seq(..) | Format::Option(..) | Format::Set(..) | Format::Map { .. }
) {
writeln!(w)?;
w.indent();
indented = true;
} else {
write!(w, " ")?;
}
}
match format {
Format::TypeName(qualified_name) => {
let fully_qualified_name = qualified_name.format(ToString::to_string, ".");
write!(w, "{fully_qualified_name}.deserialize(deserializer)")
}
Format::Unit => write!(w, "deserializer.deserialize_unit()"),
Format::Bool => write!(w, "deserializer.deserialize_bool()"),
Format::I8 => write!(w, "deserializer.deserialize_i8()"),
Format::I16 => write!(w, "deserializer.deserialize_i16()"),
Format::I32 => write!(w, "deserializer.deserialize_i32()"),
Format::I64 => write!(w, "deserializer.deserialize_i64()"),
Format::I128 => write!(w, "deserializer.deserialize_i128()"),
Format::U8 => write!(w, "deserializer.deserialize_u8()"),
Format::U16 => write!(w, "deserializer.deserialize_u16()"),
Format::U32 => write!(w, "deserializer.deserialize_u32()"),
Format::U64 => write!(w, "deserializer.deserialize_u64()"),
Format::U128 => write!(w, "deserializer.deserialize_u128()"),
Format::F32 => write!(w, "deserializer.deserialize_f32()"),
Format::F64 => write!(w, "deserializer.deserialize_f64()"),
Format::Char => write!(w, "deserializer.deserialize_char()"),
Format::Str => write!(w, "deserializer.deserialize_str()"),
Format::Bytes => write!(w, "deserializer.deserialize_bytes()"),
Format::Uuid => write!(w, "deserializer.deserializeUuid()"),
Format::Seq(format) => {
write!(w, "deserializer.deserializeListOf ")?;
write_deserialize_lambda(w, format)
}
Format::Option(format) => {
write!(w, "deserializer.deserializeOptionOf ")?;
write_deserialize_lambda(w, format)
}
Format::Set(format) => {
write!(w, "deserializer.deserializeSetOf ")?;
write_deserialize_lambda(w, format)
}
Format::Map { key, value } => {
write!(w, "deserializer.deserializeMapOf ")?;
write_map_deserialize_lambda(w, key, value)
}
Format::Tuple(formats) => {
let len = formats.len();
match len {
0 => {
write!(w, "deserializer.deserialize_unit()")?;
return Ok(());
}
1 => {
push_deserializer(w)?;
write_deserialize(w, Some("value"), &formats[0], true)?;
pop_deserializer(w)?;
return Ok(());
}
2 => {
write!(w, "run ")?;
let mut w = w.block(Newlines::BOTH)?;
write!(w, "val first = ")?;
write_deserialize(&mut w, None, &formats[0], true)?;
write!(w, "val second = ")?;
write_deserialize(&mut w, None, &formats[1], true)?;
writeln!(w, "Pair(first, second)")?;
}
3 => {
write!(w, "run ")?;
let mut w = w.block(Newlines::BOTH)?;
write!(w, "val first = ")?;
write_deserialize(&mut w, None, &formats[0], true)?;
write!(w, "val second = ")?;
write_deserialize(&mut w, None, &formats[1], true)?;
write!(w, "val third = ")?;
write_deserialize(&mut w, None, &formats[2], true)?;
writeln!(w, "Triple(first, second, third)")?;
}
_ => {
let typename = format!("NTuple{len}");
write!(w, "run ")?;
let mut w = w.block(Newlines::BOTH)?;
for (i, format) in formats.iter().enumerate() {
write!(w, "val v{i} = ")?;
write_deserialize(&mut w, None, format, true)?;
}
write!(w, "{typename}(")?;
for i in 0..len {
if i > 0 {
write!(w, ", ")?;
}
write!(w, "v{i}")?;
}
writeln!(w, ")")?;
}
}
Ok(())
}
Format::TupleArray { content, size } => {
write!(w, "buildList({size}) {{ repeat({size}) {{ add(")?;
write_deserialize(w, None, content, false)?;
write!(w, ") }} }}")
}
Format::Variable(_variable) => unreachable!("placeholders should not get this far"),
}?;
if newline
&& !matches!(
format,
Format::Seq(..)
| Format::Option(..)
| Format::Set(..)
| Format::Map { .. }
| Format::Tuple(..)
)
{
writeln!(w)?;
}
if indented {
w.unindent();
}
Ok(())
}
fn write_deserialize_lambda<W: IndentWrite>(w: &mut W, format: &Format) -> Result<()> {
let mut w = w.block(Newlines::BOTH)?;
write_deserialize(&mut w, None, format, true)
}
fn write_map_deserialize_lambda<W: IndentWrite>(
w: &mut W,
key_format: &Format,
value_format: &Format,
) -> Result<()> {
let mut w = w.block(Newlines::BOTH)?;
write!(w, "val key =")?;
if key_format.is_leaf() {
write!(w, " ")?;
write_deserialize(&mut w, None, key_format, true)?;
} else {
writeln!(w)?;
w.indent();
write_deserialize(&mut w, None, key_format, true)?;
w.unindent();
}
write!(w, "val value =")?;
if value_format.is_leaf() {
write!(w, " ")?;
write_deserialize(&mut w, None, value_format, true)?;
} else {
writeln!(w)?;
w.indent();
write_deserialize(&mut w, None, value_format, true)?;
w.unindent();
}
writeln!(w, "Pair(key, value)")
}
fn push_serializer<W: Write>(w: &mut W) -> Result<()> {
writeln!(w, "serializer.increase_container_depth()")
}
fn pop_serializer<W: Write>(w: &mut W) -> Result<()> {
writeln!(w, "serializer.decrease_container_depth()")
}
fn push_deserializer<W: Write>(w: &mut W) -> Result<()> {
writeln!(w, "deserializer.increase_container_depth()")
}
fn pop_deserializer<W: Write>(w: &mut W) -> Result<()> {
writeln!(w, "deserializer.decrease_container_depth()")
}
fn write_data_object_top_level<W: IndentWrite>(w: &mut W, name: &str) -> Result<()> {
write!(w, "fun serialize(serializer: Serializer) ")?;
let _ = w.block(Newlines::CLOSE)?;
writeln!(w)?;
write_bincode_serialize(w)?;
writeln!(w)?;
write!(w, "fun deserialize(deserializer: Deserializer): {name} ")?;
{
let mut w = w.block(Newlines::BOTH)?;
writeln!(w, "return {name}")?;
}
writeln!(w)?;
write_bincode_deserialize(w, name)?;
Ok(())
}
fn write_data_object_variant<W: IndentWrite>(
w: &mut W,
name: &str,
variant_index: usize,
) -> Result<()> {
write!(w, "override fun serialize(serializer: Serializer) ")?;
{
let mut w = w.block(Newlines::BOTH)?;
push_serializer(&mut w)?;
writeln!(w, "serializer.serialize_variant_index({variant_index})")?;
pop_serializer(&mut w)?;
}
writeln!(w)?;
write!(w, "fun deserialize(deserializer: Deserializer): {name} ")?;
{
let mut w = w.block(Newlines::BOTH)?;
writeln!(w, "return {name}")?;
}
Ok(())
}
fn write_data_class_top_level<W: IndentWrite>(
w: &mut W,
name: &str,
fields: &[Named<Format>],
) -> Result<()> {
write!(w, "fun serialize(serializer: Serializer) ")?;
if fields.is_empty() {
let _ = w.block(Newlines::CLOSE)?;
} else {
let mut w = w.block(Newlines::BOTH)?;
push_serializer(&mut w)?;
for field in fields {
write_serialize(&mut w, &field.name.to_lower_camel_case(), &field.value, 0)?;
}
pop_serializer(&mut w)?;
}
writeln!(w)?;
write_bincode_serialize(w)?;
writeln!(w)?;
write!(w, "companion object ")?;
{
let mut w = w.block(Newlines::BOTH)?;
write!(w, "fun deserialize(deserializer: Deserializer): {name} ")?;
{
let mut w = w.block(Newlines::BOTH)?;
if fields.is_empty() {
writeln!(w, "return {name}()")?;
} else {
push_deserializer(&mut w)?;
for field in fields {
write_deserialize(
&mut w,
Some(&field.name.to_lower_camel_case()),
&field.value,
true,
)?;
}
pop_deserializer(&mut w)?;
write!(w, "return {name}(")?;
for (i, field) in fields.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
write!(w, "{}", field.name.to_lower_camel_case())?;
}
writeln!(w, ")")?;
}
}
writeln!(w)?;
write_bincode_deserialize(&mut w, name)?;
}
Ok(())
}
fn write_data_class_variant<W: IndentWrite>(
w: &mut W,
name: &str,
fields: &[Named<Format>],
variant_index: usize,
) -> Result<()> {
write!(w, "override fun serialize(serializer: Serializer) ")?;
if fields.is_empty() {
let _ = w.block(Newlines::CLOSE)?;
} else {
let mut w = w.block(Newlines::BOTH)?;
push_serializer(&mut w)?;
writeln!(w, "serializer.serialize_variant_index({variant_index})")?;
for field in fields {
write_serialize(&mut w, &field.name.to_lower_camel_case(), &field.value, 0)?;
}
pop_serializer(&mut w)?;
}
writeln!(w)?;
write!(w, "companion object ")?;
{
let mut w = w.block(Newlines::BOTH)?;
write!(w, "fun deserialize(deserializer: Deserializer): {name} ")?;
{
let mut w = w.block(Newlines::BOTH)?;
if fields.is_empty() {
writeln!(w, "return {name}()")?;
} else {
push_deserializer(&mut w)?;
for field in fields {
write_deserialize(
&mut w,
Some(&field.name.to_lower_camel_case()),
&field.value,
true,
)?;
}
pop_deserializer(&mut w)?;
write!(w, "return {name}(")?;
for (i, field) in fields.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
write!(w, "{}", field.name.to_lower_camel_case())?;
}
writeln!(w, ")")?;
}
}
}
Ok(())
}
fn write_enum_class_body<W: IndentWrite>(
w: &mut W,
name: &str,
variants: &std::collections::BTreeMap<u32, Named<VariantFormat>>,
) -> Result<()> {
writeln!(w)?;
write!(w, "fun serialize(serializer: Serializer) ")?;
{
let mut w = w.block(Newlines::BOTH)?;
push_serializer(&mut w)?;
writeln!(w, "serializer.serialize_variant_index(ordinal)")?;
pop_serializer(&mut w)?;
}
writeln!(w)?;
write_bincode_serialize(w)?;
writeln!(w)?;
write!(w, "companion object ")?;
{
let mut w = w.block(Newlines::BOTH)?;
writeln!(w, "@Throws(DeserializationError::class)")?;
write!(w, "fun deserialize(deserializer: Deserializer): {name} ")?;
{
let mut w = w.block(Newlines::BOTH)?;
push_deserializer(&mut w)?;
writeln!(w, "val index = deserializer.deserialize_variant_index()")?;
pop_deserializer(&mut w)?;
write!(w, "return when (index) ")?;
{
let mut w = w.block(Newlines::BOTH)?;
for (i, variant) in variants {
let upper = variant.name.to_uppercase();
writeln!(w, "{i} -> {upper}")?;
}
writeln!(
w,
r#"else -> throw DeserializationError("Unknown variant index for {name}: $index")"#
)?;
}
}
writeln!(w)?;
write_bincode_deserialize(&mut w, name)?;
}
Ok(())
}
fn write_sealed_interface_body<W: IndentWrite>(
w: &mut W,
name: &str,
variants: &std::collections::BTreeMap<u32, Named<VariantFormat>>,
) -> Result<()> {
writeln!(w)?;
write!(w, "companion object ")?;
{
let mut w = w.block(Newlines::BOTH)?;
writeln!(w, "@Throws(DeserializationError::class)")?;
write!(w, "fun deserialize(deserializer: Deserializer): {name} ")?;
{
let mut w = w.block(Newlines::BOTH)?;
writeln!(w, "val index = deserializer.deserialize_variant_index()")?;
write!(w, "return when (index) ")?;
{
let mut w = w.block(Newlines::BOTH)?;
for (i, variant) in variants {
let vname = &variant.name;
writeln!(w, "{i} -> {vname}.deserialize(deserializer)")?;
}
writeln!(
w,
r#"else -> throw DeserializationError("Unknown variant index for {name}: $index")"#
)?;
}
}
writeln!(w)?;
write_bincode_deserialize(&mut w, name)?;
}
Ok(())
}
impl EmitterPlugin<Kotlin> for BincodePlugin {
fn runtime_files(&self) -> Vec<RuntimeFile> {
static SERDE: include_dir::Dir<'static> =
include_dir::include_dir!("$CARGO_MANIFEST_DIR/runtime/kotlin/com/novi/serde");
static BINCODE: include_dir::Dir<'static> =
include_dir::include_dir!("$CARGO_MANIFEST_DIR/runtime/kotlin/com/novi/bincode");
let mut files: Vec<RuntimeFile> = SERDE
.files()
.map(|f| RuntimeFile {
relative_path: format!("com/novi/serde/{}", f.path().display()),
contents: f.contents().to_vec(),
})
.collect();
files.extend(BINCODE.files().map(|f| RuntimeFile {
relative_path: format!("com/novi/bincode/{}", f.path().display()),
contents: f.contents().to_vec(),
}));
files
}
fn imports(&self, config: &CodeGeneratorConfig) -> Vec<String> {
let bp = resolve_package(config, BINCODE_NAMESPACE, "com.novi.bincode");
let sp = resolve_package(config, SERDE_NAMESPACE, "com.novi.serde");
let mut imports = vec![
format!("import {bp}.BincodeDeserializer"),
format!("import {bp}.BincodeSerializer"),
format!("import {sp}.DeserializationError"),
format!("import {sp}.Deserializer"),
format!("import {sp}.Serializer"),
];
for feature in &config.features {
match feature {
Feature::Bytes => {
imports.push(format!("import {sp}.Bytes"));
}
Feature::Uuid => {
imports.push(format!("import {sp}.Bytes"));
imports.push("import java.util.UUID".to_string());
}
Feature::BigInt => {
imports.push("import java.math.BigInteger".to_string());
imports.push(format!("import {sp}.Int128"));
}
_ => {}
}
}
imports
}
fn module_helpers(
&self,
w: &mut dyn IndentWrite,
config: &CodeGeneratorConfig,
) -> io::Result<()> {
for feature in &config.features {
match feature {
Feature::ListOfT => {
write!(w, "{FEATURE_LIST_OF_T}")?;
writeln!(w)?;
}
Feature::OptionOfT => {
write!(w, "{FEATURE_OPTION_OF_T}")?;
writeln!(w)?;
}
Feature::SetOfT => {
write!(w, "{FEATURE_SET_OF_T}")?;
writeln!(w)?;
}
Feature::MapOfT => {
write!(w, "{FEATURE_MAP_OF_T}")?;
writeln!(w)?;
}
Feature::Uuid => {
write!(w, "{FEATURE_UUID}")?;
writeln!(w)?;
}
_ => {}
}
}
Ok(())
}
fn has_type_body(&self, _ctx: &EmitContext) -> bool {
true
}
fn type_body_preamble(&self, w: &mut dyn IndentWrite, ctx: &EmitContext) -> io::Result<()> {
if ctx.is_variant() {
return Ok(());
}
if let ContainerFormat::Enum(variants, _) = ctx.container.format {
let all_unit = variants
.values()
.all(|v| matches!(v.value, VariantFormat::Unit));
if !all_unit {
{
let config = w.config();
let mut iw = IndentedWriter::new(&mut *w, config);
writeln!(iw, "fun serialize(serializer: Serializer)")?;
writeln!(iw)?;
write_bincode_serialize(&mut iw)?;
writeln!(iw)?;
}
}
}
Ok(())
}
fn type_body(&self, w: &mut dyn IndentWrite, ctx: &EmitContext) -> io::Result<()> {
let name = ctx.name();
let fields = ctx.fields();
if let Some(variant_info) = &ctx.variant {
let variant_index = variant_info.index;
{
let config = w.config();
let mut iw = IndentedWriter::new(&mut *w, config);
if fields.is_empty() {
write_data_object_variant(&mut iw, name, variant_index)?;
} else {
write_data_class_variant(&mut iw, name, &fields, variant_index)?;
}
}
return Ok(());
}
if let ContainerFormat::Enum(variants, _) = ctx.container.format {
let all_unit = variants
.values()
.all(|v| matches!(v.value, VariantFormat::Unit));
{
let config = w.config();
let mut iw = IndentedWriter::new(&mut *w, config);
if all_unit {
write_enum_class_body(&mut iw, name, variants)?;
} else {
write_sealed_interface_body(&mut iw, name, variants)?;
}
}
return Ok(());
}
{
let config = w.config();
let mut iw = IndentedWriter::new(&mut *w, config);
if fields.is_empty() {
write_data_object_top_level(&mut iw, name)?;
} else {
write_data_class_top_level(&mut iw, name, &fields)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generation::CodeGeneratorConfig;
use std::collections::BTreeSet;
fn make_config(features: &[Feature]) -> CodeGeneratorConfig {
let mut cfg = CodeGeneratorConfig::new("com.example".to_string());
cfg.features = features.iter().copied().collect::<BTreeSet<_>>();
cfg
}
#[test]
fn base_imports_are_present() {
let cfg = make_config(&[]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let imports = plugin.imports(&cfg);
assert!(imports.iter().any(|i| i.contains("BincodeSerializer")));
assert!(imports.iter().any(|i| i.contains("BincodeDeserializer")));
assert!(imports.iter().any(|i| i.contains("Serializer")));
assert!(imports.iter().any(|i| i.contains("Deserializer")));
assert!(imports.iter().any(|i| i.contains("DeserializationError")));
}
#[test]
fn bytes_feature_adds_import() {
let cfg = make_config(&[Feature::Bytes]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let imports = plugin.imports(&cfg);
assert!(imports.iter().any(|i| i.contains("Bytes")));
}
#[test]
fn bigint_feature_adds_imports() {
let cfg = make_config(&[Feature::BigInt]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let imports = plugin.imports(&cfg);
assert!(imports.iter().any(|i| i.contains("BigInteger")));
assert!(imports.iter().any(|i| i.contains("Int128")));
}
#[test]
fn module_helpers_emit_list_of_t() {
let cfg = make_config(&[Feature::ListOfT]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let mut buf = Vec::new();
{
let mut w = IndentedWriter::new(&mut buf, cfg.indent);
plugin.module_helpers(&mut w, &cfg).unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(!output.is_empty());
}
#[test]
fn has_type_body_always_true() {
use crate::generation::Container;
use crate::reflection::format::{ContainerFormat, Doc, QualifiedTypeName};
let cfg = make_config(&[]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let name = QualifiedTypeName::root("Foo".to_string());
let format = ContainerFormat::UnitStruct(Doc::default());
let container = Container {
name: &name,
format: &format,
};
let ctx = EmitContext::top_level(&container, &cfg);
assert!(plugin.has_type_body(&ctx));
}
#[test]
fn type_body_preamble_sealed_interface() {
use crate::generation::Container;
use crate::reflection::format::{ContainerFormat, Doc, Format, QualifiedTypeName};
use std::collections::BTreeMap;
let cfg = make_config(&[]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let mut variants = BTreeMap::new();
variants.insert(
0,
Named {
name: "A".to_string(),
doc: Doc::default(),
value: VariantFormat::NewType(Box::new(Format::Str)),
},
);
let name = QualifiedTypeName::root("MyEnum".to_string());
let format = ContainerFormat::Enum(variants, Doc::default());
let container = Container {
name: &name,
format: &format,
};
let ctx = EmitContext::top_level(&container, &cfg);
let mut buf = Vec::new();
{
let mut w = IndentedWriter::new(&mut buf, cfg.indent);
plugin
.type_body_preamble(&mut w as &mut dyn IndentWrite, &ctx)
.unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(output.contains("fun serialize(serializer: Serializer)"));
assert!(output.contains("fun bincodeSerialize(): ByteArray"));
}
#[test]
fn type_body_preamble_noop_for_enum_class() {
use crate::generation::Container;
use crate::reflection::format::{ContainerFormat, Doc, QualifiedTypeName};
use std::collections::BTreeMap;
let cfg = make_config(&[]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let mut variants = BTreeMap::new();
variants.insert(
0,
Named {
name: "A".to_string(),
doc: Doc::default(),
value: VariantFormat::Unit,
},
);
let name = QualifiedTypeName::root("MyEnum".to_string());
let format = ContainerFormat::Enum(variants, Doc::default());
let container = Container {
name: &name,
format: &format,
};
let ctx = EmitContext::top_level(&container, &cfg);
let mut buf = Vec::new();
{
let mut w = IndentedWriter::new(&mut buf, cfg.indent);
plugin
.type_body_preamble(&mut w as &mut dyn IndentWrite, &ctx)
.unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(output.is_empty());
}
#[test]
fn type_body_data_object_top_level() {
use crate::generation::Container;
use crate::reflection::format::{ContainerFormat, Doc, QualifiedTypeName};
let cfg = make_config(&[]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let name = QualifiedTypeName::root("UnitStruct".to_string());
let format = ContainerFormat::UnitStruct(Doc::default());
let container = Container {
name: &name,
format: &format,
};
let ctx = EmitContext::top_level(&container, &cfg);
let mut buf = Vec::new();
{
let mut w = IndentedWriter::new(&mut buf, cfg.indent);
plugin
.type_body(&mut w as &mut dyn IndentWrite, &ctx)
.unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(output.contains("fun serialize(serializer: Serializer)"));
assert!(output.contains("fun bincodeSerialize(): ByteArray"));
assert!(output.contains("fun deserialize(deserializer: Deserializer): UnitStruct"));
assert!(output.contains("fun bincodeDeserialize(input: ByteArray?): UnitStruct"));
}
#[test]
fn type_body_data_class_top_level() {
use crate::generation::Container;
use crate::reflection::format::{ContainerFormat, Doc, QualifiedTypeName};
let cfg = make_config(&[]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let name = QualifiedTypeName::root("MyStruct".to_string());
let fields = vec![
Named::new(&Format::Str, "name".to_string()),
Named::new(&Format::I32, "age".to_string()),
];
let format = ContainerFormat::Struct(fields, Doc::default());
let container = Container {
name: &name,
format: &format,
};
let ctx = EmitContext::top_level(&container, &cfg);
let mut buf = Vec::new();
{
let mut w = IndentedWriter::new(&mut buf, cfg.indent);
plugin
.type_body(&mut w as &mut dyn IndentWrite, &ctx)
.unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(output.contains("fun serialize(serializer: Serializer)"));
assert!(output.contains("serializer.serialize_str(name)"));
assert!(output.contains("serializer.serialize_i32(age)"));
assert!(output.contains("companion object"));
assert!(output.contains("fun deserialize(deserializer: Deserializer): MyStruct"));
assert!(output.contains("return MyStruct(name, age)"));
assert!(output.contains("fun bincodeDeserialize(input: ByteArray?): MyStruct"));
}
#[test]
fn type_body_enum_top_level_skips_to_avoid_duplication() {
use crate::generation::Container;
use crate::reflection::format::{ContainerFormat, Doc, QualifiedTypeName};
use std::collections::BTreeMap;
let cfg = make_config(&[]);
let plugin = &BincodePlugin as &dyn EmitterPlugin<Kotlin>;
let mut variants = BTreeMap::new();
variants.insert(
0,
Named {
name: "A".to_string(),
doc: Doc::default(),
value: VariantFormat::Unit,
},
);
let name = QualifiedTypeName::root("MyEnum".to_string());
let format = ContainerFormat::Enum(variants, Doc::default());
let container = Container {
name: &name,
format: &format,
};
let ctx = EmitContext::top_level(&container, &cfg);
let mut buf = Vec::new();
{
let mut w = IndentedWriter::new(&mut buf, cfg.indent);
plugin
.type_body(&mut w as &mut dyn IndentWrite, &ctx)
.unwrap();
}
let output = String::from_utf8(buf).unwrap();
assert!(!output.is_empty());
assert!(output.contains("fun serialize(serializer: Serializer)"));
assert!(output.contains("fun deserialize(deserializer: Deserializer)"));
}
}