Skip to main content

pdk_test/services/flex/
api.rs

1// Copyright (c) 2026, Salesforce, Inc.,
2// All rights reserved.
3// For full license text, see the LICENSE.txt file
4
5use crate::config::Config;
6use crate::port::Port;
7use crate::services::flex::gcl::ToGcl;
8use crate::services::flex::policy::PolicyConfig;
9use convert_case::{Case, Casing};
10use indoc::formatdoc;
11use std::ops::Add;
12
13/// Configuration for a local API service.
14#[derive(Debug, Clone)]
15pub struct ApiConfig {
16    pub(super) name: String,
17    upstream: String,
18    pub(super) port: Port,
19    path: String,
20    policies: Vec<PolicyConfig>,
21}
22
23impl ApiConfig {
24    fn new() -> Self {
25        Self {
26            name: "ingress-http".to_string(),
27            upstream: "http://backend:80".to_string(),
28            port: 8081,
29            path: "/anything/echo/".to_string(),
30            policies: vec![],
31        }
32    }
33
34    /// Creates a builder for initialize a [`ApiConfig`].
35    pub fn builder() -> ApiConfigBuilder {
36        ApiConfigBuilder::new()
37    }
38}
39
40/// A builder for initilizing a [`ApiConfig`].
41#[derive(Debug, Clone)]
42pub struct ApiConfigBuilder {
43    config: ApiConfig,
44}
45
46impl ApiConfigBuilder {
47    fn new() -> Self {
48        Self {
49            config: ApiConfig::new(),
50        }
51    }
52
53    /// Set the API name.
54    pub fn name<T: Into<String>>(self, name: T) -> Self {
55        Self {
56            config: ApiConfig {
57                name: name.into(),
58                ..self.config
59            },
60        }
61    }
62
63    #[cfg(feature = "experimental")]
64    pub fn no_upstream(self) -> Self {
65        Self {
66            config: ApiConfig {
67                upstream: "".to_string(),
68                ..self.config
69            },
70        }
71    }
72
73    /// Set the API upstream.
74    pub fn upstream(self, upstream: &dyn Config) -> Self {
75        Self {
76            config: ApiConfig {
77                upstream: format!(
78                    "{}://{}:{}",
79                    upstream.schema(),
80                    upstream.hostname(),
81                    upstream.port()
82                ),
83                ..self.config
84            },
85        }
86    }
87
88    /// Set the API port.
89    pub fn port(self, port: Port) -> Self {
90        Self {
91            config: ApiConfig {
92                port,
93                ..self.config
94            },
95        }
96    }
97
98    /// Set the API path.
99    pub fn path<T: Into<String>>(self, path: T) -> Self {
100        Self {
101            config: ApiConfig {
102                path: path.into(),
103                ..self.config
104            },
105        }
106    }
107
108    /// Set the API policies.
109    pub fn policies<T>(self, policies: T) -> Self
110    where
111        T: IntoIterator<Item = PolicyConfig>,
112    {
113        Self {
114            config: ApiConfig {
115                policies: policies.into_iter().collect(),
116                ..self.config
117            },
118        }
119    }
120
121    /// Builds a new [`ApiConfig`].
122    pub fn build(self) -> ApiConfig {
123        self.config
124    }
125}
126
127const API_POLICY_SECTION_GCL: &str = r#"  policies:"#;
128
129impl ToGcl for ApiConfig {
130    fn to_gcl(&self) -> String {
131        let name = self.name.to_case(Case::Kebab);
132        let port = self.port;
133        let upstream = self.upstream.as_str();
134        let path = self.path.as_str();
135
136        let mut result = if upstream.is_empty() {
137            formatdoc!(
138                "
139        # Copyright (c) 2026, Salesforce, Inc.,
140        # All rights reserved.
141        # For full license text, see the LICENSE.txt file
142        ---
143        apiVersion: gateway.mulesoft.com/v1alpha1
144        kind: ApiInstance
145        metadata:
146          name: {name}
147        spec:
148          address: http://0.0.0.0:{port}"
149            )
150        } else {
151            formatdoc!(
152                "
153        # Copyright (c) 2026, Salesforce, Inc.,
154        # All rights reserved.
155        # For full license text, see the LICENSE.txt file
156        ---
157        apiVersion: gateway.mulesoft.com/v1alpha1
158        kind: ApiInstance
159        metadata:
160          name: {name}
161        spec:
162          address: http://0.0.0.0:{port}
163          services:
164            upstream:
165              address: {upstream}
166              routes:
167                - config:
168                    destinationPath: {path}
169        "
170            )
171        };
172
173        if !self.policies.is_empty() {
174            result = result.add(API_POLICY_SECTION_GCL);
175        }
176
177        for policy_config in self.policies.iter() {
178            result = result.add(&policy_config.to_gcl());
179        }
180
181        result
182    }
183
184    fn name(&self) -> &str {
185        &self.name
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use crate::services::flex::gcl::ToGcl;
192    use crate::services::flex::{ApiConfig, PolicyConfig};
193    use crate::services::httpmock::HttpMockConfig;
194    use indoc::formatdoc;
195
196    #[test]
197    fn generate_api_gcl() {
198        let upstream_config = HttpMockConfig::builder().hostname("mock").port(80).build();
199
200        let policy_config = PolicyConfig::builder()
201            .name("simple-oauth-2-validation-v1-0-impl")
202            .configuration(serde_json::json!({
203                    "authorization": "whatever",
204                    "oauthService": "http://mock:80/auth",
205            }))
206            .build();
207
208        let api_config = ApiConfig::builder()
209            .upstream(&upstream_config)
210            .policies([policy_config])
211            .build();
212
213        let expected = formatdoc!(
214            r#"
215        # Copyright (c) 2026, Salesforce, Inc.,
216        # All rights reserved.
217        # For full license text, see the LICENSE.txt file
218        ---
219        apiVersion: gateway.mulesoft.com/v1alpha1
220        kind: ApiInstance
221        metadata:
222          name: ingress-http
223        spec:
224          address: http://0.0.0.0:8081
225          services:
226            upstream:
227              address: http://mock:80
228              routes:
229                - config:
230                    destinationPath: /anything/echo/
231          policies:
232            - policyRef:
233                name: simple-oauth-2-validation-v1-0-impl
234                namespace: default
235              config: {{"authorization":"whatever","oauthService":"http://mock:80/auth"}}"#
236        );
237
238        assert_eq!(api_config.to_gcl(), expected)
239    }
240
241    #[test]
242    #[cfg(feature = "experimental")]
243    fn generate_api_gcl_no_inline_service() {
244        let api_config = ApiConfig::builder().no_upstream().build();
245
246        let expected = formatdoc!(
247            r#"
248        # Copyright (c) 2026, Salesforce, Inc.,
249        # All rights reserved.
250        # For full license text, see the LICENSE.txt file
251        ---
252        apiVersion: gateway.mulesoft.com/v1alpha1
253        kind: ApiInstance
254        metadata:
255          name: ingress-http
256        spec:
257          address: http://0.0.0.0:8081"#
258        );
259
260        assert_eq!(api_config.to_gcl(), expected)
261    }
262}