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