use apolloconfig_sig::AuthSignature;
use http_api_client_endpoint::{
http::{
header::{ACCEPT, USER_AGENT},
Method, StatusCode,
},
Body, Endpoint, Request, Response, MIME_APPLICATION_JSON,
};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use url::Url;
use crate::{
endpoints::{common::EndpointError, CLUSTER_NAME_DEFAULT, NAMESPACE_NAME_DEFAULT},
objects::{
configurations::{Configurations, ConfigurationsFromMapError},
ResponseBodyErrJson,
},
types::NamespaceFormat,
};
#[derive(Debug, Clone)]
pub struct Configs {
pub config_server_url: Box<str>,
pub access_key_secret: Box<str>,
pub app_id: Box<str>,
pub cluster_name: Option<Box<str>>,
pub namespace_name: Option<Box<str>>,
pub release_key: Option<Box<str>>,
pub client_ip: Option<Box<str>>,
}
impl Configs {
pub fn new(
config_server_url: impl AsRef<str>,
access_key_secret: impl AsRef<str>,
app_id: impl AsRef<str>,
) -> Self {
Self {
config_server_url: config_server_url.as_ref().into(),
access_key_secret: access_key_secret.as_ref().into(),
app_id: app_id.as_ref().into(),
cluster_name: None,
namespace_name: None,
release_key: None,
client_ip: None,
}
}
pub fn cluster_name(mut self, value: impl AsRef<str>) -> Self {
self.cluster_name = Some(value.as_ref().into());
self
}
pub fn namespace_name(mut self, value: impl AsRef<str>) -> Self {
self.namespace_name = Some(value.as_ref().into());
self
}
pub fn release_key(mut self, value: impl AsRef<str>) -> Self {
self.release_key = Some(value.as_ref().into());
self
}
pub fn client_ip(mut self, value: impl AsRef<str>) -> Self {
self.client_ip = Some(value.as_ref().into());
self
}
}
impl Endpoint for Configs {
type RenderRequestError = EndpointError;
type ParseResponseOutput = ConfigsEndpointRet;
type ParseResponseError = EndpointError;
fn render_request(&self) -> Result<Request<Body>, Self::RenderRequestError> {
let mut url =
Url::parse(&self.config_server_url).map_err(EndpointError::MakeRequestUrlFailed)?;
let path = format!(
"/configs/{}/{}/{}",
self.app_id,
self.cluster_name.as_deref().unwrap_or(CLUSTER_NAME_DEFAULT),
self.namespace_name
.as_deref()
.unwrap_or(NAMESPACE_NAME_DEFAULT)
);
url.set_path(path.as_str());
if let Some(release_key) = &self.release_key {
url.query_pairs_mut()
.append_pair("releaseKey", release_key.to_string().as_str());
}
if let Some(client_ip) = &self.client_ip {
url.query_pairs_mut()
.append_pair("ip", client_ip.to_string().as_str());
}
let auth_headers = AuthSignature
.http_headers(url.as_str(), &self.app_id, &self.access_key_secret)
.map_err(EndpointError::MakeSignatureFailed)?;
let request_builder = Request::builder()
.method(Method::GET)
.uri(url.as_str())
.header(USER_AGENT, "apolloconfig-rs")
.header(ACCEPT, MIME_APPLICATION_JSON);
let request_builder = auth_headers
.into_iter()
.flat_map(|(k, v)| k.map(|k| (k, v)))
.fold(request_builder, |request_builder, (k, v)| {
request_builder.header(k, v)
});
let request = request_builder
.body(vec![])
.map_err(EndpointError::MakeRequestFailed)?;
Ok(request)
}
fn parse_response(
&self,
response: Response<Body>,
) -> Result<Self::ParseResponseOutput, Self::ParseResponseError> {
let status = response.status();
match status {
StatusCode::OK => Ok(ConfigsEndpointRet::Ok(
serde_json::from_slice(response.body())
.map_err(EndpointError::DeResponseBodyOkJsonFailed)?,
)),
StatusCode::NOT_MODIFIED => {
if self.release_key.is_none() {
return Err(EndpointError::Other(
"server error because release_key is none".into(),
));
}
Ok(ConfigsEndpointRet::NotChanged)
}
status => match serde_json::from_slice::<ResponseBodyErrJson>(response.body()) {
Ok(err_json) => Ok(ConfigsEndpointRet::Other((status, Ok(err_json)))),
Err(_) => Ok(ConfigsEndpointRet::Other((
status,
Err(response.body().to_owned()),
))),
},
}
}
}
#[derive(Debug, Clone)]
pub enum ConfigsEndpointRet {
Ok(ConfigsResponseBodyOkJson),
NotChanged,
Other((StatusCode, Result<ResponseBodyErrJson, Body>)),
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ConfigsResponseBodyOkJson {
#[serde(rename = "appId")]
pub app_id: Box<str>,
#[serde(rename = "cluster")]
pub cluster_name: Box<str>,
#[serde(rename = "namespaceName")]
pub namespace_name: Box<str>,
configurations: Map<String, Value>,
#[serde(rename = "releaseKey")]
pub release_key: Box<str>,
}
impl ConfigsResponseBodyOkJson {
pub fn configurations(&self) -> Result<Configurations, ConfigurationsFromMapError> {
Configurations::from_map(
&self.configurations,
NamespaceFormat::from_namespace_name(Some(&self.namespace_name)),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_de_response_body_ok_json() {
let content =
include_str!("../../tests/response_body_json_files/configs_application_ok.json");
match serde_json::from_str::<ConfigsResponseBodyOkJson>(content) {
Ok(ok_json) => {
let c = ok_json.configurations().unwrap();
match &c {
Configurations::Properties(m) => {
assert_eq!(m.get("content").unwrap().as_str().unwrap(), "content");
}
_ => panic!("{:?}", c),
}
let m1: Map<String, Value> = serde_json::from_str(content).unwrap();
let m2: Map<String, Value> =
serde_json::from_str(serde_json::to_string(&c).unwrap().as_str()).unwrap();
assert_eq!(m1.get("configurations").unwrap().as_object().unwrap(), &m2);
}
Err(err) => panic!("{}", err),
}
let content =
include_str!("../../tests/response_body_json_files/configs_namespace_json_ok.json");
match serde_json::from_str::<ConfigsResponseBodyOkJson>(content) {
Ok(ok_json) => {
let c = ok_json.configurations().unwrap();
match &c {
Configurations::Json(v) => {
assert_eq!(
v.as_object()
.unwrap()
.get("firstName")
.unwrap()
.as_str()
.unwrap(),
"John"
);
}
_ => panic!("{:?}", c),
}
let m1: Map<String, Value> = serde_json::from_str(content).unwrap();
let m2: Map<String, Value> =
serde_json::from_str(serde_json::to_string(&c).unwrap().as_str()).unwrap();
assert_eq!(
serde_json::from_str::<Map<String, Value>>(
m1.get("configurations")
.unwrap()
.as_object()
.unwrap()
.get("content")
.unwrap()
.as_str()
.unwrap()
)
.unwrap(),
serde_json::from_str::<Map<String, Value>>(
m2.get("content").unwrap().as_str().unwrap()
)
.unwrap()
);
}
Err(err) => panic!("{}", err),
}
let content = include_str!(
"../../tests/response_body_json_files/configs_namespace_properties_ok.json"
);
match serde_json::from_str::<ConfigsResponseBodyOkJson>(content) {
Ok(ok_json) => {
let c = ok_json.configurations().unwrap();
match &c {
Configurations::Properties(m) => {
assert_eq!(m.get("k").unwrap().as_str().unwrap(), "v");
}
_ => panic!("{:?}", c),
}
}
Err(err) => panic!("{}", err),
}
let content =
include_str!("../../tests/response_body_json_files/configs_namespace_txt_ok.json");
match serde_json::from_str::<ConfigsResponseBodyOkJson>(content) {
Ok(ok_json) => {
let c = ok_json.configurations().unwrap();
match &c {
Configurations::Txt(m) => {
assert_eq!(*m, "This is Content.".into());
}
_ => panic!("{:?}", c),
}
let m1: Map<String, Value> = serde_json::from_str(content).unwrap();
let m2: Map<String, Value> =
serde_json::from_str(serde_json::to_string(&c).unwrap().as_str()).unwrap();
assert_eq!(
m1.get("configurations")
.unwrap()
.as_object()
.unwrap()
.get("content")
.unwrap()
.as_str()
.unwrap(),
m2.get("content").unwrap().as_str().unwrap()
);
}
Err(err) => panic!("{}", err),
}
let content =
include_str!("../../tests/response_body_json_files/configs_namespace_xml_ok.json");
match serde_json::from_str::<ConfigsResponseBodyOkJson>(content) {
Ok(ok_json) => {
let c = ok_json.configurations().unwrap();
match &c {
Configurations::Other(m) => {
assert!(m.contains("ISO-8859-1"));
}
_ => panic!("{:?}", c),
}
let m1: Map<String, Value> = serde_json::from_str(content).unwrap();
let m2: Map<String, Value> =
serde_json::from_str(serde_json::to_string(&c).unwrap().as_str()).unwrap();
assert_eq!(
m1.get("configurations")
.unwrap()
.as_object()
.unwrap()
.get("content")
.unwrap()
.as_str()
.unwrap(),
m2.get("content").unwrap().as_str().unwrap()
);
}
Err(err) => panic!("{}", err),
}
let content =
include_str!("../../tests/response_body_json_files/configs_namespace_yml_ok.json");
match serde_json::from_str::<ConfigsResponseBodyOkJson>(content) {
Ok(ok_json) => {
let c = ok_json.configurations().unwrap();
match &c {
Configurations::Other(m) => {
assert!(m.contains("name: John Smith"));
}
_ => panic!("{:?}", c),
}
let m1: Map<String, Value> = serde_json::from_str(content).unwrap();
let m2: Map<String, Value> =
serde_json::from_str(serde_json::to_string(&c).unwrap().as_str()).unwrap();
assert_eq!(
m1.get("configurations")
.unwrap()
.as_object()
.unwrap()
.get("content")
.unwrap()
.as_str()
.unwrap(),
m2.get("content").unwrap().as_str().unwrap()
);
}
Err(err) => panic!("{}", err),
}
}
}