Disambiguate generated argument types when distinct operation names normalize to the same PascalCase form, and add the requested Number round-trip and collision regression coverage.
169 lines
4.8 KiB
Rust
169 lines
4.8 KiB
Rust
use wip_interface::wip_protocol::{
|
|
EnumDeclaration, JsonMember, JsonValue, NamedValue, RecordDeclaration, TypeDeclaration,
|
|
TypeExpr, UnionDeclaration, Value,
|
|
};
|
|
use wip_interface::{Bytes, Entry, WipType};
|
|
|
|
/// A request record.
|
|
#[derive(Debug, Clone, PartialEq, WipType)]
|
|
struct Request {
|
|
/// Required title.
|
|
title: String,
|
|
/// Optional priority.
|
|
priority: Option<i32>,
|
|
/// Related entries.
|
|
related: Vec<Entry<String, bool>>,
|
|
/// Opaque bytes.
|
|
body: Bytes,
|
|
/// Arbitrary metadata.
|
|
metadata: JsonValue,
|
|
}
|
|
|
|
/// Available colors.
|
|
#[derive(Debug, Clone, PartialEq, WipType)]
|
|
enum Color {
|
|
/// Warm red.
|
|
Red,
|
|
/// Cool blue.
|
|
Blue,
|
|
}
|
|
|
|
/// A receipt record.
|
|
#[derive(Debug, Clone, PartialEq, WipType)]
|
|
struct Receipt {
|
|
/// Receipt number.
|
|
number: u32,
|
|
}
|
|
|
|
/// Application-level publication outcome.
|
|
#[derive(Debug, Clone, PartialEq, WipType)]
|
|
enum PublishOutcome {
|
|
/// Application success.
|
|
Published(Receipt),
|
|
/// Domain rejection, not a host failure.
|
|
Rejected(String),
|
|
}
|
|
|
|
#[test]
|
|
fn supported_values_round_trip() {
|
|
let request = Request {
|
|
title: "hello".into(),
|
|
priority: None,
|
|
related: vec![Entry {
|
|
key: "/articles/1".into(),
|
|
value: true,
|
|
}],
|
|
body: Bytes(vec![1, 2, 3]),
|
|
metadata: JsonValue::Object(vec![JsonMember {
|
|
name: "source".into(),
|
|
value: JsonValue::String("test".into()),
|
|
}]),
|
|
};
|
|
|
|
let encoded = request.clone().encode().unwrap();
|
|
let Value::Record(fields) = &encoded else {
|
|
panic!("record derive must encode a protocol record")
|
|
};
|
|
assert!(fields.iter().all(|field| field.name != "priority"));
|
|
assert_eq!(Request::decode(encoded).unwrap(), request);
|
|
|
|
let number = 42.5_f64;
|
|
assert_eq!(f64::type_expr(), TypeExpr::Number);
|
|
assert_eq!(f64::decode(number.encode().unwrap()).unwrap(), number);
|
|
|
|
let narrow_number = 1.5_f32;
|
|
assert_eq!(f32::type_expr(), TypeExpr::Number);
|
|
assert_eq!(
|
|
f32::decode(narrow_number.encode().unwrap()).unwrap(),
|
|
narrow_number
|
|
);
|
|
|
|
for color in [Color::Red, Color::Blue] {
|
|
let encoded = color.clone().encode().unwrap();
|
|
let decoded = Color::decode(encoded).unwrap();
|
|
assert_eq!(decoded, color);
|
|
}
|
|
|
|
for outcome in [
|
|
PublishOutcome::Published(Receipt { number: 7 }),
|
|
PublishOutcome::Rejected("policy".into()),
|
|
] {
|
|
let encoded = outcome.clone().encode().unwrap();
|
|
let decoded = PublishOutcome::decode(encoded).unwrap();
|
|
assert_eq!(decoded, outcome);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn derived_declarations_include_docs_optionality_and_dependencies() {
|
|
let mut declarations = Vec::new();
|
|
Request::collect_declarations(&mut declarations);
|
|
Color::collect_declarations(&mut declarations);
|
|
PublishOutcome::collect_declarations(&mut declarations);
|
|
|
|
let TypeDeclaration::Record(RecordDeclaration {
|
|
documentation,
|
|
fields,
|
|
..
|
|
}) = &declarations[0]
|
|
else {
|
|
panic!("Request must be a record declaration")
|
|
};
|
|
assert_eq!(documentation.as_ref().unwrap().summary, "A request record.");
|
|
assert_eq!(
|
|
fields[0].documentation.as_ref().unwrap().summary,
|
|
"Required title."
|
|
);
|
|
assert!(!fields[0].optional);
|
|
assert!(fields[1].optional);
|
|
assert_eq!(fields[1].ty, TypeExpr::Integer);
|
|
assert_eq!(
|
|
fields[2].ty,
|
|
TypeExpr::List(Box::new(TypeExpr::Entry {
|
|
key: Box::new(TypeExpr::String),
|
|
value: Box::new(TypeExpr::Boolean),
|
|
}))
|
|
);
|
|
|
|
let color = declarations
|
|
.iter()
|
|
.find(|declaration| declaration.name() == "Color")
|
|
.unwrap();
|
|
let TypeDeclaration::Enum(EnumDeclaration { variants, .. }) = color else {
|
|
panic!("Color must be an enum declaration")
|
|
};
|
|
assert_eq!(
|
|
variants[0].documentation.as_ref().unwrap().summary,
|
|
"Warm red."
|
|
);
|
|
|
|
let outcome = declarations
|
|
.iter()
|
|
.find(|declaration| declaration.name() == "PublishOutcome")
|
|
.unwrap();
|
|
let TypeDeclaration::Union(UnionDeclaration { variants, .. }) = outcome else {
|
|
panic!("PublishOutcome must be a union declaration")
|
|
};
|
|
assert_eq!(variants[0].ty, TypeExpr::Named("Receipt".into()));
|
|
assert!(
|
|
declarations
|
|
.iter()
|
|
.any(|declaration| declaration.name() == "Receipt")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn codecs_reject_invalid_boundaries() {
|
|
assert!(i64::MAX.encode().is_err());
|
|
assert!(f64::NAN.encode().is_err());
|
|
assert!(Request::decode(Value::Record(vec![])).is_err());
|
|
assert!(
|
|
Request::decode(Value::Record(vec![NamedValue {
|
|
name: "unknown".into(),
|
|
value: Value::String("value".into()),
|
|
}]))
|
|
.is_err()
|
|
);
|
|
assert!(Color::decode(Value::Enum("Green".into())).is_err());
|
|
}
|