progenitor/progenitor-impl/src/util.rs

145 lines
3.8 KiB
Rust

// Copyright 2022 Oxide Computer Company
use std::collections::BTreeMap;
use indexmap::IndexMap;
use openapiv3::{
Components, Parameter, ReferenceOr, RequestBody, Response, Schema,
};
use unicode_ident::{is_xid_continue, is_xid_start};
use crate::Result;
pub(crate) trait ReferenceOrExt<T: ComponentLookup> {
fn item<'a>(&'a self, components: &'a Option<Components>) -> Result<&'a T>;
}
pub(crate) trait ComponentLookup: Sized {
fn get_components(
components: &Components,
) -> &IndexMap<String, ReferenceOr<Self>>;
}
impl<T: ComponentLookup> ReferenceOrExt<T> for openapiv3::ReferenceOr<T> {
fn item<'a>(&'a self, components: &'a Option<Components>) -> Result<&'a T> {
match self {
ReferenceOr::Item(item) => Ok(item),
ReferenceOr::Reference { reference } => {
let idx = reference.rfind('/').unwrap();
let key = &reference[idx + 1..];
let parameters =
T::get_components(components.as_ref().unwrap());
parameters
.get(key)
.unwrap_or_else(|| panic!("key {} is missing", key))
.item(components)
}
}
}
}
pub(crate) fn items<'a, T>(
refs: &'a [ReferenceOr<T>],
components: &'a Option<Components>,
) -> impl Iterator<Item = Result<&'a T>>
where
T: ComponentLookup,
{
refs.iter().map(|r| r.item(components))
}
pub(crate) fn parameter_map<'a>(
refs: &'a [ReferenceOr<Parameter>],
components: &'a Option<Components>,
) -> Result<BTreeMap<&'a String, &'a Parameter>> {
items(refs, components)
.map(|res| res.map(|param| (&param.parameter_data_ref().name, param)))
.collect()
}
impl ComponentLookup for Parameter {
fn get_components(
components: &Components,
) -> &IndexMap<String, ReferenceOr<Self>> {
&components.parameters
}
}
impl ComponentLookup for RequestBody {
fn get_components(
components: &Components,
) -> &IndexMap<String, ReferenceOr<Self>> {
&components.request_bodies
}
}
impl ComponentLookup for Response {
fn get_components(
components: &Components,
) -> &IndexMap<String, ReferenceOr<Self>> {
&components.responses
}
}
impl ComponentLookup for Schema {
fn get_components(
components: &Components,
) -> &IndexMap<String, ReferenceOr<Self>> {
&components.schemas
}
}
pub(crate) enum Case {
Pascal,
Snake,
}
pub(crate) fn sanitize(input: &str, case: Case) -> String {
use heck::{ToPascalCase, ToSnakeCase};
let to_case = match case {
Case::Pascal => str::to_pascal_case,
Case::Snake => str::to_snake_case,
};
// If every case was special then none of them would be.
let out = match input {
"+1" => "plus1".to_string(),
"-1" => "minus1".to_string(),
_ => to_case(
&input
.replace('\'', "")
.replace(|c: char| !is_xid_continue(c), "-"),
),
};
let out = match out.chars().next() {
None => to_case("x"),
Some(c) if is_xid_start(c) => out,
Some(_) => format!("_{}", out),
};
// Make sure the string is a valid Rust identifier.
if syn::parse_str::<syn::Ident>(&out).is_ok() {
out
} else {
format!("{}_", out)
}
}
/// Given a desired name and a slice of proc_macro2::Ident, generate a new
/// Ident that is unique from the slice.
pub(crate) fn unique_ident_from(
name: &str,
identities: &[proc_macro2::Ident],
) -> proc_macro2::Ident {
let mut name = name.to_string();
loop {
let ident = quote::format_ident!("{}", name);
if !identities.contains(&ident) {
return ident;
}
name.insert_str(0, "_");
}
}