oas3-gen 0.12.0

A rust type generator for OpenAPI v3.1.x specification.
use std::collections::BTreeMap;

use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::{
  generator::ast::RustType,
  reserved::{header_const_name, regex_const_name},
};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct RegexKey {
  owner_type: String,
  owner_variant: Option<String>,
  field: String,
}

impl RegexKey {
  pub fn for_struct(type_name: &str, field_name: &str) -> Self {
    Self {
      owner_type: type_name.to_string(),
      owner_variant: None,
      field: field_name.to_string(),
    }
  }

  #[allow(dead_code)]
  pub fn for_variant(type_name: &str, variant_name: &str, field_name: &str) -> Self {
    Self {
      owner_type: type_name.to_string(),
      owner_variant: Some(variant_name.to_string()),
      field: field_name.to_string(),
    }
  }

  pub fn parts(&self) -> Vec<&str> {
    let mut parts = vec![self.owner_type.as_str()];
    if let Some(variant) = &self.owner_variant {
      parts.push(variant.as_str());
    }
    parts.push(self.field.as_str());
    parts
  }
}

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

  for rust_type in types {
    match rust_type {
      RustType::Struct(def) => {
        for field in &def.fields {
          let Some(pattern) = &field.regex_validation else {
            continue;
          };
          let key = RegexKey::for_struct(&def.name, &field.name);
          let pattern_key = pattern.clone();
          let const_name = if let Some(existing) = pattern_to_const.get(&pattern_key) {
            existing.clone()
          } else {
            let name = regex_const_name(&key.parts());
            pattern_to_const.insert(pattern_key.clone(), name.clone());
            const_defs.insert(name.clone(), pattern_key);
            name
          };
          lookup.insert(key, const_name);
        }
      }
      RustType::Enum(_) | RustType::TypeAlias(_) | RustType::DiscriminatedEnum(_) => {}
    }
  }

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

  let regex_defs: Vec<TokenStream> = const_defs
    .into_iter()
    .map(|(name, pattern)| {
      let ident = format_ident!("{}", name);
      quote! {
        static #ident: 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: &[&String]) -> TokenStream {
  if headers.is_empty() {
    return quote! {};
  }

  let const_tokens: Vec<TokenStream> = headers
    .iter()
    .map(|header| {
      let const_name = header_const_name(header);
      let ident = format_ident!("{}", const_name);
      quote! {
        pub const #ident: http::HeaderName = http::HeaderName::from_static(#header);
      }
    })
    .collect();

  quote! { #(#const_tokens)* }
}