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, /// Related entries. related: Vec>, /// 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()); }