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.
18fn plugin_validate_args_pass(_: &serde_json::Value) -> Result<(), Error> {
19	Ok(())
20}
21
22impl MiddlewareMetadata {
23	/// Build a middleware-metadata record from a plugin export. Used by
24	/// the daemon-side metadata provider to satisfy compile-stage
25	/// queries for `<module>:<export>` references — the plugin's
26	/// `kind` / `stateless` / `needs_body` map directly onto the
27	/// middleware-metadata shape; `inspects` is plugin-internal and
28	/// has no analogue here.
29	#[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		// Plugin-backed validate_args is a pass-through — schema lives
165		// in the WASM module, not in the metadata record.
166		assert!((meta.validate_args)(&Value::Null).is_ok());
167		assert!((meta.validate_args)(&json!({ "skew": 30 })).is_ok());
168	}
169}