use crate::MetadataClient;
use crate::envelope::xml_escape;
use crate::error::{MetadataError, MetadataResult};
use crate::result::{
DescribeMetadataResult, DescribeValueTypeResult, FileProperties, ListMetadataQuery,
};
use crate::transport::SoapOperation;
use serde::Deserialize;
pub const MAX_LIST_METADATA_QUERIES_PER_CALL: usize = 3;
struct ListMetadataOp<'a> {
queries: Vec<ListMetadataQuery>,
as_of_version: &'a str,
}
#[derive(Deserialize)]
struct ListMetadataResponseWire {
#[serde(default, rename = "result")]
results: Vec<FileProperties>,
}
impl SoapOperation for ListMetadataOp<'_> {
const NAME: &'static str = "listMetadata";
type Response = ListMetadataResponseWire;
fn render_body(&self) -> MetadataResult<String> {
let mut out = String::with_capacity(64 + self.queries.len() * 64);
for q in &self.queries {
out.push_str("<met:queries>");
if let Some(folder) = &q.folder {
out.push_str("<met:folder>");
out.push_str(&xml_escape(folder));
out.push_str("</met:folder>");
}
out.push_str("<met:type>");
out.push_str(&xml_escape(&q.type_name));
out.push_str("</met:type>");
out.push_str("</met:queries>");
}
out.push_str("<met:asOfVersion>");
out.push_str(&xml_escape(self.as_of_version));
out.push_str("</met:asOfVersion>");
Ok(out)
}
}
struct DescribeMetadataOp<'a> {
as_of_version: &'a str,
}
#[derive(Deserialize)]
struct DescribeMetadataResponseWire {
result: DescribeMetadataResult,
}
impl SoapOperation for DescribeMetadataOp<'_> {
const NAME: &'static str = "describeMetadata";
type Response = DescribeMetadataResponseWire;
fn render_body(&self) -> MetadataResult<String> {
Ok(format!(
"<met:asOfVersion>{}</met:asOfVersion>",
xml_escape(self.as_of_version),
))
}
}
struct DescribeValueTypeOp {
type_name: String,
}
#[derive(Deserialize)]
struct DescribeValueTypeResponseWire {
result: DescribeValueTypeResult,
}
impl SoapOperation for DescribeValueTypeOp {
const NAME: &'static str = "describeValueType";
type Response = DescribeValueTypeResponseWire;
fn render_body(&self) -> MetadataResult<String> {
Ok(format!(
"<met:type>{}</met:type>",
xml_escape(&self.type_name),
))
}
}
impl MetadataClient {
pub async fn list_metadata(
&self,
queries: Vec<ListMetadataQuery>,
as_of_version: &str,
) -> MetadataResult<Vec<FileProperties>> {
if queries.len() > MAX_LIST_METADATA_QUERIES_PER_CALL {
return Err(MetadataError::InvalidArgument(format!(
"listMetadata accepts at most {MAX_LIST_METADATA_QUERIES_PER_CALL} queries per \
call; got {}",
queries.len()
)));
}
let op = ListMetadataOp {
queries,
as_of_version,
};
let resp = self.call(&op).await?;
Ok(resp.results)
}
pub async fn describe_metadata(
&self,
as_of_version: &str,
) -> MetadataResult<DescribeMetadataResult> {
let op = DescribeMetadataOp { as_of_version };
let resp = self.call(&op).await?;
Ok(resp.result)
}
pub async fn describe_value_type(
&self,
qualified_type_name: &str,
) -> MetadataResult<DescribeValueTypeResult> {
let op = DescribeValueTypeOp {
type_name: qualified_type_name.to_string(),
};
let resp = self.call(&op).await?;
Ok(resp.result)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn list_metadata_op_emits_queries_and_version() {
let op = ListMetadataOp {
queries: vec![
ListMetadataQuery {
type_name: "ApexClass".into(),
folder: None,
},
ListMetadataQuery {
type_name: "Dashboard".into(),
folder: Some("SharedDashboards".into()),
},
],
as_of_version: "66.0",
};
let body = op.render_body().unwrap();
assert!(body.contains("<met:queries><met:type>ApexClass</met:type></met:queries>"));
assert!(body.contains(
"<met:queries><met:folder>SharedDashboards</met:folder><met:type>Dashboard</met:type></met:queries>"
));
assert!(body.contains("<met:asOfVersion>66.0</met:asOfVersion>"));
}
#[test]
fn list_metadata_op_escapes_folder_and_type() {
let op = ListMetadataOp {
queries: vec![ListMetadataQuery {
type_name: "Type<>".into(),
folder: Some("Folder&Co".into()),
}],
as_of_version: "66.0",
};
let body = op.render_body().unwrap();
assert!(body.contains("<met:folder>Folder&Co</met:folder>"));
assert!(body.contains("<met:type>Type<></met:type>"));
}
#[test]
fn describe_metadata_op_emits_version() {
let op = DescribeMetadataOp {
as_of_version: "58.0",
};
let body = op.render_body().unwrap();
assert_eq!(body, "<met:asOfVersion>58.0</met:asOfVersion>");
}
#[test]
fn describe_value_type_emits_qualified_type() {
let op = DescribeValueTypeOp {
type_name: "{http://soap.sforce.com/2006/04/metadata}ApexClass".into(),
};
let body = op.render_body().unwrap();
assert_eq!(
body,
"<met:type>{http://soap.sforce.com/2006/04/metadata}ApexClass</met:type>"
);
}
}