use serde::{Deserialize, Serialize};
mod sealed {
pub trait Sealed {}
impl Sealed for String {}
impl Sealed for Vec<String> {}
impl Sealed for crate::Id {}
impl Sealed for Vec<crate::Id> {}
impl Sealed for crate::Date {}
impl Sealed for Vec<crate::Date> {}
impl Sealed for crate::UTCDate {}
impl Sealed for Vec<crate::UTCDate> {}
impl Sealed for crate::State {}
impl Sealed for Vec<crate::State> {}
impl Sealed for u32 {}
impl Sealed for u64 {}
impl Sealed for bool {}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ResultReference {
#[serde(rename = "resultOf")]
pub result_of: String,
pub name: String,
pub path: String,
}
impl ResultReference {
pub fn new(
result_of: impl Into<String>,
name: impl Into<String>,
path: impl Into<String>,
) -> Self {
Self {
result_of: result_of.into(),
name: name.into(),
path: path.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Argument<T: sealed::Sealed> {
Value(T),
Ref(ResultReference),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn result_reference_round_trip() {
let rr = ResultReference {
result_of: "0".into(),
name: "ChatContact/get".into(),
path: "/list/0/id".into(),
};
let json_str = serde_json::to_string(&rr).unwrap();
let rr2: ResultReference = serde_json::from_str(&json_str).unwrap();
assert_eq!(rr, rr2);
}
#[test]
fn result_reference_field_names() {
let rr = ResultReference {
result_of: "req-1".into(),
name: "Chat/get".into(),
path: "/id".into(),
};
let json_str = serde_json::to_string(&rr).unwrap();
assert!(
json_str.contains("\"resultOf\""),
"must use camelCase resultOf"
);
assert!(json_str.contains("\"name\""));
assert!(json_str.contains("\"path\""));
assert!(
!json_str.contains("\"result_of\""),
"must not use snake_case"
);
}
#[test]
fn argument_value_serializes_as_inner_type() {
let arg: Argument<u32> = Argument::Value(42);
let json_str = serde_json::to_string(&arg).unwrap();
assert_eq!(json_str, "42");
}
#[test]
fn argument_ref_serializes_as_result_reference() {
let rr = ResultReference {
result_of: "0".into(),
name: "ChatContact/get".into(),
path: "/list/0/id".into(),
};
let arg: Argument<Vec<String>> = Argument::Ref(rr);
let json_str = serde_json::to_string(&arg).unwrap();
assert!(json_str.contains("\"resultOf\""));
assert!(json_str.contains("\"ChatContact/get\""));
}
#[test]
fn argument_value_vec_string_deserializes() {
let json_str = r#"["alice","bob"]"#;
let arg: Argument<Vec<String>> = serde_json::from_str(json_str).unwrap();
match arg {
Argument::Value(v) => assert_eq!(v, vec!["alice", "bob"]),
Argument::Ref(_) => panic!("expected Value variant"),
}
}
#[test]
fn result_reference_deserializes_from_fixture() {
let raw = include_str!("../tests/fixtures/rfc8620-result-reference.json");
let rr: ResultReference = serde_json::from_str(raw).expect("deserialize ResultReference");
assert_eq!(rr.result_of, "t0");
assert_eq!(rr.name, "Foo/changes");
assert_eq!(rr.path, "/created");
}
#[test]
fn argument_ref_deserializes() {
let j = r#"{"resultOf":"0","name":"X/get","path":"/ids"}"#;
let arg: Argument<Vec<String>> = serde_json::from_str(j).expect("deser");
match arg {
Argument::Ref(rr) => {
assert_eq!(rr.result_of, "0");
assert_eq!(rr.name, "X/get");
}
Argument::Value(_) => panic!("expected Ref"),
}
}
#[test]
fn argument_string_newtypes_compile_and_round_trip() {
use crate::{Date, State, UTCDate};
let arg_date: Argument<Date> = Argument::Value(Date::from("2024-06-15"));
let j_date = serde_json::to_string(&arg_date).expect("serialize Date Value");
assert_eq!(j_date, "\"2024-06-15\"");
let back_date: Argument<Date> =
serde_json::from_str(&j_date).expect("deserialize Date Value");
assert_eq!(arg_date, back_date);
let arg_date_list: Argument<Vec<Date>> = Argument::Value(vec![Date::from("2024-06-15")]);
let _ = serde_json::to_string(&arg_date_list).expect("Vec<Date> Value");
let arg_utc: Argument<UTCDate> = Argument::Value(UTCDate::from("2024-06-15T09:00:00Z"));
let j_utc = serde_json::to_string(&arg_utc).expect("serialize UTCDate Value");
assert_eq!(j_utc, "\"2024-06-15T09:00:00Z\"");
let back_utc: Argument<UTCDate> =
serde_json::from_str(&j_utc).expect("deserialize UTCDate Value");
assert_eq!(arg_utc, back_utc);
let arg_utc_list: Argument<Vec<UTCDate>> = Argument::Value(vec![]);
let _ = serde_json::to_string(&arg_utc_list).expect("Vec<UTCDate> Value");
let arg_state: Argument<State> = Argument::Value(State::from("s-42"));
let j_state = serde_json::to_string(&arg_state).expect("serialize State Value");
assert_eq!(j_state, "\"s-42\"");
let back_state: Argument<State> =
serde_json::from_str(&j_state).expect("deserialize State Value");
assert_eq!(arg_state, back_state);
let arg_state_list: Argument<Vec<State>> = Argument::Value(vec![]);
let _ = serde_json::to_string(&arg_state_list).expect("Vec<State> Value");
let rr = ResultReference::new("0", "Email/changes", "/newState");
let arg_state_ref: Argument<State> = Argument::Ref(rr.clone());
let j_state_ref = serde_json::to_string(&arg_state_ref).expect("serialize Ref");
assert!(j_state_ref.contains("\"resultOf\""));
assert!(j_state_ref.contains("\"Email/changes\""));
let back_ref: Argument<State> =
serde_json::from_str(&j_state_ref).expect("deserialize Ref");
match back_ref {
Argument::Ref(rr2) => {
assert_eq!(rr2.result_of, "0");
assert_eq!(rr2.name, "Email/changes");
assert_eq!(rr2.path, "/newState");
}
Argument::Value(_) => panic!("expected Ref"),
}
}
}