use std::collections::BTreeMap;
use crate::host::property::PropertyAccessor;
use classy::hl::Uri;
use classy::user_agent::UserAgent;
use classy::{hl::Service, user_agent};
use serde::Deserialize;
use url::{Host, Url};
const PLUGIN_NAME: &[&str] = &["plugin_name"];
const DEFAULT_POLICY_ID: &str = "NoPolicyId";
const DEFAULT_POLICY_NAMESPACE: &str = "NoPolicyNamespace";
const DEFAULT_FLEX_NAME: &str = "NoFlexName";
#[cfg(feature = "experimental_filter_name")]
const DEFAULT_FILTER_NAME: &str = "NoPolicyName.NoFlexName.NoApiName";
const FLEX_AGENT: &str = "FlexGateway";
#[derive(Clone, Hash)]
pub struct PolicyMetadata {
flex_name: String,
policy_id: String,
policy_namespace: String,
#[cfg(feature = "experimental_filter_name")]
filter_name: String,
context: ApiContext,
}
#[derive(Clone, Deserialize, Debug, Default, Hash)]
pub struct Api {
#[serde(rename = "id")]
id: String,
#[serde(rename = "name")]
name: String,
#[serde(rename = "legacyApiId")]
legacy_api_id: String,
#[serde(rename = "version")]
version: String,
#[serde(rename = "basePath")]
base_path: Option<String>,
#[serde(rename = "exchange")]
exchange: Option<ExchangeContext>,
}
impl Api {
pub fn new(
id: String,
name: String,
legacy_api_id: String,
version: String,
exchange: Option<ExchangeContext>,
) -> Self {
Api {
id,
name,
legacy_api_id,
version,
base_path: None,
exchange,
}
}
pub fn base_path(&self) -> Option<&str> {
self.base_path.as_deref()
}
pub fn id(&self) -> &str {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn legacy_api_id(&self) -> &str {
&self.legacy_api_id
}
pub fn version(&self) -> &str {
&self.version
}
pub fn exchange(&self) -> Option<&ExchangeContext> {
self.exchange.as_ref()
}
}
#[derive(Clone, Deserialize, Debug, Default, Hash)]
pub struct ApiContext {
#[serde(rename = "policyConfig")]
pub policy_config: Option<PolicyConfig>,
#[serde(rename = "api")]
api: Option<Api>,
#[serde(rename = "tiers")]
tiers: Option<Vec<ApiSla>>,
#[serde(rename = "identityManagement")]
identity_management: Option<IdentityManagementContext>,
#[serde(rename = "environment")]
environment: Option<EnvironmentContext>,
#[serde(rename = "platformPolicyIDs")]
platform_policy_ids: Option<BTreeMap<String, String>>,
}
impl ApiContext {
pub fn new(
policy_config: Option<PolicyConfig>,
api: Option<Api>,
tiers: Option<Vec<ApiSla>>,
identity_management: Option<IdentityManagementContext>,
environment: Option<EnvironmentContext>,
platform_policy_ids: Option<BTreeMap<String, String>>,
) -> Self {
ApiContext {
policy_config,
api,
tiers,
identity_management,
environment,
platform_policy_ids,
}
}
pub fn api(&self) -> Option<&Api> {
self.api.as_ref()
}
pub fn tiers(&self) -> Option<&Vec<ApiSla>> {
self.tiers.as_ref()
}
pub fn identity_management(&self) -> Option<&IdentityManagementContext> {
self.identity_management.as_ref()
}
pub fn environment(&self) -> Option<&EnvironmentContext> {
self.environment.as_ref()
}
pub fn platform_policy_ids(&self) -> Option<&BTreeMap<String, String>> {
self.platform_policy_ids.as_ref()
}
}
#[derive(Deserialize, Debug, Default, Clone, Hash)]
pub struct PolicyConfig {
logging: Option<Logging>,
}
impl PolicyConfig {
pub fn logging(&self) -> Option<&Logging> {
self.logging.as_ref()
}
}
#[derive(Deserialize, Debug, Default, Clone, Hash)]
pub struct Logging {
level: String,
}
impl Logging {
pub fn level(&self) -> &str {
self.level.as_ref()
}
}
#[derive(Deserialize, Debug, Default, Clone, Hash)]
pub struct ApiSla {
#[serde(rename = "id")]
id: String,
#[serde(rename = "name", default)]
name: String,
#[serde(rename = "limits")]
tiers: Vec<Tier>,
}
impl ApiSla {
pub fn new(id: String, name: String, tiers: Vec<Tier>) -> Self {
ApiSla { id, name, tiers }
}
pub fn id(&self) -> &str {
self.id.as_str()
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn tiers(&self) -> &Vec<Tier> {
&self.tiers
}
}
#[derive(Deserialize, Debug, Default, Clone, Hash)]
pub struct Tier {
#[serde(rename = "maximumRequests")]
requests: u64,
#[serde(rename = "timePeriodInMilliseconds")]
period_in_millis: u64,
}
impl Tier {
pub fn new(requests: u64, period_in_millis: u64) -> Self {
Tier {
requests,
period_in_millis,
}
}
pub fn requests(&self) -> u64 {
self.requests
}
pub fn period_in_millis(&self) -> u64 {
self.period_in_millis
}
}
#[derive(Clone, Deserialize, Debug, Default, Hash)]
pub struct IdentityManagementContext {
#[serde(rename = "clientId")]
client_id: String,
#[serde(rename = "clientSecret")]
client_secret: String,
#[serde(rename = "tokenUrl")]
token_url: String,
#[serde(rename = "serviceName")]
service_name: String,
}
impl IdentityManagementContext {
pub fn new(
client_id: String,
client_secret: String,
token_url: String,
service_name: String,
) -> Self {
IdentityManagementContext {
client_id,
client_secret,
token_url,
service_name,
}
}
pub fn client_id(&self) -> &str {
&self.client_id
}
pub fn client_secret(&self) -> &str {
&self.client_secret
}
pub fn token_url(&self) -> &str {
&self.token_url
}
pub fn service_name(&self) -> &str {
&self.service_name
}
}
#[derive(Deserialize, Debug, Default, Clone, Hash)]
pub struct EnvironmentContext {
#[serde(rename = "organizationId")]
organization_id: String,
#[serde(rename = "environmentId")]
environment_id: String,
#[serde(rename = "masterOrganizationId")]
root_organization_id: String,
#[serde(rename = "clusterId")]
cluster_id: String,
#[serde(rename = "anypoint")]
anypoint: Option<AnypointContext>,
#[serde(rename = "flexVersion")]
flex_version: Option<String>,
}
impl EnvironmentContext {
pub fn new(
organization_id: String,
environment_id: String,
root_organization_id: String,
cluster_id: String,
anypoint: Option<AnypointContext>,
flex_version: Option<String>,
) -> Self {
EnvironmentContext {
organization_id,
environment_id,
root_organization_id,
cluster_id,
anypoint,
flex_version,
}
}
pub fn organization_id(&self) -> &str {
&self.organization_id
}
pub fn environment_id(&self) -> &str {
&self.environment_id
}
pub fn master_organization_id(&self) -> &str {
&self.root_organization_id
}
pub fn cluster_id(&self) -> &str {
&self.cluster_id
}
pub fn anypoint(&self) -> Option<&AnypointContext> {
self.anypoint
.as_ref()
.filter(|&a| a.service_name.is_some() && a.url.is_some())
}
pub fn flex_version(&self) -> Option<&str> {
self.flex_version.as_deref()
}
}
#[derive(Deserialize, Debug, Default, Clone, Hash)]
pub struct AnypointContext {
#[serde(rename = "clientId")]
client_id: String,
#[serde(rename = "clientSecret")]
client_secret: String,
#[serde(rename = "serviceName")]
service_name: Option<String>,
#[serde(rename = "url")]
url: Option<String>,
}
impl AnypointContext {
pub fn new(
client_id: String,
client_secret: String,
service_name: String,
url: String,
) -> Self {
AnypointContext {
client_id,
client_secret,
service_name: Some(service_name),
url: Some(url),
}
}
pub fn client_id(&self) -> &str {
&self.client_id
}
pub fn client_secret(&self) -> &str {
&self.client_secret
}
pub fn service_name(&self) -> &str {
self.service_name.as_deref().unwrap_or("UNDEFINED")
}
fn _url(&self) -> &str {
self.url.as_deref().unwrap_or("UNDEFINED")
}
#[cfg(not(feature = "ll"))]
fn url(&self) -> &str {
self._url()
}
#[cfg(feature = "ll")]
pub fn url(&self) -> &str {
self._url()
}
pub fn base_path(&self) -> String {
Url::parse(self.url())
.map(|url| url.path().to_string())
.unwrap_or_else(|_| "/".to_string())
}
pub fn authority(&self) -> String {
Url::parse(self.url())
.ok()
.and_then(Self::get_host)
.unwrap_or_else(|| "anypoint.com".to_string())
}
fn get_host(url: Url) -> Option<String> {
let host = url.host()?;
match host {
Host::Domain(host) => Some(host.to_string()),
_ => None,
}
}
}
#[derive(Deserialize, Debug, Default, Clone, Hash)]
pub struct ExchangeContext {
#[serde(rename = "serviceName")]
service_name: String,
#[serde(rename = "schemaUrl")]
schema_url: String,
}
impl ExchangeContext {
pub fn new(service_name: String, schema_url: String) -> Self {
ExchangeContext {
service_name,
schema_url,
}
}
pub fn service(&self) -> Service {
Service::new(self.service_name(), self.schema_uri())
}
pub fn service_name(&self) -> &str {
&self.service_name
}
pub fn schema_uri(&self) -> Uri {
self.schema_url.parse().expect("schema url must be valid")
}
}
fn read_string(property_accessor: &dyn PropertyAccessor, coordinate: &[&str]) -> Option<String> {
property_accessor
.read_property(coordinate)
.and_then(|bytes| {
std::str::from_utf8(bytes.as_slice())
.ok()
.map(str::to_string)
})
}
pub fn read_api_name_from_plugin_name(property_accessor: &dyn PropertyAccessor) -> String {
read_string(property_accessor, PLUGIN_NAME)
.map(|name| PolicyMetadata::split_plugin_name(&name).unwrap_or_default())
.map(|(api_id, _, _)| api_id)
.unwrap_or_default()
}
impl PolicyMetadata {
#[allow(rustdoc::invalid_html_tags)]
pub fn split_plugin_name(plugin_name: &str) -> Result<(String, String, String), String> {
log::debug!("Plugin name: {plugin_name}");
let parts: Vec<&str> = plugin_name.split('.').collect();
if parts.len() >= 3 {
let mut iter = parts.iter();
let policy_id = iter.next().unwrap().to_string();
let policy_namespace = iter.next().unwrap().to_string();
let mut api_id = iter.next().unwrap().to_string();
let mut aux = iter.next();
while aux.is_some() {
api_id = format!("{}.{}", api_id, aux.unwrap());
aux = iter.next();
}
Ok((api_id, policy_namespace, policy_id))
} else {
Err(format!(
"Plugin name '{plugin_name:?}' did not match the expected format"
))
}
}
pub fn from(property_accessor: &dyn PropertyAccessor) -> Self {
let flex_name = read_string(property_accessor, &["node", "id"]).unwrap_or_else(|| {
log::debug!("did not find nodeId");
DEFAULT_FLEX_NAME.to_string()
});
read_string(property_accessor, PLUGIN_NAME)
.map(
|name: String| match Self::split_plugin_name(name.as_str()) {
Ok((api_id, policy_namespace, policy_id)) => {
let api_context = ApiContext::from(property_accessor, api_id.as_ref());
Self::new(
flex_name,
policy_id,
policy_namespace,
#[cfg(feature = "experimental_filter_name")]
name,
api_context,
)
}
Err(message) => {
log::warn!("{message} (ErrorCode: FLTR-201).");
#[cfg(feature = "experimental_filter_name")]
return Self::new(
flex_name,
name.clone(),
DEFAULT_POLICY_NAMESPACE.to_string(),
name,
ApiContext::default(),
);
#[cfg(not(feature = "experimental_filter_name"))]
return Self::new(
flex_name,
name.clone(),
DEFAULT_POLICY_NAMESPACE.to_string(),
ApiContext::default(),
);
}
},
)
.unwrap_or_default()
}
pub fn new(
flex_name: String,
policy_id: String,
policy_namespace: String,
#[cfg(feature = "experimental_filter_name")] filter_name: String,
context: ApiContext,
) -> Self {
Self {
flex_name,
policy_id,
policy_namespace,
#[cfg(feature = "experimental_filter_name")]
filter_name,
context,
}
}
pub fn policy_id(&self) -> &str {
self.policy_id.as_ref()
}
pub fn policy_namespace(&self) -> &str {
self.policy_namespace.as_ref()
}
#[cfg(feature = "experimental_filter_name")]
pub fn filter_name(&self) -> &str {
self.filter_name.as_ref()
}
pub fn policy_name(&self) -> &str {
self.policy_id.as_ref()
}
pub fn api_info(&self) -> Option<&Api> {
self.context.api.as_ref()
}
pub fn anypoint_environment(&self) -> Option<&EnvironmentContext> {
self.context.environment.as_ref()
}
pub fn api_tiers(&self) -> Option<&Vec<ApiSla>> {
self.context.tiers.as_ref()
}
pub fn flex_name(&self) -> &str {
self.flex_name.as_str()
}
pub fn identity_management_context(&self) -> Option<&IdentityManagementContext> {
self.context.identity_management.as_ref()
}
pub fn policy_config(&self) -> Option<&PolicyConfig> {
self.context.policy_config.as_ref()
}
pub fn platform_policy_ids(&self) -> Option<&BTreeMap<String, String>> {
self.context.platform_policy_ids.as_ref()
}
}
impl Default for PolicyMetadata {
fn default() -> Self {
Self {
flex_name: DEFAULT_FLEX_NAME.to_string(),
policy_id: DEFAULT_POLICY_ID.to_string(),
policy_namespace: DEFAULT_POLICY_NAMESPACE.to_string(),
#[cfg(feature = "experimental_filter_name")]
filter_name: DEFAULT_FILTER_NAME.to_string(),
context: ApiContext::default(),
}
}
}
impl ApiContext {
pub fn from(property_accessor: &dyn PropertyAccessor, api_instance_name: &str) -> Self {
match read_string(
property_accessor,
&[
"listener_metadata",
"filter_metadata",
api_instance_name,
"context",
],
) {
None => {
log::debug!("Api context info for '{api_instance_name:?}' was not present.");
ApiContext::default()
}
Some(value) => {
log::debug!("Api context for {api_instance_name} successfully parsed.");
match serde_json::from_str(value.as_str()) {
Ok(context) => context,
Err(_cause) => {
log::warn!("Could not parse context info: incomplete/malformed incoming data from platform (ErrorCode: FLTR-201).");
ApiContext::default()
}
}
}
}
}
}
impl From<&PolicyMetadata> for UserAgent {
fn from(metadata: &PolicyMetadata) -> Self {
metadata
.anypoint_environment()
.and_then(|env| env.flex_version())
.map(|version| UserAgent::new(FLEX_AGENT, version))
.unwrap_or_default()
}
}
pub fn configure_user_agent() {
let metadata = PolicyMetadata::from(<dyn PropertyAccessor>::default());
user_agent::set_user_agent(UserAgent::from(&metadata));
}
#[cfg(test)]
mod tests {
use classy::proxy_wasm::types::Bytes;
use classy::user_agent::UserAgent;
use mockall::mock;
use crate::policy_context::metadata::{
ApiContext, EnvironmentContext, PolicyMetadata, DEFAULT_POLICY_ID, DEFAULT_POLICY_NAMESPACE,
};
mock! {
pub PropertyAccessor {}
impl crate::host::property::PropertyAccessor for PropertyAccessor{
fn read_property<'a>(&self, path: &[&'a str]) -> Option<Bytes>;
fn set_property<'a>(&self, path: &[&'a str], value: &[u8]);
}
}
#[test]
pub fn full_api_info() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
expect_stream_metadata(&mut property_accessor);
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(metadata.flex_name(), "my-flex-name");
assert_eq!(
metadata.policy_config().unwrap().logging().unwrap().level,
"error"
);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_some());
assert!(metadata.identity_management_context().is_some());
assert!(metadata.api_info().is_some());
let api_info = metadata.api_info().unwrap();
assert_eq!(api_info.id(), "anypoint_id");
assert_eq!(api_info.name(), "anypoint_name");
assert_eq!(api_info.legacy_api_id(), "anypoint_legacy_api_id");
assert_eq!(api_info.base_path(), Some("/path"));
assert_eq!(api_info.version(), "anypoint_version");
assert!(api_info.exchange().is_some());
let tiers = metadata.api_tiers().unwrap();
assert_eq!(tiers.len(), 1);
assert_eq!(tiers.first().unwrap().id(), "123");
assert_eq!(tiers.first().unwrap().name(), "tier-123");
assert_eq!(
tiers.first().unwrap().tiers().first().unwrap().requests,
100
);
assert_eq!(
tiers
.first()
.unwrap()
.tiers()
.first()
.unwrap()
.period_in_millis,
10
);
assert_eq!(tiers.first().unwrap().tiers().get(1).unwrap().requests, 1);
assert_eq!(
tiers
.first()
.unwrap()
.tiers()
.get(1)
.unwrap()
.period_in_millis,
5
);
let identity = metadata.identity_management_context().unwrap();
assert_eq!(identity.client_id(), "myClientId");
assert_eq!(identity.client_secret(), "myClientSecret");
assert_eq!(identity.token_url(), "myTokenUrl");
assert_eq!(identity.service_name(), "myServiceName");
let environment = metadata.anypoint_environment().unwrap();
assert_eq!(environment.organization_id(), "some_org_id");
assert_eq!(environment.environment_id(), "some_env_id");
assert_eq!(environment.master_organization_id(), "some_root_org_id");
assert_eq!(environment.cluster_id(), "some_cluster_id");
assert!(environment.anypoint().is_some());
let anypoint = environment.anypoint().unwrap();
assert_eq!(anypoint.client_id(), "some_client_id");
assert_eq!(anypoint.client_secret(), "some_secret");
assert_eq!(anypoint.service_name(), "some_service_name");
assert_eq!(anypoint.base_path(), "/path");
assert_eq!(anypoint.authority(), "qax.anypoint.mulesoft.com");
let exchange_info = api_info.exchange().unwrap();
assert_eq!(exchange_info.service_name(), "some_service_name");
let schema_uri = exchange_info.schema_uri();
assert_eq!(schema_uri.scheme(), "https");
assert_eq!(schema_uri.authority(), "some_url");
assert_eq!(
schema_uri.path(),
"/99c799a5-6853-4b2a-94ad-a4cb47f6e9cc?param1=value1¶m2=value2"
);
let policy_ids = metadata.platform_policy_ids().unwrap();
assert_eq!(policy_ids["first_policy"], "first_policy_platform_id");
assert_eq!(policy_ids["second_policy"], "second_policy_platform_id");
}
fn expect_stream_metadata(property_accessor: &mut MockPropertyAccessor) {
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| Some(FULL_API_INFO.as_bytes().to_vec()));
}
fn expect_stream_plugin_name(property_accessor: &mut MockPropertyAccessor) {
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| *arg == ["plugin_name"])
.times(1)
.returning(|_| Some("123-123.rate-limit.456456".as_bytes().to_vec()));
}
fn expect_stream_node_id(property_accessor: &mut MockPropertyAccessor) {
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| *arg == ["node", "id"])
.times(1)
.returning(|_| Some("my-flex-name".as_bytes().to_vec()));
}
#[test]
pub fn no_context_info() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.return_const(None);
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.policy_config().is_none());
assert!(metadata.api_tiers().is_none());
assert!(metadata.identity_management_context().is_none());
assert!(metadata.anypoint_environment().is_none());
}
#[test]
pub fn no_tiers() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"policyConfig": {
"logging": {
"level": "warn"
}
},
"api": {
"id": "anypoint_id",
"name": "anypoint_name",
"legacyApiId": "anypoint_legacy_api_id",
"version": "anypoint_version"
},
"identityManagement": {
"clientId": "myClientId",
"clientSecret": "myClientSecret",
"tokenUrl": "myTokenUrl",
"serviceName": "myServiceName"
},
"environment": {
"organizationId": "some_org_id",
"environmentId": "some_env_id",
"masterOrganizationId": "some_root_org_id",
"clusterId": "some_cluster_id",
"anypoint" : {
"clientId": "some_client_id",
"clientSecret": "some_secret",
"serviceName": "some_service_name",
"url": "https://qax.anypoint.mulesoft.com/path"
}
}
}"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(
metadata.policy_config().unwrap().logging().unwrap().level,
"warn"
);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_none());
assert!(metadata.identity_management_context().is_some());
assert!(metadata.anypoint_environment().is_some());
assert!(metadata
.anypoint_environment()
.unwrap()
.anypoint()
.is_some());
assert!(metadata.api_info().is_some());
}
#[test]
pub fn no_identity() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"policyConfig": {
"logging": {
"level": "error"
}
},
"api": {
"id": "anypoint_id",
"name": "anypoint_name",
"legacyApiId": "anypoint_legacy_api_id",
"version": "anypoint_version",
"basePath": "/path"
},
"tiers": [{
"id": "123",
"name": "tier-123",
"limits": [
{
"timePeriodInMilliseconds": 10,
"maximumRequests": 100
},
{
"timePeriodInMilliseconds": 5,
"maximumRequests": 1
}
]
}],
"environment": {
"organizationId": "some_org_id",
"environmentId": "some_env_id",
"masterOrganizationId": "some_root_org_id",
"clusterId": "some_cluster_id",
"anypoint": {
"clientId": "some_client_id",
"clientSecret": "some_secret",
"serviceName": "some_service_name",
"url": "https://qax.anypoint.mulesoft.com/path"
}
}
}"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(
metadata.policy_config().unwrap().logging().unwrap().level,
"error"
);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_some());
assert!(metadata.identity_management_context().is_none());
assert!(metadata.api_info().is_some());
assert!(metadata.anypoint_environment().is_some());
assert!(metadata
.anypoint_environment()
.unwrap()
.anypoint()
.is_some());
}
#[test]
pub fn no_api_info() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"policyConfig": {
"logging": {
"level": "error"
}
},
"tiers": [{
"id": "123",
"name": "tier-123",
"limits": [
{
"timePeriodInMilliseconds": 10,
"maximumRequests": 100
},
{
"timePeriodInMilliseconds": 5,
"maximumRequests": 1
}
]
}],
"identityManagement": {
"clientId": "myClientId",
"clientSecret": "myClientSecret",
"tokenUrl": "myTokenUrl",
"serviceName": "myServiceName"
},
"environment": {
"organizationId": "some_org_id",
"environmentId": "some_env_id",
"masterOrganizationId": "some_root_org_id",
"clusterId": "some_cluster_id",
"anypoint" : {
"clientId": "some_client_id",
"clientSecret": "some_secret",
"serviceName": "some_service_name",
"url": "https://qax.anypoint.mulesoft.com/path"
}
}
}
"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(
metadata.policy_config().unwrap().logging().unwrap().level,
"error"
);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_some());
assert!(metadata.identity_management_context().is_some());
assert!(metadata.anypoint_environment().is_some());
assert!(metadata
.anypoint_environment()
.unwrap()
.anypoint()
.is_some());
assert!(metadata.api_info().is_none());
}
#[test]
pub fn no_anypoint_environment() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"policyConfig": {
"logging": {
"level": "error"
}
},
"api": {
"id": "anypoint_id",
"name": "anypoint_name",
"legacyApiId": "anypoint_legacy_api_id",
"version": "anypoint_version",
"basePath": "/path"
},
"tiers": [{
"id": "123",
"name": "tier-123",
"limits": [
{
"timePeriodInMilliseconds": 10,
"maximumRequests": 100
},
{
"timePeriodInMilliseconds": 5,
"maximumRequests": 1
}
]
}],
"identityManagement": {
"clientId": "myClientId",
"clientSecret": "myClientSecret",
"tokenUrl": "myTokenUrl",
"serviceName": "myServiceName"
}
}
"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(
metadata.policy_config().unwrap().logging().unwrap().level,
"error"
);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_some());
assert!(metadata.identity_management_context().is_some());
assert!(metadata.anypoint_environment().is_none());
assert!(metadata.api_info().is_some());
}
#[test]
pub fn no_policy_ids() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"api": {
"id": "anypoint_id",
"name": "anypoint_name",
"legacyApiId": "anypoint_legacy_api_id",
"version": "anypoint_version"
},
"tiers": [{
"id": "123",
"name": "tier-123",
"limits": [
{
"timePeriodInMilliseconds": 10,
"maximumRequests": 100
},
{
"timePeriodInMilliseconds": 5,
"maximumRequests": 1
}
]
}],
"identityManagement": {
"clientId": "myClientId",
"clientSecret": "myClientSecret",
"tokenUrl": "myTokenUrl",
"serviceName": "myServiceName"
},
"environment": {
"organizationId": "some_org_id",
"environmentId": "some_env_id",
"masterOrganizationId": "some_root_org_id",
"clusterId": "some_cluster_id",
"anypoint" : {
"clientId": "some_client_id",
"clientSecret": "some_secret",
"serviceName": "some_service_name",
"url": "https://qax.anypoint.mulesoft.com/path"
}
}
}
"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_some());
assert!(metadata.identity_management_context().is_some());
assert!(metadata.anypoint_environment().is_some());
assert!(metadata.api_info().is_some());
assert!(metadata.platform_policy_ids().is_none());
}
#[test]
fn policy_binding_identity() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| *arg == ["plugin_name"])
.times(1)
.returning(|_| {
Some(
"some_binding_name.some_binding_namespace.456456"
.as_bytes()
.to_vec(),
)
});
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| None);
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(metadata.policy_id(), "some_binding_name");
assert_eq!(metadata.policy_namespace(), "some_binding_namespace");
}
#[test]
pub fn no_plugin_name() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| *arg == ["plugin_name"])
.times(1)
.return_const(None);
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(metadata.policy_id(), DEFAULT_POLICY_ID);
assert_eq!(metadata.policy_namespace(), DEFAULT_POLICY_NAMESPACE);
assert!(metadata.api_tiers().is_none());
assert!(metadata.identity_management_context().is_none());
}
#[test]
pub fn not_matching_plugin_name() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| *arg == ["plugin_name"])
.times(1)
.returning(|_| Some("123-123.456456".as_bytes().to_vec()));
let metadata = PolicyMetadata::from(&property_accessor);
assert!(metadata.policy_config().is_none());
assert_eq!(metadata.policy_id(), "123-123.456456");
assert!(metadata.api_tiers().is_none());
assert!(metadata.identity_management_context().is_none());
}
#[test]
pub fn unexpected_json() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"policyConfig": {
"logging": {
"level": "error"
}
},
"tiers": [{
"id": 123,
"name": "tier-123",
"limits": [
{
"timePeriodInMilliseconds": 10,
"maximumRequests": 100
},
{
"timePeriodInMilliseconds": 5,
"maximumRequests": 1
}
]
}],
"identityManagement": {
"clientId": "myClientId",
"clientSecret": "myClientSecret",
"tokenUrl": "myTokenUrl",
"serviceName": "myServiceName"
},
"clientIdEnforcement": {
"clientId": "myClientId",
"clientSecret": "myClientSecret",
"serviceName": "myServiceName",
"orgId": "myOrgId",
"envId": "myEnvId"
}
}"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert!(metadata.policy_config().is_none());
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_none());
assert!(metadata.identity_management_context().is_none());
}
#[test]
pub fn only_api_info() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"policyConfig": {
"logging": {
"level": "error"
}
},
"api": {
"id": "anypoint_id",
"name": "anypoint_name",
"legacyApiId": "anypoint_legacy_api_id",
"version": "anypoint_version",
"basePath": "/path",
"exchange": {
"schemaUrl": "https://exchange2-asset-manager-kstg.s3.amazonaws.com/99c799a5-6853-4b2a-94ad-a4cb47f6e9cc",
"serviceName": "some_service_name"
}
}
}
"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(
metadata.policy_config().unwrap().logging().unwrap().level,
"error"
);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_none());
assert!(metadata.identity_management_context().is_none());
assert!(metadata.api_info().is_some());
let api_info = metadata.api_info().unwrap();
assert_eq!(api_info.id(), "anypoint_id");
assert_eq!(api_info.name(), "anypoint_name");
assert_eq!(api_info.legacy_api_id(), "anypoint_legacy_api_id");
assert_eq!(api_info.base_path(), Some("/path"));
assert_eq!(api_info.version(), "anypoint_version");
assert!(api_info.exchange().is_some());
let exchange_info = api_info.exchange().unwrap();
assert_eq!(exchange_info.service_name(), "some_service_name");
let schema_uri = exchange_info.schema_uri();
assert_eq!(schema_uri.scheme(), "https");
assert_eq!(
schema_uri.authority(),
"exchange2-asset-manager-kstg.s3.amazonaws.com"
);
assert_eq!(schema_uri.path(), "/99c799a5-6853-4b2a-94ad-a4cb47f6e9cc");
}
#[test]
pub fn only_environment_with_anypoint() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"policyConfig": {
"logging": {
"level": "error"
}
},
"environment": {
"organizationId": "some_org_id",
"environmentId": "some_env_id",
"masterOrganizationId": "some_root_org_id",
"clusterId": "some_cluster_id",
"anypoint": {
"clientId": "some_client_id",
"clientSecret": "some_secret",
"serviceName": "some_service_name",
"url": "https://qax.anypoint.mulesoft.com/path"
}
}
}
"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(
metadata.policy_config().unwrap().logging().unwrap().level,
"error"
);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_none());
assert!(metadata.identity_management_context().is_none());
assert!(metadata.api_info().is_none());
assert!(metadata.anypoint_environment().is_some());
let environment = metadata.anypoint_environment().unwrap();
assert_eq!(environment.organization_id(), "some_org_id");
assert_eq!(environment.environment_id(), "some_env_id");
assert_eq!(environment.master_organization_id(), "some_root_org_id");
assert_eq!(environment.cluster_id(), "some_cluster_id");
assert!(environment.anypoint().is_some());
let anypoint = environment.anypoint().unwrap();
assert_eq!(anypoint.client_id(), "some_client_id");
assert_eq!(anypoint.client_secret(), "some_secret");
assert_eq!(anypoint.service_name(), "some_service_name");
assert_eq!(anypoint.base_path(), "/path");
assert_eq!(anypoint.authority(), "qax.anypoint.mulesoft.com");
}
#[test]
pub fn only_environment_without_anypoint() {
let mut property_accessor = MockPropertyAccessor::new();
expect_stream_node_id(&mut property_accessor);
expect_stream_plugin_name(&mut property_accessor);
property_accessor
.expect_read_property()
.withf(|arg: &[&str]| {
*arg == ["listener_metadata", "filter_metadata", "456456", "context"]
})
.times(1)
.returning(|_| {
Some(
r#"
{
"environment": {
"organizationId": "some_org_id",
"environmentId": "some_env_id",
"masterOrganizationId": "some_root_org_id",
"clusterId": "some_cluster_id"
}
}
"#
.as_bytes()
.to_vec(),
)
});
let metadata = PolicyMetadata::from(&property_accessor);
assert_eq!(metadata.policy_id(), "123-123");
assert!(metadata.api_tiers().is_none());
assert!(metadata.identity_management_context().is_none());
assert!(metadata.api_info().is_none());
assert!(metadata.anypoint_environment().is_some());
let environment = metadata.anypoint_environment().unwrap();
assert_eq!(environment.organization_id(), "some_org_id");
assert_eq!(environment.environment_id(), "some_env_id");
assert_eq!(environment.master_organization_id(), "some_root_org_id");
assert_eq!(environment.cluster_id(), "some_cluster_id");
assert!(environment.anypoint().is_none());
}
#[test]
fn from_metadata() {
assert_eq!(
UserAgent::from(&metadata_with_flex_version(Some(
"some_flex_version".to_string()
)))
.value(),
"FlexGateway/some_flex_version"
);
}
#[test]
fn legacy_metadata() {
assert_eq!(
UserAgent::from(&metadata_with_flex_version(None)).value(),
&format!("PDK-HttpClient/{}", env!("CARGO_PKG_VERSION"))
);
}
fn metadata_with_flex_version(flex_version: Option<String>) -> PolicyMetadata {
let environment = EnvironmentContext::new(
"org_id".to_string(),
"env_id".to_string(),
"root_org_id".to_string(),
"cluster_id".to_string(),
None,
flex_version,
);
let context = ApiContext::new(None, None, None, None, Some(environment), None);
PolicyMetadata::new(
"".to_string(),
"".to_string(),
"".to_string(),
#[cfg(feature = "experimental_filter_name")]
"".to_string(),
context,
)
}
const FULL_API_INFO: &str = r#"
{
"policyConfig": {
"logging": {
"level": "error"
}
},
"api": {
"id": "anypoint_id",
"name": "anypoint_name",
"legacyApiId": "anypoint_legacy_api_id",
"version": "anypoint_version",
"basePath": "/path",
"exchange": {
"schemaUrl": "https://some_url/99c799a5-6853-4b2a-94ad-a4cb47f6e9cc?param1=value1¶m2=value2",
"serviceName": "some_service_name"
}
},
"tiers": [{
"id": "123",
"name" : "tier-123",
"limits": [
{
"timePeriodInMilliseconds": 10,
"maximumRequests": 100
},
{
"timePeriodInMilliseconds": 5,
"maximumRequests": 1
}
]
}],
"identityManagement": {
"clientId": "myClientId",
"clientSecret": "myClientSecret",
"tokenUrl": "myTokenUrl",
"serviceName": "myServiceName"
},
"environment": {
"organizationId": "some_org_id",
"environmentId": "some_env_id",
"masterOrganizationId": "some_root_org_id",
"clusterId": "some_cluster_id",
"anypoint" : {
"clientId": "some_client_id",
"clientSecret": "some_secret",
"serviceName": "some_service_name",
"url": "https://qax.anypoint.mulesoft.com/path"
}
},
"platformPolicyIDs": {
"first_policy": "first_policy_platform_id",
"second_policy": "second_policy_platform_id"
}
}"#;
}