pdk_test/services/flex/
api.rs

1// Copyright (c) 2025, 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    /// Set the API upstream.
64    pub fn upstream(self, upstream: &dyn Config) -> Self {
65        Self {
66            config: ApiConfig {
67                upstream: format!(
68                    "{}://{}:{}",
69                    upstream.schema(),
70                    upstream.hostname(),
71                    upstream.port()
72                ),
73                ..self.config
74            },
75        }
76    }
77
78    /// Set the API port.
79    pub fn port(self, port: Port) -> Self {
80        Self {
81            config: ApiConfig {
82                port,
83                ..self.config
84            },
85        }
86    }
87
88    /// Set the API path.
89    pub fn path<T: Into<String>>(self, path: T) -> Self {
90        Self {
91            config: ApiConfig {
92                path: path.into(),
93                ..self.config
94            },
95        }
96    }
97
98    /// Set the API policies.
99    pub fn policies<T>(self, policies: T) -> Self
100    where
101        T: IntoIterator<Item = PolicyConfig>,
102    {
103        Self {
104            config: ApiConfig {
105                policies: policies.into_iter().collect(),
106                ..self.config
107            },
108        }
109    }
110
111    /// Builds a new [`ApiConfig`].
112    pub fn build(self) -> ApiConfig {
113        self.config
114    }
115}
116
117const API_POLICY_SECTION_GCL: &str = r#"  policies:"#;
118
119impl ToGcl for ApiConfig {
120    fn to_gcl(&self) -> String {
121        let name = self.name.to_case(Case::Kebab);
122        let port = self.port;
123        let upstream = self.upstream.as_str();
124        let path = self.path.as_str();
125
126        let mut result = formatdoc!(
127            "
128        # Copyright (c) 2025, Salesforce, Inc.,
129        # All rights reserved.
130        # For full license text, see the LICENSE.txt file
131        ---
132        apiVersion: gateway.mulesoft.com/v1alpha1
133        kind: ApiInstance
134        metadata:
135          name: {name}
136        spec:
137          address: http://0.0.0.0:{port}
138          services:
139            upstream:
140              address: {upstream}
141              routes:
142                - config:
143                    destinationPath: {path}
144        "
145        );
146
147        if !self.policies.is_empty() {
148            result = result.add(API_POLICY_SECTION_GCL);
149        }
150
151        for policy_config in self.policies.iter() {
152            result = result.add(&policy_config.to_gcl());
153        }
154
155        result
156    }
157
158    fn name(&self) -> &str {
159        &self.name
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use crate::services::flex::gcl::ToGcl;
166    use crate::services::flex::{ApiConfig, PolicyConfig};
167    use crate::services::httpmock::HttpMockConfig;
168    use indoc::formatdoc;
169
170    #[test]
171    fn generate_api_gcl() {
172        let upstream_config = HttpMockConfig::builder().hostname("mock").port(80).build();
173
174        let policy_config = PolicyConfig::builder()
175            .name("simple-oauth-2-validation-v1-0-impl")
176            .configuration(serde_json::json!({
177                    "authorization": "whatever",
178                    "oauthService": "http://mock:80/auth",
179            }))
180            .build();
181
182        let api_config = ApiConfig::builder()
183            .upstream(&upstream_config)
184            .policies([policy_config])
185            .build();
186
187        let expected = formatdoc!(
188            r#"
189        # Copyright (c) 2025, Salesforce, Inc.,
190        # All rights reserved.
191        # For full license text, see the LICENSE.txt file
192        ---
193        apiVersion: gateway.mulesoft.com/v1alpha1
194        kind: ApiInstance
195        metadata:
196          name: ingress-http
197        spec:
198          address: http://0.0.0.0:8081
199          services:
200            upstream:
201              address: http://mock:80
202              routes:
203                - config:
204                    destinationPath: /anything/echo/
205          policies:
206            - policyRef:
207                name: simple-oauth-2-validation-v1-0-impl
208                namespace: default
209              config: {{"authorization":"whatever","oauthService":"http://mock:80/auth"}}"#
210        );
211
212        assert_eq!(api_config.to_gcl(), expected)
213    }
214}