1use crate::error::Error;
2use crate::fetch::{FetchKind, FetchOutputModes, FetchPhase};
3use crate::middleware::MiddlewareKind;
4use crate::wasm_runtime::PluginExport;
5
6pub struct MiddlewareMetadata {
7 pub kind: MiddlewareKind,
8 pub stateless: bool,
9 pub needs_body: bool,
10 pub validate_args: fn(&serde_json::Value) -> Result<(), Error>,
11}
12
13fn plugin_validate_args_pass(_: &serde_json::Value) -> Result<(), Error> {
19 Ok(())
20}
21
22impl MiddlewareMetadata {
23 #[must_use]
30 pub fn from_plugin(export: &PluginExport) -> Self {
31 Self {
32 kind: export.kind,
33 stateless: export.stateless,
34 needs_body: export.needs_body,
35 validate_args: plugin_validate_args_pass,
36 }
37 }
38}
39
40pub trait MiddlewareMetadataProvider {
41 fn get(&self, name: &str) -> Option<MiddlewareMetadata>;
42}
43
44pub struct FetchMetadata {
45 pub kind: FetchKind,
46 pub phase: FetchPhase,
47 pub output_modes: FetchOutputModes,
48 pub validate_args: fn(&serde_json::Value) -> Result<(), Error>,
49}
50
51pub trait FetchMetadataProvider {
52 fn get(&self, kind: FetchKind) -> Option<FetchMetadata>;
53}
54
55#[cfg(test)]
56mod tests {
57 use serde_json::{Value, json};
58
59 use super::*;
60 use crate::error::Error;
61
62 fn reject_null_accept_object(v: &Value) -> Result<(), Error> {
63 match v {
64 Value::Object(_) => Ok(()),
65 _ => Err(Error::compile("expected object")),
66 }
67 }
68
69 struct StaticMwProvider;
70 impl MiddlewareMetadataProvider for StaticMwProvider {
71 fn get(&self, name: &str) -> Option<MiddlewareMetadata> {
72 if name == "rate_limit" {
73 Some(MiddlewareMetadata {
74 kind: MiddlewareKind::L7Request,
75 stateless: false,
76 needs_body: false,
77 validate_args: reject_null_accept_object,
78 })
79 } else {
80 None
81 }
82 }
83 }
84
85 struct StaticFetchProvider;
86 impl FetchMetadataProvider for StaticFetchProvider {
87 fn get(&self, kind: FetchKind) -> Option<FetchMetadata> {
88 if kind == FetchKind::HttpProxy {
89 Some(FetchMetadata {
90 kind: FetchKind::HttpProxy,
91 phase: FetchPhase::L7,
92 output_modes: FetchOutputModes { response: true, tunnel: false },
93 validate_args: reject_null_accept_object,
94 })
95 } else {
96 None
97 }
98 }
99 }
100
101 #[test]
102 fn middleware_provider_returns_known_record_and_none_for_unknown() {
103 let p = StaticMwProvider;
104 let meta = p.get("rate_limit").expect("known entry");
105 assert_eq!(meta.kind, MiddlewareKind::L7Request);
106 assert!(!meta.stateless);
107 assert!(!meta.needs_body);
108 assert!(p.get("no_such_middleware").is_none());
109 }
110
111 #[test]
112 fn middleware_validate_args_fn_pointer_dispatches() {
113 let p = StaticMwProvider;
114 let meta = p.get("rate_limit").expect("known entry");
115 assert!((meta.validate_args)(&Value::Null).is_err());
116 assert!((meta.validate_args)(&json!({ "rate": 100 })).is_ok());
117 }
118
119 #[test]
120 fn middleware_provider_is_object_safe() {
121 let p: &dyn MiddlewareMetadataProvider = &StaticMwProvider;
122 assert!(p.get("rate_limit").is_some());
123 assert!(p.get("unknown").is_none());
124 }
125
126 #[test]
127 fn fetch_provider_returns_known_kind_and_none_for_unknown() {
128 let p = StaticFetchProvider;
129 let meta = p.get(FetchKind::HttpProxy).expect("known kind");
130 assert_eq!(meta.kind, FetchKind::HttpProxy);
131 assert_eq!(meta.phase, FetchPhase::L7);
132 assert_eq!(meta.output_modes, FetchOutputModes { response: true, tunnel: false });
133 assert!(p.get(FetchKind::L4Forward).is_none());
134 }
135
136 #[test]
137 fn fetch_validate_args_fn_pointer_dispatches() {
138 let p = StaticFetchProvider;
139 let meta = p.get(FetchKind::HttpProxy).expect("known kind");
140 assert!((meta.validate_args)(&Value::Null).is_err());
141 assert!((meta.validate_args)(&json!({ "upstream": "127.0.0.1:80" })).is_ok());
142 }
143
144 #[test]
145 fn fetch_provider_is_object_safe() {
146 let p: &dyn FetchMetadataProvider = &StaticFetchProvider;
147 assert!(p.get(FetchKind::HttpProxy).is_some());
148 assert!(p.get(FetchKind::WebSocketUpgrade).is_none());
149 }
150
151 #[test]
152 fn middleware_metadata_from_plugin_copies_fields() {
153 let export = PluginExport {
154 name: "jwt-validator".to_string(),
155 kind: MiddlewareKind::L7Request,
156 stateless: false,
157 needs_body: true,
158 inspects: vec!["http.header.authorization".to_string()],
159 };
160 let meta = MiddlewareMetadata::from_plugin(&export);
161 assert_eq!(meta.kind, MiddlewareKind::L7Request);
162 assert!(!meta.stateless);
163 assert!(meta.needs_body);
164 assert!((meta.validate_args)(&Value::Null).is_ok());
167 assert!((meta.validate_args)(&json!({ "skew": 30 })).is_ok());
168 }
169}