spacegate_plugin/
model.rs1use serde::{Deserialize, Serialize};
2use spacegate_kernel::service::http_route::match_request::HttpPathMatchRewrite;
3
4#[derive(Default, Debug, Serialize, Deserialize, Clone)]
5#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
6pub struct SgHttpPathModifier {
7 pub kind: SgHttpPathModifierType,
9 pub value: String,
11}
12
13#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default, Copy)]
14#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15#[serde(rename_all = "PascalCase")]
16pub enum SgHttpPathModifierType {
17 ReplaceFullPath,
19 #[default]
22 ReplacePrefixMatch,
23 ReplaceRegex,
24}
25
26impl SgHttpPathModifier {
27 pub fn replace(&self, path: &str, path_match: &HttpPathMatchRewrite) -> Option<String> {
28 let value = &self.value;
29 match (self.kind, path_match) {
30 (SgHttpPathModifierType::ReplaceFullPath, _) => {
31 if value.eq_ignore_ascii_case(path) {
32 Some(value.clone())
33 } else {
34 None
35 }
36 }
37 (SgHttpPathModifierType::ReplacePrefixMatch, HttpPathMatchRewrite::Prefix(prefix, _)) => {
38 fn not_empty(s: &&str) -> bool {
39 !s.is_empty()
40 }
41 let mut path_segments = path.split('/').filter(not_empty);
42 let mut prefix_segments = prefix.split('/').filter(not_empty);
43 loop {
44 match (path_segments.next(), prefix_segments.next()) {
45 (Some(path_seg), Some(prefix_seg)) => {
46 if !path_seg.eq_ignore_ascii_case(prefix_seg) {
47 return None;
48 }
49 }
50 (None, None) => {
51 let mut new_path = String::from("/");
53 new_path.push_str(self.value.trim_start_matches('/'));
54 return Some(new_path);
55 }
56 (Some(rest_path), None) => {
57 let mut new_path = String::from("/");
58 let replace_value = self.value.trim_matches('/');
59 new_path.push_str(replace_value);
60 if !replace_value.is_empty() {
61 new_path.push('/');
62 }
63 new_path.push_str(rest_path);
64 for seg in path_segments {
65 new_path.push('/');
66 new_path.push_str(seg);
67 }
68 if path.ends_with('/') {
69 new_path.push('/')
70 }
71 return Some(new_path);
72 }
73 (None, Some(_)) => return None,
74 }
75 }
76 }
77 (SgHttpPathModifierType::ReplaceRegex, HttpPathMatchRewrite::RegExp(re, _)) => Some(re.replace(path, value).to_string()),
78 _ => None,
79 }
80 }
81}
82
83#[test]
84fn test_prefix_replace() {
85 let modifier = SgHttpPathModifier {
86 kind: SgHttpPathModifierType::ReplacePrefixMatch,
87 value: "/iam".into(),
88 };
89 let replace = HttpPathMatchRewrite::prefix("api/iam");
90 assert_eq!(Some("/iam/get_name"), modifier.replace("api/iam/get_name", &replace).as_deref());
91 assert_eq!(Some("/iam/get_name/example.js"), modifier.replace("api/iam/get_name/example.js", &replace).as_deref());
92 assert_eq!(Some("/iam/get_name/"), modifier.replace("api/iam/get_name/", &replace).as_deref());
93}
94
95#[test]
96fn test_regex_replace() {
97 let modifier = SgHttpPathModifier {
98 kind: SgHttpPathModifierType::ReplaceRegex,
99 value: "/path/$1/subpath$2".into(),
100 };
101 let replace = HttpPathMatchRewrite::regex(regex::Regex::new(r"/api/(\w*)/subpath($|/.*)").expect("invalid regex"));
102 assert_eq!(Some("/path/iam/subpath/get_name"), modifier.replace("/api/iam/subpath/get_name", &replace).as_deref());
103 assert_eq!(Some("/path/iam/subpath/"), modifier.replace("/api/iam/subpath/", &replace).as_deref());
104 assert_eq!(Some("/path/iam/subpath"), modifier.replace("/api/iam/subpath", &replace).as_deref());
105 assert_eq!(Some("/api/iam/subpath2"), modifier.replace("/api/iam/subpath2", &replace).as_deref());
107}