use super::utils::{matches, not_found};
use crate::{Backend, UnitHttpMessage, UnitHttpRequest, UnitHttpResponse};
use regex::Regex;
use serde::Serialize;
use serde_json::json;
use sha2::{Digest, Sha256};
use std::cell::RefCell;
#[derive(Default)]
pub struct AnypointBackend {
contracts: RefCell<Vec<ContractsDataResponse>>,
}
const CONTRACTS_PATH_PREFIX: &str =
"/apigateway/ccs/v3/organizations/test-org-id/environments/test-env-id/apis/1/contracts";
thread_local! {
static LOGIN_PATH: Regex = Regex::new("^/accounts/oauth2/token$").unwrap();
static CONTRACTS_PATH: Regex = Regex::new("^/apigateway/ccs/v3/organizations/test-org-id/environments/test-env-id/apis/1/contracts$").unwrap();
static CONTRACTS_UPDATE_PATH: Regex = Regex::new(r"^/apigateway/ccs/v3/organizations/test-org-id/environments/test-env-id/apis/1/contracts/(.*)$").unwrap();
}
impl Backend for AnypointBackend {
fn call(&self, req: UnitHttpRequest) -> UnitHttpResponse {
let path = req.header(":path").unwrap();
if let Some([]) = matches(&LOGIN_PATH, path) {
self.login()
} else if let Some([]) = matches(&CONTRACTS_PATH, path) {
self.contracts(0, path)
} else if let Some([pos]) = matches(&CONTRACTS_UPDATE_PATH, path) {
self.contracts(pos.parse().unwrap(), path)
} else {
not_found()
}
}
}
impl AnypointBackend {
pub(crate) fn login(&self) -> UnitHttpResponse {
UnitHttpResponse::new(200)
.with_header("content-type", "application/json")
.with_body(json!({"access_token": "test", "token_type": "Bearer"}).to_string())
}
pub(crate) fn contracts(&self, start: usize, path: &str) -> UnitHttpResponse {
UnitHttpResponse::new(200)
.with_header("content-type", "application/json")
.with_body(
serde_json::to_string(&ContractsResponse {
links: ContractsLinksResponse {
self_link: path.to_string(),
next: format!("{CONTRACTS_PATH_PREFIX}/{}", self.contracts.borrow().len()),
},
data: self.contracts.borrow()[start..].to_vec(),
})
.unwrap(),
)
}
pub(crate) fn add_contract(
&self,
id: String,
name: String,
secret: Option<String>,
sla_tier_id: Option<String>,
) {
self.contracts
.borrow_mut()
.push(ContractsDataResponse::new(secret, id, name, sla_tier_id));
}
pub(crate) fn remove_contract(&self, id: String) {
self.contracts
.borrow_mut()
.push(ContractsDataResponse::delete(id));
}
}
pub fn hash(salt: &str, client_secret: &str) -> String {
let mut hashed_result = [0u8; 32];
let mut hasher = Sha256::new();
hasher.update(format!("{salt}{client_secret}").as_str());
hashed_result.copy_from_slice(&hasher.finalize());
hex::encode(hashed_result)
}
#[derive(Serialize, Debug)]
struct ContractsLinksResponse {
#[serde(rename = "self")]
self_link: String,
next: String,
}
#[allow(unused)]
#[derive(Serialize, Debug, Clone)]
struct ContractsDataResponse {
#[serde(rename = "organizationId")]
organization_id: Option<String>,
#[serde(rename = "contractId")]
contract_id: String,
#[serde(rename = "apiId")]
api_id: String,
#[serde(rename = "versionId")]
version_id: String,
#[serde(rename = "slaTierId")]
sla_tier_id: Option<String>,
#[serde(rename = "clientId")]
client_id: String,
#[serde(rename = "clientSecret")]
client_secret: Option<String>,
#[serde(rename = "clientSecretSalt")]
client_secret_salt: Option<String>,
#[serde(rename = "contractUpdatedDate")]
contract_updated_date: Option<String>,
#[serde(rename = "redirectUris")]
redirect_uris: Option<Vec<String>>,
#[serde(rename = "clientName")]
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>,
removed: Option<bool>,
}
impl ContractsDataResponse {
fn new(
client_secret: Option<String>,
client_id: String,
client_name: String,
sla_tier_id: Option<String>,
) -> Self {
Self {
organization_id: Some("org".to_string()),
contract_id: "contract_id".to_string(),
api_id: "api_id".to_string(),
version_id: "version_id".to_string(),
sla_tier_id,
client_id,
client_secret: client_secret.map(|secret| hash("client_secret_salt", &secret)),
client_secret_salt: Some("client_secret_salt".to_string()),
contract_updated_date: Some("contract_updated_date".to_string()),
redirect_uris: None,
client_name: Some(client_name),
client_description: Some("client_description".to_string()),
client_updated_date: None,
updated_date: Some("updated_date".to_string()),
removed: Some(false),
}
}
fn delete(client_id: String) -> Self {
Self {
organization_id: Some("org".to_string()),
contract_id: "contract_id".to_string(),
api_id: "api_id".to_string(),
version_id: "version_id".to_string(),
sla_tier_id: None,
client_id,
client_secret: None,
client_secret_salt: None,
contract_updated_date: Some("contract_updated_date".to_string()),
redirect_uris: None,
client_name: None,
client_description: None,
client_updated_date: None,
updated_date: Some("updated_date".to_string()),
removed: Some(true),
}
}
}
#[derive(Serialize, Debug)]
pub struct ContractsResponse {
links: ContractsLinksResponse,
data: Vec<ContractsDataResponse>,
}