oas3-gen 0.18.0

A rust type generator for OpenAPI v3.1.x specification.
use proc_macro2::TokenStream;
use quote::quote;

use crate::generator::ast::{RustPrimitive, TypeRef};

pub(crate) fn json_to_rust_literal(value: &serde_json::Value, rust_type: &TypeRef) -> TokenStream {
  if matches!(value, serde_json::Value::Null) {
    return quote! { None };
  }

  let base_expr = coerce_to_rust_type(value, &rust_type.base_type);

  if rust_type.nullable {
    quote! { Some(#base_expr) }
  } else {
    base_expr
  }
}

pub(crate) fn parse_type_string(type_str: &str) -> TokenStream {
  type_str.parse().unwrap_or_else(|_| quote! { serde_json::Value })
}

fn coerce_to_rust_type(value: &serde_json::Value, rust_type: &RustPrimitive) -> TokenStream {
  match rust_type {
    RustPrimitive::String => coerce_to_string(value),
    RustPrimitive::I8
    | RustPrimitive::I16
    | RustPrimitive::I32
    | RustPrimitive::I64
    | RustPrimitive::I128
    | RustPrimitive::Isize => coerce_to_int(value, rust_type),
    RustPrimitive::U8
    | RustPrimitive::U16
    | RustPrimitive::U32
    | RustPrimitive::U64
    | RustPrimitive::U128
    | RustPrimitive::Usize => coerce_to_uint(value, rust_type),
    RustPrimitive::F32 | RustPrimitive::F64 => coerce_to_float(value, rust_type),
    RustPrimitive::Bool => coerce_to_bool(value),
    _ => quote! { Default::default() },
  }
}

fn coerce_to_string(value: &serde_json::Value) -> TokenStream {
  match value {
    serde_json::Value::String(s) if s.is_empty() => quote! { String::new() },
    serde_json::Value::String(s) => quote! { #s.to_string() },
    serde_json::Value::Number(n) => {
      let n_str = n.to_string();
      quote! { #n_str.to_string() }
    }
    serde_json::Value::Bool(b) => {
      let b_str = b.to_string();
      quote! { #b_str.to_string() }
    }
    _ => quote! { Default::default() },
  }
}

fn coerce_to_bool(value: &serde_json::Value) -> TokenStream {
  match value {
    serde_json::Value::Bool(b) => quote! { #b },
    serde_json::Value::Number(n) => {
      let b = n.as_i64().is_some_and(|i| i != 0);
      quote! { #b }
    }
    serde_json::Value::String(s) => {
      let b = matches!(s.to_lowercase().as_str(), "true" | "1" | "yes");
      quote! { #b }
    }
    _ => quote! { Default::default() },
  }
}

#[allow(clippy::match_same_arms)]
fn coerce_to_int(value: &serde_json::Value, rust_type: &RustPrimitive) -> TokenStream {
  let type_suffix = match rust_type {
    RustPrimitive::I8 => "i8",
    RustPrimitive::I16 => "i16",
    RustPrimitive::I32 => "i32",
    RustPrimitive::I64 => "i64",
    RustPrimitive::I128 => "i128",
    RustPrimitive::Isize => "isize",
    _ => "i64",
  };

  match value {
    serde_json::Value::Number(n) => n.as_i64().map_or_else(
      || quote! { Default::default() },
      |i| {
        let literal = format!("{i}{type_suffix}");
        literal
          .parse::<TokenStream>()
          .unwrap_or_else(|_| quote! { Default::default() })
      },
    ),
    serde_json::Value::String(s) => s.parse::<i64>().ok().map_or_else(
      || quote! { Default::default() },
      |i| {
        let literal = format!("{i}{type_suffix}");
        literal
          .parse::<TokenStream>()
          .unwrap_or_else(|_| quote! { Default::default() })
      },
    ),
    _ => quote! { Default::default() },
  }
}

#[allow(clippy::match_same_arms)]
fn coerce_to_uint(value: &serde_json::Value, rust_type: &RustPrimitive) -> TokenStream {
  let type_suffix = match rust_type {
    RustPrimitive::U8 => "u8",
    RustPrimitive::U16 => "u16",
    RustPrimitive::U32 => "u32",
    RustPrimitive::U64 => "u64",
    RustPrimitive::U128 => "u128",
    RustPrimitive::Usize => "usize",
    _ => "u64",
  };

  match value {
    serde_json::Value::Number(n) => n.as_u64().map_or_else(
      || quote! { Default::default() },
      |u| {
        let literal = format!("{u}{type_suffix}");
        literal
          .parse::<TokenStream>()
          .unwrap_or_else(|_| quote! { Default::default() })
      },
    ),
    serde_json::Value::String(s) => s.parse::<u64>().ok().map_or_else(
      || quote! { Default::default() },
      |u| {
        let literal = format!("{u}{type_suffix}");
        literal
          .parse::<TokenStream>()
          .unwrap_or_else(|_| quote! { Default::default() })
      },
    ),
    _ => quote! { Default::default() },
  }
}

fn coerce_to_float(value: &serde_json::Value, rust_type: &RustPrimitive) -> TokenStream {
  #[allow(clippy::match_same_arms)]
  let type_suffix = match rust_type {
    RustPrimitive::F32 => "f32",
    RustPrimitive::F64 => "f64",
    _ => "f64",
  };

  match value {
    serde_json::Value::Number(n) => {
      if let Some(f) = n.as_f64() {
        let literal = format!("{f}{type_suffix}");
        literal
          .parse::<TokenStream>()
          .unwrap_or_else(|_| quote! { Default::default() })
      } else if let Some(i) = n.as_i64() {
        #[allow(clippy::cast_precision_loss)]
        let f = i as f64;
        let literal = format!("{f}{type_suffix}");
        literal
          .parse::<TokenStream>()
          .unwrap_or_else(|_| quote! { Default::default() })
      } else {
        quote! { Default::default() }
      }
    }
    serde_json::Value::String(s) => s.parse::<f64>().ok().map_or_else(
      || quote! { Default::default() },
      |f| {
        let literal = format!("{f}{type_suffix}");
        literal
          .parse::<TokenStream>()
          .unwrap_or_else(|_| quote! { Default::default() })
      },
    ),
    _ => quote! { Default::default() },
  }
}