use std::io;
use crate::generation::{
CodeGeneratorConfig, Feature,
indent::IndentWrite,
kotlin::Kotlin,
plugin::{EmitContext, EmitterPlugin, RuntimeFile},
};
use crate::reflection::format::{ContainerFormat, VariantFormat};
use super::JsonPlugin;
const FEATURE_BIGINT: &str = r#"typealias BigInteger = @Serializable(with = BigIntegerSerializer::class) BigInteger
private object BigIntegerSerializer : KSerializer<BigInteger> {
override val descriptor =
PrimitiveSerialDescriptor("java.math.BigInteger", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): BigInteger =
when (decoder) {
is JsonDecoder -> decoder.decodeJsonElement().jsonPrimitive.content.toBigInteger()
else -> decoder.decodeString().toBigInteger()
}
override fun serialize(encoder: Encoder, value: BigInteger) =
when (encoder) {
is JsonEncoder -> encoder.encodeJsonElement(JsonUnquotedLiteral(value.toString()))
else -> encoder.encodeString(value.toString())
}
}
"#;
const FEATURE_UUID: &str = r#"private object UUIDSerializer : KSerializer<java.util.UUID> {
override val descriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): java.util.UUID = java.util.UUID.fromString(decoder.decodeString())
override fun serialize(encoder: Encoder, value: java.util.UUID) = encoder.encodeString(value.toString())
}
typealias UUID = @Serializable(with = UUIDSerializer::class) java.util.UUID
"#;
impl EmitterPlugin<Kotlin> for JsonPlugin {
fn runtime_files(&self) -> Vec<RuntimeFile> {
static SERDE: include_dir::Dir<'static> =
include_dir::include_dir!("$CARGO_MANIFEST_DIR/runtime/kotlin/com/novi/serde");
SERDE
.files()
.map(|f| RuntimeFile {
relative_path: format!("com/novi/serde/{}", f.path().display()),
contents: f.contents().to_vec(),
})
.collect()
}
fn manifest_dependencies(&self) -> Vec<String> {
vec![
r#" implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")"#
.to_string(),
]
}
fn imports(&self, config: &CodeGeneratorConfig) -> Vec<String> {
let mut imports = vec![
"import kotlinx.serialization.Serializable".to_string(),
"import kotlinx.serialization.SerialName".to_string(),
];
if config.features.contains(&Feature::Uuid) {
imports.extend([
"import kotlinx.serialization.KSerializer".to_string(),
"import kotlinx.serialization.descriptors.PrimitiveKind".to_string(),
"import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor".to_string(),
"import kotlinx.serialization.encoding.Decoder".to_string(),
"import kotlinx.serialization.encoding.Encoder".to_string(),
]);
}
if config.features.contains(&Feature::BigInt) {
imports.extend([
"import kotlinx.serialization.KSerializer".to_string(),
"import kotlinx.serialization.descriptors.PrimitiveKind".to_string(),
"import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor".to_string(),
"import kotlinx.serialization.encoding.Decoder".to_string(),
"import kotlinx.serialization.encoding.Encoder".to_string(),
"import kotlinx.serialization.json.JsonDecoder".to_string(),
"import kotlinx.serialization.json.JsonEncoder".to_string(),
"import kotlinx.serialization.json.JsonUnquotedLiteral".to_string(),
"import kotlinx.serialization.json.jsonPrimitive".to_string(),
]);
}
imports
}
fn module_helpers(
&self,
w: &mut dyn IndentWrite,
config: &CodeGeneratorConfig,
) -> io::Result<()> {
if config.features.contains(&Feature::Uuid) {
write!(w, "{FEATURE_UUID}")?;
writeln!(w)?;
}
if config.features.contains(&Feature::BigInt) {
write!(w, "{FEATURE_BIGINT}")?;
writeln!(w)?;
}
Ok(())
}
fn type_annotations(&self, ctx: &EmitContext) -> Vec<String> {
let name = ctx.name();
vec![
"@Serializable".to_string(),
format!(r#"@SerialName("{name}")"#),
]
}
fn enum_variant_annotations(&self, name: &str) -> Vec<String> {
vec![format!(r#"@SerialName("{name}")"#)]
}
fn type_body(&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 {
writeln!(w)?;
writeln!(w, "val serialName: String")?;
writeln!(
w,
" get() = javaClass.getDeclaredField(name).getAnnotation(SerialName::class.java)!!.value"
)?;
}
}
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 = &JsonPlugin as &dyn EmitterPlugin<Kotlin>;
let imports = plugin.imports(&cfg);
assert!(imports.iter().any(|i| i.contains("Serializable")));
assert!(imports.iter().any(|i| i.contains("SerialName")));
}
#[test]
fn bigint_adds_json_imports() {
let cfg = make_config(&[Feature::BigInt]);
let plugin = &JsonPlugin as &dyn EmitterPlugin<Kotlin>;
let imports = plugin.imports(&cfg);
assert!(imports.iter().any(|i| i.contains("KSerializer")));
assert!(imports.iter().any(|i| i.contains("PrimitiveKind")));
assert!(imports.iter().any(|i| i.contains("JsonDecoder")));
assert!(imports.iter().any(|i| i.contains("JsonEncoder")));
assert!(imports.iter().any(|i| i.contains("JsonUnquotedLiteral")));
}
#[test]
fn bigint_module_helpers_emit_feature() {
let cfg = make_config(&[Feature::BigInt]);
let plugin = &JsonPlugin as &dyn EmitterPlugin<Kotlin>;
let mut buf = Vec::new();
{
use crate::generation::indent::IndentedWriter;
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 type_annotations_include_serializable_and_serial_name() {
use crate::generation::Container;
use crate::reflection::format::{ContainerFormat, Doc, QualifiedTypeName};
let config = make_config(&[]);
let name = QualifiedTypeName::root("Foo".to_string());
let format = ContainerFormat::Struct(vec![], Doc::default());
let container = Container {
name: &name,
format: &format,
};
let ctx = EmitContext::top_level(&container, &config);
let plugin = &JsonPlugin as &dyn EmitterPlugin<Kotlin>;
let annotations = plugin.type_annotations(&ctx);
assert_eq!(annotations.len(), 2);
assert_eq!(annotations[0], "@Serializable");
assert_eq!(annotations[1], r#"@SerialName("Foo")"#);
}
}