use crate::config::Config;
use crate::port::Port;
use crate::services::flex::gcl::ToGcl;
use crate::services::flex::policy::PolicyConfig;
use convert_case::{Case, Casing};
use indoc::formatdoc;
use std::ops::Add;
#[derive(Debug, Clone)]
pub struct ApiConfig {
pub(super) name: String,
upstream: String,
pub(super) port: Port,
path: String,
policies: Vec<PolicyConfig>,
}
impl ApiConfig {
fn new() -> Self {
Self {
name: "ingress-http".to_string(),
upstream: "http://backend:80".to_string(),
port: 8081,
path: "/anything/echo/".to_string(),
policies: vec![],
}
}
pub fn builder() -> ApiConfigBuilder {
ApiConfigBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct ApiConfigBuilder {
config: ApiConfig,
}
impl ApiConfigBuilder {
fn new() -> Self {
Self {
config: ApiConfig::new(),
}
}
pub fn name<T: Into<String>>(self, name: T) -> Self {
Self {
config: ApiConfig {
name: name.into(),
..self.config
},
}
}
pub fn upstream(self, upstream: &dyn Config) -> Self {
Self {
config: ApiConfig {
upstream: format!(
"{}://{}:{}",
upstream.schema(),
upstream.hostname(),
upstream.port()
),
..self.config
},
}
}
pub fn port(self, port: Port) -> Self {
Self {
config: ApiConfig {
port,
..self.config
},
}
}
pub fn path<T: Into<String>>(self, path: T) -> Self {
Self {
config: ApiConfig {
path: path.into(),
..self.config
},
}
}
pub fn policies<T>(self, policies: T) -> Self
where
T: IntoIterator<Item = PolicyConfig>,
{
Self {
config: ApiConfig {
policies: policies.into_iter().collect(),
..self.config
},
}
}
pub fn build(self) -> ApiConfig {
self.config
}
}
const API_POLICY_SECTION_GCL: &str = r#" policies:"#;
impl ToGcl for ApiConfig {
fn to_gcl(&self) -> String {
let name = self.name.to_case(Case::Kebab);
let port = self.port;
let upstream = self.upstream.as_str();
let path = self.path.as_str();
let mut result = formatdoc!(
"
# Copyright (c) 2025, Salesforce, Inc.,
# All rights reserved.
# For full license text, see the LICENSE.txt file
---
apiVersion: gateway.mulesoft.com/v1alpha1
kind: ApiInstance
metadata:
name: {name}
spec:
address: http://0.0.0.0:{port}
services:
upstream:
address: {upstream}
routes:
- config:
destinationPath: {path}
"
);
if !self.policies.is_empty() {
result = result.add(API_POLICY_SECTION_GCL);
}
for policy_config in self.policies.iter() {
result = result.add(&policy_config.to_gcl());
}
result
}
fn name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
mod tests {
use crate::services::flex::gcl::ToGcl;
use crate::services::flex::{ApiConfig, PolicyConfig};
use crate::services::httpmock::HttpMockConfig;
use indoc::formatdoc;
#[test]
fn generate_api_gcl() {
let upstream_config = HttpMockConfig::builder().hostname("mock").port(80).build();
let policy_config = PolicyConfig::builder()
.name("simple-oauth-2-validation-v1-0-impl")
.configuration(serde_json::json!({
"authorization": "whatever",
"oauthService": "http://mock:80/auth",
}))
.build();
let api_config = ApiConfig::builder()
.upstream(&upstream_config)
.policies([policy_config])
.build();
let expected = formatdoc!(
r#"
# Copyright (c) 2025, Salesforce, Inc.,
# All rights reserved.
# For full license text, see the LICENSE.txt file
---
apiVersion: gateway.mulesoft.com/v1alpha1
kind: ApiInstance
metadata:
name: ingress-http
spec:
address: http://0.0.0.0:8081
services:
upstream:
address: http://mock:80
routes:
- config:
destinationPath: /anything/echo/
policies:
- policyRef:
name: simple-oauth-2-validation-v1-0-impl
namespace: default
config: {{"authorization":"whatever","oauthService":"http://mock:80/auth"}}"#
);
assert_eq!(api_config.to_gcl(), expected)
}
}