use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Implementation {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub version: String,
#[serde(default, rename = "_meta", skip_serializing_if = "BTreeMap::is_empty")]
pub meta: BTreeMap<String, serde_json::Value>,
}
impl Implementation {
pub fn protocol_droid(name: &str, version: &str) -> Self {
Self {
name: name.to_owned(),
title: Some("protocol-droid".to_owned()),
version: version.to_owned(),
meta: BTreeMap::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
#[expect(
dead_code,
reason = "the test only constructs the error variant on failure; the field is read by the Debug derive"
)]
enum Failure {
Json(serde_json::Error),
}
impl From<serde_json::Error> for Failure {
fn from(error: serde_json::Error) -> Self {
Self::Json(error)
}
}
#[test]
fn the_inner_field_names_are_already_lowercase() -> Result<(), Failure> {
let info = Implementation::protocol_droid("test-client", "0.1.0");
let value = serde_json::to_value(&info)?;
let name = value.get("name").and_then(serde_json::Value::as_str);
let title = value.get("title").and_then(serde_json::Value::as_str);
let version = value.get("version").and_then(serde_json::Value::as_str);
assert_eq!(name, Some("test-client"));
assert_eq!(title, Some("protocol-droid"));
assert_eq!(version, Some("0.1.0"));
assert!(
value.get("_meta").is_none(),
"empty _meta is omitted from the wire"
);
Ok(())
}
#[test]
fn serialize_then_deserialize_round_trips() -> Result<(), Failure> {
let mut info = Implementation::protocol_droid("test", "0.1.0");
info.meta
.insert("k".to_owned(), serde_json::json!({"nested": 42}));
let json = serde_json::to_string(&info)?;
let round_tripped: Implementation = serde_json::from_str(&json)?;
assert_eq!(round_tripped, info);
Ok(())
}
}