use crate::services::flex::gcl::ToGcl;
use crate::services::flex::PolicyConfig;
use indoc::formatdoc;
#[derive(Debug, Clone)]
pub struct PolicyBindingConfig {
name: String,
target: Target,
policy_config: PolicyConfig,
}
#[derive(Debug, Clone)]
pub struct Target {
name: String,
kind: Option<TargetKind>,
}
impl Target {
fn new() -> Self {
Self {
name: "".to_string(),
kind: None,
}
}
pub fn builder() -> TargetBuilder {
TargetBuilder::new()
}
}
pub struct TargetBuilder {
target: Target,
}
impl TargetBuilder {
pub fn name<T: Into<String>>(self, name: T) -> Self {
Self {
target: Target {
name: name.into(),
..self.target
},
}
}
pub fn kind<T: Into<TargetKind>>(self, kind: T) -> Self {
Self {
target: Target {
kind: Some(kind.into()),
..self.target
},
}
}
pub fn build(self) -> Target {
self.target
}
pub fn new() -> Self {
Self {
target: Target::new(),
}
}
}
#[derive(Debug, Clone)]
pub enum TargetKind {
Service,
ApiInstance,
}
impl PolicyBindingConfig {
fn new() -> Self {
Self {
name: "".to_string(),
target: Target {
name: "".to_string(),
kind: None,
},
policy_config: PolicyConfig::builder().build(),
}
}
pub fn builder() -> PolicyBindingConfigBuilder {
PolicyBindingConfigBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct PolicyBindingConfigBuilder {
config: PolicyBindingConfig,
}
impl PolicyBindingConfigBuilder {
fn new() -> Self {
Self {
config: PolicyBindingConfig::new(),
}
}
pub fn config<T: Into<PolicyConfig>>(self, config: T) -> Self {
Self {
config: PolicyBindingConfig {
policy_config: config.into(),
..self.config
},
}
}
pub fn target<T: Into<Target>>(self, target: T) -> Self {
Self {
config: PolicyBindingConfig {
target: target.into(),
..self.config
},
}
}
pub fn name<T: Into<String>>(self, name: T) -> Self {
Self {
config: PolicyBindingConfig {
name: name.into(),
..self.config
},
}
}
pub fn build(self) -> PolicyBindingConfig {
self.config
}
}
impl ToGcl for PolicyBindingConfig {
fn to_gcl(&self) -> String {
let name = self.name.as_str();
let target_name = &self.target.name.as_str();
let kind = match &self.target.kind {
None => "ApiInstance",
Some(kind) => match kind {
TargetKind::Service => "Service",
TargetKind::ApiInstance => "ApiInstance",
},
};
let ref_name = self.policy_config.name();
let config = self.policy_config.config();
formatdoc!(
r#"
# Copyright (c) 2026, Salesforce, Inc.,
# All rights reserved.
# For full license text, see the LICENSE.txt file
---
apiVersion: gateway.mulesoft.com/v1alpha1
kind: PolicyBinding
metadata:
name: {name}
spec:
targetRef:
kind: {kind}
name: {target_name}
policyRef:
name: {ref_name}
config: {config}"#
)
}
fn name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
mod tests {
use crate::services::flex::gcl::ToGcl as _;
use crate::services::flex::policy_binding::{PolicyBindingConfig, Target, TargetKind};
use crate::services::flex::PolicyConfig;
use indoc::formatdoc;
#[test]
fn generate_policy_biding_to_service() {
let service_config = PolicyBindingConfig::builder()
.name("httpbin-route-oauth2")
.target(Target::builder().name("httpbin-api-oauth2").build())
.config(
PolicyConfig::builder()
.name("route")
.configuration(serde_json::json!({"destinationRef": [{
"name": "httpbin-local-oauth2"
}]}))
.build(),
)
.build();
let expected = formatdoc!(
r#"
# Copyright (c) 2026, Salesforce, Inc.,
# All rights reserved.
# For full license text, see the LICENSE.txt file
---
apiVersion: gateway.mulesoft.com/v1alpha1
kind: PolicyBinding
metadata:
name: httpbin-route-oauth2
spec:
targetRef:
kind: ApiInstance
name: httpbin-api-oauth2
policyRef:
name: route
config: {{"destinationRef":[{{"name":"httpbin-local-oauth2"}}]}}"#
);
assert_eq!(service_config.to_gcl(), expected);
}
#[test]
fn generate_policy_biding_to_upstream() {
let service_config = PolicyBindingConfig::builder()
.name("httpbin-route-oauth2")
.target(
Target::builder()
.kind(TargetKind::Service)
.name("httpbin-local-oauth2")
.build(),
)
.config(
PolicyConfig::builder()
.name("credential-injection-oauth2-flex")
.configuration(serde_json::json!({
"oauthService": "http://oauth-service:9100/token",
"tokenFetchTimeout": 5,
"clientId": "id",
"clientSecret": "secret",
"overwrite": true,
"scope": ["pepe","pia"],
"allowRequestWithoutCredential": false
}))
.build(),
)
.build();
let expected = formatdoc!(
r#"
# Copyright (c) 2026, Salesforce, Inc.,
# All rights reserved.
# For full license text, see the LICENSE.txt file
---
apiVersion: gateway.mulesoft.com/v1alpha1
kind: PolicyBinding
metadata:
name: httpbin-route-oauth2
spec:
targetRef:
kind: Service
name: httpbin-local-oauth2
policyRef:
name: credential-injection-oauth2-flex
config: {{"allowRequestWithoutCredential":false,"clientId":"id","clientSecret":"secret","oauthService":"http://oauth-service:9100/token","overwrite":true,"scope":["pepe","pia"],"tokenFetchTimeout":5}}"#
);
assert_eq!(service_config.to_gcl(), expected);
}
}