pdk_test/services/flex/
api.rs1use 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#[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 pub fn builder() -> ApiConfigBuilder {
36 ApiConfigBuilder::new()
37 }
38}
39
40#[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 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 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 pub fn port(self, port: Port) -> Self {
90 Self {
91 config: ApiConfig {
92 port,
93 ..self.config
94 },
95 }
96 }
97
98 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 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 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}