use super::*;
use crate::hir::{Annotation, AnnotationParams, ConstExpr, Literal};
fn builtin(name: &str, raw: &str) -> Annotation {
Annotation::Builtin {
name: name.to_string(),
params: Some(AnnotationParams::Raw(raw.to_string())),
}
}
fn string(value: &str) -> ConstExpr {
ConstExpr::Literal(Literal::StringLiteral(format!("\"{value}\"")))
}
fn positional(name: &str, values: Vec<ConstExpr>) -> Annotation {
Annotation::Builtin {
name: name.to_string(),
params: Some(AnnotationParams::Positional(values)),
}
}
#[test]
fn deprecated_info_normalizes_dates_and_validates_ranges() {
assert_eq!(deprecated_info(&[]).unwrap(), None);
let info = deprecated_info(&[builtin(
"deprecated",
r#"since="2026-01-02", after="2026-01-03""#,
)])
.expect("deprecated info")
.expect("present");
assert!(info.deprecated);
assert!(info.since.is_some());
assert!(info.after.is_some());
let value_alias = deprecated_info(&[builtin("deprecated", r#""2026-01-05T01:02:03Z""#)])
.expect("deprecated value alias")
.expect("present");
assert_eq!(value_alias.since.as_deref(), Some("2026-01-05T01:02:03Z"));
assert_eq!(value_alias.after, None);
let err = deprecated_info(&[builtin(
"deprecated",
r#"since="2026-01-04", after="2026-01-03""#,
)])
.expect_err("invalid range");
assert!(err.contains("since <= after"));
let err = deprecated_info(&[builtin("deprecated", r#"since="not-a-date""#)])
.expect_err("invalid timestamp");
assert!(err.contains("invalid @deprecated timestamp literal"));
}
#[test]
fn validate_http_annotations_checks_security_and_media_type() {
validate_http_annotations(
"operation 'ok'",
&[
positional(
"cors",
vec![
string("https://app.example.com"),
string("https://admin.example.com"),
],
),
builtin("request", r#""json""#),
builtin("response", r#""msgpack""#),
],
)
.expect("valid annotation set");
let err = validate_http_annotations("operation 'bad'", &[builtin("response", r#""xml""#)])
.expect_err("bad media type");
assert!(err.contains(
"unsupported @response(\"xml\") format, expected 'json', 'urlencoded', or 'msgpack'"
));
let err = validate_http_annotations(
"operation 'mime'",
&[builtin("request", r#""application/json""#)],
)
.expect_err("mime string media type");
assert!(err.contains("unsupported @request(\"application/json\") format"));
let err = validate_http_annotations(
"operation 'plain'",
&[builtin("response", r#""text/plain""#)],
)
.expect_err("explicit text/plain");
assert!(err.contains("unsupported @response(\"text/plain\") format"));
let err = validate_http_annotations(
"operation 'secure'",
&[builtin("no_security", ""), builtin("http_basic", "")],
)
.expect_err("bad security");
assert!(err.contains("operation 'secure': @no_security cannot be combined"));
let err = validate_http_annotations(
"operation 'cors'",
&[builtin("cors", r#"origin="https://app.example.com""#)],
)
.expect_err("bad cors");
assert!(err.contains("@cors only accepts comma-separated string literals"));
validate_http_annotations(
"operation 'empty'",
&[builtin("response", ""), Annotation::Final],
)
.expect("empty media annotation should be ignored");
}