oas3-gen 0.22.3

A rust type generator for OpenAPI v3.1.x specification.
use std::collections::{BTreeMap, btree_map::Entry};

use proc_macro2::TokenStream;
use quote::quote;

use crate::generator::ast::{
  RegexKey, RustType, ValidationAttribute,
  tokens::{ConstToken, HeaderToken},
};

pub(crate) fn generate_regex_constants(types: &[&RustType]) -> (TokenStream, BTreeMap<RegexKey, ConstToken>) {
  let mut const_defs: BTreeMap<ConstToken, String> = BTreeMap::new();
  let mut lookup: BTreeMap<RegexKey, ConstToken> = BTreeMap::new();
  let mut pattern_to_const: BTreeMap<String, ConstToken> = BTreeMap::new();

  for rust_type in types {
    let RustType::Struct(def) = rust_type else {
      continue;
    };

    for field in &def.fields {
      let Some(pattern) = field.validation_attrs.iter().find_map(|attr| match attr {
        ValidationAttribute::Regex(p) => Some(p),
        _ => None,
      }) else {
        continue;
      };

      let key = RegexKey::for_struct(&def.name, field.name.as_str());
      let const_token = match pattern_to_const.entry(pattern.clone()) {
        Entry::Occupied(entry) => entry.get().clone(),
        Entry::Vacant(entry) => {
          let token = ConstToken::from(&key);
          const_defs.insert(token.clone(), pattern.clone());
          entry.insert(token.clone());
          token
        }
      };
      lookup.insert(key, const_token);
    }
  }

  if const_defs.is_empty() {
    return (quote! {}, lookup);
  }

  let regex_defs: Vec<TokenStream> = const_defs
    .into_iter()
    .map(|(const_token, pattern)| {
      quote! {
        static #const_token: std::sync::LazyLock<regex::Regex> =
          std::sync::LazyLock::new(|| regex::Regex::new(#pattern).expect("invalid regex"));
      }
    })
    .collect();

  (quote! { #(#regex_defs)* }, lookup)
}

pub(crate) fn generate_header_constants(headers: &[HeaderToken]) -> TokenStream {
  if headers.is_empty() {
    return quote! {};
  }

  let const_tokens: Vec<TokenStream> = headers
    .iter()
    .map(|header| {
      let const_token = &header.const_token;
      let header_name = &header.header_name;
      quote! {
        pub const #const_token: http::HeaderName = http::HeaderName::from_static(#header_name);
      }
    })
    .collect();

  quote! { #(#const_tokens)* }
}