spacegate_config/service/fs/
model.rs

1use std::{
2    collections::HashMap,
3    hash::{DefaultHasher, Hasher},
4};
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use spacegate_model::{constants::DEFAULT_API_PORT, ConfigItem, PluginInstanceId, PluginInstanceMap, PluginInstanceName, SgGateway, SgHttpRoute};
9
10#[derive(Debug, Serialize, Deserialize, Clone)]
11#[serde(untagged)]
12pub enum FsAsmPluginConfig {
13    Anon { uid: String, code: String, spec: Value },
14    Named { name: String, code: String },
15    Mono { code: String },
16}
17
18#[derive(Debug, Serialize, Deserialize, Clone)]
19#[serde(untagged)]
20pub(crate) enum FsAsmPluginConfigMaybeUninitialized {
21    Anon { uid: Option<String>, code: String, spec: Value },
22    Named { name: String, code: String },
23    Mono { code: String },
24}
25
26#[derive(Debug, Serialize, Deserialize, Clone)]
27
28pub struct FsNamedPluginConfig {
29    pub name: String,
30    pub code: String,
31    pub spec: Value,
32}
33
34#[derive(Debug, Serialize, Deserialize, Clone)]
35
36pub struct FsMonoPluginConfig {
37    pub code: String,
38    pub spec: Value,
39}
40
41#[derive(Debug, Serialize, Deserialize, Clone)]
42
43pub struct FsAnonPluginConfig {
44    pub code: String,
45    pub spec: Value,
46    pub uid: u64,
47}
48
49#[derive(Debug, Serialize, Deserialize, Clone)]
50#[serde(default)]
51pub struct MainFileConfig<P = FsAsmPluginConfig> {
52    // for config usage, list is preferred over map
53    pub gateways: Vec<MainFileConfigItem<P>>,
54    pub plugins: PluginConfigs,
55    pub api_port: u16,
56}
57
58impl<P> Default for MainFileConfig<P> {
59    fn default() -> Self {
60        MainFileConfig {
61            gateways: Default::default(),
62            plugins: Default::default(),
63            api_port: DEFAULT_API_PORT,
64        }
65    }
66}
67
68#[derive(Debug, Serialize, Deserialize, Clone)]
69#[serde(default)]
70pub struct MainFileConfigItem<P = FsAsmPluginConfig> {
71    #[serde(flatten)]
72    pub gateway: SgGateway<P>,
73    pub routes: Vec<SgHttpRoute<P>>,
74}
75impl<P> Default for MainFileConfigItem<P> {
76    fn default() -> Self {
77        MainFileConfigItem {
78            gateway: Default::default(),
79            routes: Default::default(),
80        }
81    }
82}
83impl<P> From<ConfigItem<P>> for MainFileConfigItem<P> {
84    fn from(value: ConfigItem<P>) -> Self {
85        MainFileConfigItem {
86            gateway: value.gateway,
87            routes: value.routes.into_values().collect(),
88        }
89    }
90}
91
92impl<P> From<MainFileConfigItem<P>> for ConfigItem<P> {
93    fn from(val: MainFileConfigItem<P>) -> Self {
94        ConfigItem {
95            gateway: val.gateway,
96            routes: val.routes.into_iter().map(|route| (route.route_name.clone(), route)).collect(),
97        }
98    }
99}
100
101impl<P> MainFileConfigItem<P> {
102    pub fn map_plugins<F, T>(self, mut f: F) -> MainFileConfigItem<T>
103    where
104        F: FnMut(P) -> T,
105    {
106        MainFileConfigItem {
107            gateway: self.gateway.map_plugins(&mut f),
108            routes: self.routes.into_iter().map(|route| route.map_plugins(&mut f)).collect(),
109        }
110    }
111}
112
113#[derive(Default, Debug, Serialize, Deserialize, Clone)]
114#[serde(default)]
115pub struct PluginConfigs {
116    pub named: Vec<FsNamedPluginConfig>,
117    pub mono: Vec<FsMonoPluginConfig>,
118}
119
120impl MainFileConfig<FsAsmPluginConfigMaybeUninitialized> {
121    pub fn initialize_uid(self) -> MainFileConfig<FsAsmPluginConfig> {
122        let mut hasher = DefaultHasher::new();
123        let mut set_uid = move |p: FsAsmPluginConfigMaybeUninitialized| match p {
124            FsAsmPluginConfigMaybeUninitialized::Anon { uid, code, spec } => {
125                let uid = if let Some(uid) = uid {
126                    hasher.write(uid.as_bytes());
127                    uid
128                } else {
129                    hasher.write(code.as_bytes());
130                    hasher.write(spec.to_string().as_bytes());
131                    format!("{:016x}", hasher.finish())
132                };
133                FsAsmPluginConfig::Anon { uid, code, spec }
134            }
135            FsAsmPluginConfigMaybeUninitialized::Named { name, code } => FsAsmPluginConfig::Named { name, code },
136            FsAsmPluginConfigMaybeUninitialized::Mono { code } => FsAsmPluginConfig::Mono { code },
137        };
138        let gateways = self.gateways.into_iter().map(|item| item.map_plugins(&mut set_uid)).collect();
139        MainFileConfig {
140            gateways,
141            plugins: self.plugins,
142            api_port: self.api_port,
143        }
144    }
145}
146
147impl MainFileConfig<FsAsmPluginConfig> {
148    pub fn into_model_config(self) -> spacegate_model::Config {
149        let mut plugins = PluginInstanceMap::default();
150        for named in self.plugins.named {
151            let id = PluginInstanceId {
152                code: named.code.into(),
153                name: spacegate_model::PluginInstanceName::Named { name: named.name },
154            };
155            plugins.insert(id.clone(), named.spec);
156        }
157        for mono in self.plugins.mono {
158            let id = PluginInstanceId {
159                code: mono.code.into(),
160                name: spacegate_model::PluginInstanceName::Mono {},
161            };
162            plugins.insert(id.clone(), mono.spec);
163        }
164        let mut collect_plugin = |p: FsAsmPluginConfig| match p {
165            FsAsmPluginConfig::Anon { uid, code, spec } => {
166                let id = PluginInstanceId {
167                    code: code.into(),
168                    name: spacegate_model::PluginInstanceName::Anon { uid },
169                };
170                plugins.insert(id.clone(), spec);
171                id
172            }
173            FsAsmPluginConfig::Named { name, code } => PluginInstanceId {
174                code: code.into(),
175                name: spacegate_model::PluginInstanceName::Named { name },
176            },
177            FsAsmPluginConfig::Mono { code } => PluginInstanceId {
178                code: code.into(),
179                name: spacegate_model::PluginInstanceName::Mono {},
180            },
181        };
182        let gateways = self
183            .gateways
184            .into_iter()
185            .map(|item| {
186                let config_item: ConfigItem = item.map_plugins(&mut collect_plugin).into();
187                (config_item.gateway.name.clone(), config_item)
188            })
189            .collect();
190        spacegate_model::Config {
191            gateways,
192            plugins,
193            api_port: Some(self.api_port),
194        }
195    }
196}
197
198impl From<spacegate_model::Config> for MainFileConfig<FsAsmPluginConfig> {
199    fn from(value: spacegate_model::Config) -> Self {
200        let mut plugins = PluginConfigs::default();
201        let mut anon_plugins = HashMap::new();
202        for (id, spec) in value.plugins.into_inner() {
203            match id.name {
204                PluginInstanceName::Anon { uid } => {
205                    anon_plugins.insert(uid, spec);
206                }
207                PluginInstanceName::Named { name } => plugins.named.push(FsNamedPluginConfig { name, code: id.code.into(), spec }),
208                PluginInstanceName::Mono {} => plugins.mono.push(FsMonoPluginConfig { code: id.code.into(), spec }),
209            }
210        }
211        let mut map = |id: PluginInstanceId| match id.name {
212            PluginInstanceName::Anon { uid } => {
213                if let Some(spec) = anon_plugins.remove(&uid) {
214                    FsAsmPluginConfig::Anon { uid, code: id.code.into(), spec }
215                } else {
216                    FsAsmPluginConfig::Anon {
217                        uid,
218                        code: id.code.into(),
219                        spec: Default::default(),
220                    }
221                }
222            }
223            PluginInstanceName::Named { name } => FsAsmPluginConfig::Named { name, code: id.code.into() },
224            PluginInstanceName::Mono {} => FsAsmPluginConfig::Mono { code: id.code.into() },
225        };
226        let gateways = value.gateways.into_values().map(|item| <MainFileConfigItem<FsAsmPluginConfig>>::from(item.map_plugins(&mut map))).collect();
227        MainFileConfig {
228            gateways,
229            plugins,
230            api_port: value.api_port.unwrap_or(DEFAULT_API_PORT),
231        }
232    }
233}