Skip to main content

vane_core/
metadata.rs

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/// Pass-through validator for plugin-backed middleware. Per-plugin
14/// arg schemas live inside the WASM module itself (the export's
15/// `validate-args` host call); the compile pipeline only needs
16/// `Some(meta)` to confirm the name resolves. Schema violations
17/// surface at link time when the runtime invokes the plugin.
18#[allow(clippy::unnecessary_wraps)]
19fn plugin_validate_args_pass(_: &serde_json::Value) -> Result<(), Error> {
20	Ok(())
21}
22
23impl MiddlewareMetadata {
24	/// Build a middleware-metadata record from a plugin export. Used by
25	/// the daemon-side metadata provider to satisfy compile-stage
26	/// queries for `<module>:<export>` references — the plugin's
27	/// `kind` / `stateless` / `needs_body` map directly onto the
28	/// middleware-metadata shape; `inspects` is plugin-internal and
29	/// has no analogue here.
30	#[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		// Plugin-backed validate_args is a pass-through — schema lives
166		// in the WASM module, not in the metadata record.
167		assert!((meta.validate_args)(&Value::Null).is_ok());
168		assert!((meta.validate_args)(&json!({ "skew": 30 })).is_ok());
169	}
170}