use crate::implementation::platform::shared::Contract;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct LoginResponse {
#[serde(skip_serializing_if = "Option::is_none")]
access_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
token_type: Option<String>,
}
impl LoginResponse {
pub fn get_token(&self) -> &str {
self.access_token.as_deref().unwrap_or_default()
}
pub fn get_type(&self) -> &str {
self.token_type.as_deref().unwrap_or_default()
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ContractsLinksResponse {
#[serde(rename = "self")]
self_link: String,
next: String,
}
#[allow(unused)]
#[derive(Deserialize, Debug)]
pub struct ContractsDataResponse {
#[serde(rename = "organizationId")]
organization_id: Option<String>,
#[serde(rename = "contractId")]
pub contract_id: String,
#[serde(rename = "apiId")]
pub api_id: String,
#[serde(rename = "versionId")]
pub version_id: String,
#[serde(rename = "slaTierId")]
pub sla_tier_id: Option<String>,
#[serde(rename = "clientId")]
pub client_id: String,
#[serde(rename = "clientSecret")]
pub client_secret: Option<String>,
#[serde(rename = "clientSecretSalt")]
pub client_secret_salt: Option<String>,
#[serde(rename = "contractUpdatedDate")]
contract_updated_date: Option<String>,
#[serde(rename = "redirectUris")]
redirect_uris: Option<Vec<String>>,
#[serde(rename = "clientName")]
pub client_name: Option<String>,
#[serde(rename = "clientDescription")]
client_description: Option<String>,
#[serde(rename = "clientUpdatedDate")]
client_updated_date: Option<String>,
#[serde(rename = "updatedDate")]
updated_date: Option<String>,
pub removed: Option<bool>,
}
#[derive(Deserialize, Debug)]
pub struct ContractsResponse {
links: ContractsLinksResponse,
data: Vec<ContractsDataResponse>,
}
impl ContractsResponse {
pub fn verify_contracts(&self) -> Result<(), Vec<String>> {
let errors: Vec<String> = self
.data
.iter()
.filter_map(|contract| {
if contract.removed.unwrap_or_default() {
None
} else {
let mut missing_fields = vec![];
if contract.client_name.is_none() {
missing_fields.push("client_name");
}
if contract.client_secret.is_some() && contract.client_secret_salt.is_none() {
missing_fields.push("client_secret_salt");
}
if contract.client_secret.is_none() && contract.client_secret_salt.is_some() {
missing_fields.push("client_secret");
}
if missing_fields.is_empty() {
None
} else {
let fields = missing_fields.join(", ");
Some(format!(
"Non removed contract {} for api {} is missing: {fields}",
contract.contract_id, contract.api_id
))
}
}
})
.collect();
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
impl ContractsResponse {
pub fn get_links(&self) -> &ContractsLinksResponse {
&self.links
}
pub fn get_data(&self) -> &Vec<ContractsDataResponse> {
&self.data
}
}
impl ContractsLinksResponse {
pub fn self_link(&self) -> &str {
&self.self_link
}
pub fn next_link(&self) -> &str {
&self.next
}
}
pub fn contract_from_event(data: &ContractsDataResponse) -> Contract {
Contract {
contract_id: data.contract_id.clone(),
api_id: data.api_id.clone(),
version_id: data.version_id.clone(),
sla_tier_id: data.sla_tier_id.clone(),
client_id: data.client_id.clone(),
client_secret: data
.client_secret
.clone()
.unwrap_or_else(default_field_value),
client_secret_salt: data
.client_secret_salt
.clone()
.unwrap_or_else(default_field_value),
client_name: data.client_name.clone().unwrap_or_else(default_field_value),
removed: data.removed.unwrap_or(false),
}
}
fn default_field_value() -> String {
"None".to_string()
}
#[cfg(test)]
mod tests {
use crate::implementation::platform::responses::{
ContractsDataResponse, ContractsLinksResponse, ContractsResponse,
};
#[test]
fn with_no_contracts_then_no_errors_are_generated() {
let contracts_response = ContractsResponse {
links: ContractsLinksResponse {
self_link: "".to_string(),
next: "".to_string(),
},
data: vec![],
};
assert_eq!(Ok(()), contracts_response.verify_contracts())
}
#[test]
fn with_valid_contracts_then_no_errors_are_generated() {
let contracts_response = ContractsResponse {
links: ContractsLinksResponse {
self_link: "".to_string(),
next: "".to_string(),
},
data: vec![invalid_removed_contract(), valid_active_contract()],
};
assert_eq!(Ok(()), contracts_response.verify_contracts())
}
#[test]
fn with_invalid_contract_errors_are_generated() {
let contracts_response = ContractsResponse {
links: ContractsLinksResponse {
self_link: "".to_string(),
next: "".to_string(),
},
data: vec![
invalid_removed_contract(),
valid_active_contract(),
invalid_active_contract(),
],
};
let errors = contracts_response.verify_contracts().err().unwrap();
assert_eq!(1, errors.len());
assert_eq!(
"Non removed contract contract id for api api id is missing: client_name",
errors.first().unwrap()
);
}
#[test]
fn with_non_removed_contract_without_client_secret_then_no_errors() {
let contracts_response = ContractsResponse {
links: ContractsLinksResponse {
self_link: "".to_string(),
next: "".to_string(),
},
data: vec![active_contract_without_secret()],
};
assert_eq!(Ok(()), contracts_response.verify_contracts());
}
#[test]
fn with_client_secret_but_no_salt_then_error() {
let contracts_response = ContractsResponse {
links: ContractsLinksResponse {
self_link: "".to_string(),
next: "".to_string(),
},
data: vec![contract_with_secret_no_salt()],
};
let errors = contracts_response.verify_contracts().err().unwrap();
assert_eq!(1, errors.len());
assert!(errors.first().unwrap().contains("client_secret_salt"));
}
#[test]
fn with_client_secret_salt_but_no_secret_then_error() {
let contracts_response = ContractsResponse {
links: ContractsLinksResponse {
self_link: "".to_string(),
next: "".to_string(),
},
data: vec![contract_with_salt_no_secret()],
};
let errors = contracts_response.verify_contracts().err().unwrap();
assert_eq!(1, errors.len());
assert!(errors.first().unwrap().contains("client_secret"));
}
fn invalid_active_contract() -> ContractsDataResponse {
ContractsDataResponse {
organization_id: None,
contract_id: "contract id".to_string(),
api_id: "api id".to_string(),
version_id: "".to_string(),
sla_tier_id: None,
client_id: "".to_string(),
client_secret: None,
client_secret_salt: None,
contract_updated_date: None,
redirect_uris: None,
client_name: None,
client_description: None,
client_updated_date: None,
updated_date: None,
removed: Some(false),
}
}
fn valid_active_contract() -> ContractsDataResponse {
ContractsDataResponse {
organization_id: Some("none".to_string()),
contract_id: "".to_string(),
api_id: "".to_string(),
version_id: "".to_string(),
sla_tier_id: Some("none".to_string()),
client_id: "".to_string(),
client_secret: Some("none".to_string()),
client_secret_salt: Some("none".to_string()),
contract_updated_date: Some("none".to_string()),
redirect_uris: Some(vec![]),
client_name: Some("none".to_string()),
client_description: Some("none".to_string()),
client_updated_date: Some("none".to_string()),
updated_date: Some("none".to_string()),
removed: None,
}
}
fn invalid_removed_contract() -> ContractsDataResponse {
ContractsDataResponse {
organization_id: None,
contract_id: "".to_string(),
api_id: "".to_string(),
version_id: "".to_string(),
sla_tier_id: None,
client_id: "".to_string(),
client_secret: None,
client_secret_salt: None,
contract_updated_date: None,
redirect_uris: None,
client_name: None,
client_description: None,
client_updated_date: None,
updated_date: None,
removed: Some(true),
}
}
fn active_contract_without_secret() -> ContractsDataResponse {
ContractsDataResponse {
organization_id: None,
contract_id: "pub-contract".to_string(),
api_id: "api-1".to_string(),
version_id: "".to_string(),
sla_tier_id: None,
client_id: "public_client".to_string(),
client_secret: None,
client_secret_salt: None,
contract_updated_date: None,
redirect_uris: None,
client_name: Some("Public Client".to_string()),
client_description: None,
client_updated_date: None,
updated_date: None,
removed: Some(false),
}
}
fn contract_with_secret_no_salt() -> ContractsDataResponse {
ContractsDataResponse {
organization_id: None,
contract_id: "c1".to_string(),
api_id: "api-1".to_string(),
version_id: "".to_string(),
sla_tier_id: None,
client_id: "client1".to_string(),
client_secret: Some("hashed".to_string()),
client_secret_salt: None,
contract_updated_date: None,
redirect_uris: None,
client_name: Some("Client 1".to_string()),
client_description: None,
client_updated_date: None,
updated_date: None,
removed: Some(false),
}
}
fn contract_with_salt_no_secret() -> ContractsDataResponse {
ContractsDataResponse {
organization_id: None,
contract_id: "c2".to_string(),
api_id: "api-1".to_string(),
version_id: "".to_string(),
sla_tier_id: None,
client_id: "client2".to_string(),
client_secret: None,
client_secret_salt: Some("salt".to_string()),
contract_updated_date: None,
redirect_uris: None,
client_name: Some("Client 2".to_string()),
client_description: None,
client_updated_date: None,
updated_date: None,
removed: Some(false),
}
}
}