fn generate_parts()

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)),
        }
    }