in yaml_test_runner/src/step/do.rs [641:845]
fn generate_parts(
api_call: &str,
endpoint: &ApiEndpoint,
parts: &[(&str, &Yaml)],
) -> anyhow::Result<Option<Tokens>> {
// TODO: ideally, this should share the logic from EnumBuilder
let enum_name = {
let name = api_call.to_pascal_case().replace(".", "");
syn::Ident::from(format!("{}Parts", name))
};
// Enum variants containing no URL parts where there is only a single API URL,
// are not required to be passed in the API.
//
// Also, short circuit for tests where the only parts specified are null
// e.g. security API test. It seems these should simply omit the value though...
if parts.is_empty() || parts.iter().all(|(_, v)| v.is_null()) {
let param_counts = endpoint
.url
.paths
.iter()
.map(|p| p.path.params().len())
.collect::<Vec<usize>>();
// check there's actually a None value
if !param_counts.contains(&0) {
return Err(anyhow!(
r#"no path for "{}" API with no url parts"#,
api_call
));
}
return match endpoint.url.paths.len() {
1 => Ok(None),
_ => Ok(Some(quote!(#enum_name::None))),
};
}
let path = match endpoint.url.paths.len() {
1 => {
let path = &endpoint.url.paths[0];
if path.path.params().len() == parts.len() {
Some(path)
} else {
None
}
}
_ => {
// get the matching path parts
let matching_path_parts = endpoint
.url
.paths
.iter()
.filter(|path| {
let p = path.path.params();
if p.len() != parts.len() {
return false;
}
let contains = parts
.iter()
.filter_map(|i| if p.contains(&i.0) { Some(()) } else { None })
.collect::<Vec<_>>();
contains.len() == parts.len()
})
.collect::<Vec<_>>();
match matching_path_parts.len() {
0 => None,
_ => Some(matching_path_parts[0]),
}
}
}
.ok_or_else(|| {
anyhow!(
r#"no path for "{}" API with url parts {:?}"#,
&api_call,
parts
)
})?;
let path_parts = path.path.params();
let variant_name = {
let v = path_parts
.iter()
.map(|k| k.to_pascal_case())
.collect::<Vec<_>>()
.join("");
syn::Ident::from(v)
};
let part_tokens: Vec<anyhow::Result<Tokens>> = parts
.iter()
// don't rely on URL parts being ordered in the yaml test in the same order as specified
// in the REST spec.
.sorted_by(|(p, _), (p2, _)| {
let f = path_parts.iter().position(|x| x == p).unwrap();
let s = path_parts.iter().position(|x| x == p2).unwrap();
f.cmp(&s)
})
.map(|(p, v)| {
let ty = path
.parts
.get(*p)
.ok_or_else(|| anyhow!(r#"no url part found for "{}" in {}"#, p, &path.path))?;
match v {
Yaml::String(s) => {
let is_set_value = s.starts_with('$') || s.contains("${");
match ty.ty {
TypeKind::List => {
let values: Vec<Tokens> = s
.split(',')
.map(|s| {
if is_set_value {
let set_value = Self::from_set_value(s);
quote! { #set_value.as_str().unwrap() }
} else {
quote! { #s }
}
})
.collect();
Ok(quote! { &[#(#values),*] })
}
TypeKind::Long => {
if is_set_value {
let set_value = Self::from_set_value(s);
Ok(quote! { #set_value.as_i64().unwrap() })
} else {
let l = s.parse::<i64>().unwrap();
Ok(quote! { #l })
}
}
_ => {
if is_set_value {
let set_value = Self::from_set_value(s);
Ok(quote! { #set_value.as_str().unwrap() })
} else {
Ok(quote! { #s })
}
}
}
}
Yaml::Boolean(b) => {
let s = b.to_string();
Ok(quote! { #s })
}
Yaml::Integer(l) => match ty.ty {
TypeKind::Long => Ok(quote! { #l }),
TypeKind::Integer => {
let i = *l as i32;
Ok(quote! { #i })
}
_ => {
let s = l.to_string();
Ok(quote! { #s })
}
},
Yaml::Array(arr) => {
// only support param string arrays
let result: Vec<_> = arr
.iter()
.map(|i| match i {
Yaml::String(s) => Ok(s),
y => Err(anyhow!("unsupported array value {:?}", y)),
})
.collect();
match ok_or_accumulate(&result) {
Ok(_) => {
let result: Vec<_> =
result.into_iter().filter_map(Result::ok).collect();
match ty.ty {
// Some APIs specify a part is a string in the REST API spec
// but is really a list, which is what a YAML test might pass
// e.g. security.get_role_mapping.
// see https://github.com/elastic/elasticsearch/pull/53207
TypeKind::String => {
let s = result.iter().join(",");
Ok(quote! { #s })
}
_ => Ok(quote! { &[#(#result),*] }),
}
}
Err(e) => Err(anyhow!(e)),
}
}
_ => Err(anyhow!("unsupported value {:?}", v)),
}
})
.collect();
match ok_or_accumulate(&part_tokens) {
Ok(_) => {
let part_tokens: Vec<Tokens> =
part_tokens.into_iter().filter_map(Result::ok).collect();
Ok(Some(
quote! { #enum_name::#variant_name(#(#part_tokens),*) },
))
}
Err(e) => Err(anyhow!(e)),
}
}