oas3-gen 0.13.0

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

use proc_macro2::TokenStream;

use super::{TypeUsage, attributes};
use crate::generator::ast::StructKind;

const DEBUG: &str = "Debug";
const CLONE: &str = "Clone";
const SERIALIZE: &str = "Serialize";
const DESERIALIZE: &str = "Deserialize";
const VALIDATE: &str = "validator::Validate";
const DEFAULT: &str = "oas3_gen_support::Default";

type DeriveSet = BTreeSet<String>;

pub(crate) struct DeriveManager {
  derives: DeriveSet,
}

impl DeriveManager {
  fn new(initial_derives: &[String]) -> Self {
    Self {
      derives: initial_derives.iter().cloned().collect(),
    }
  }

  fn add(&mut self, derive: &str) -> &mut Self {
    self.derives.insert(derive.to_string());
    self
  }

  fn apply_schema_derives(&mut self, usage: Option<&TypeUsage>) -> &mut Self {
    if let Some(usage) = usage {
      match usage {
        TypeUsage::RequestOnly => {
          self.add(SERIALIZE).add(VALIDATE);
        }
        TypeUsage::ResponseOnly => {
          self.add(DESERIALIZE);
        }
        TypeUsage::Bidirectional => {
          self.add(SERIALIZE).add(DESERIALIZE).add(VALIDATE);
        }
      }
    }
    self
  }

  fn apply_request_body_derives(&mut self, usage: Option<&TypeUsage>) -> &mut Self {
    self
      .add(DEBUG)
      .add(CLONE)
      .apply_schema_derives(usage.or(Some(&TypeUsage::Bidirectional)))
      .add(DEFAULT)
  }

  fn apply_operation_request_derives(&mut self) -> &mut Self {
    self.add(DEBUG).add(CLONE).add(VALIDATE).add(DEFAULT)
  }

  pub fn for_struct(def_derives: &[String], kind: StructKind, usage: Option<&TypeUsage>) -> Self {
    match kind {
      StructKind::OperationRequest => {
        let mut manager = Self::new(&[]);
        manager.apply_operation_request_derives();
        manager
      }
      StructKind::RequestBody => {
        let mut manager = Self::new(&[]);
        manager.apply_request_body_derives(usage);
        manager
      }
      StructKind::Schema => {
        let mut manager = Self::new(def_derives);
        manager.apply_schema_derives(usage);
        manager
      }
    }
  }

  pub fn for_enum(def_derives: &[String]) -> Self {
    Self::new(def_derives)
  }

  pub fn to_token_stream(&self) -> TokenStream {
    let derives_vec: Vec<_> = self.derives.iter().cloned().collect();
    attributes::generate_derives_from_slice(&derives_vec)
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_derive_manager_operation_request() {
    let manager = DeriveManager::for_struct(&[], StructKind::OperationRequest, None);
    let tokens = manager.to_token_stream().to_string();

    assert!(tokens.contains("Debug"));
    assert!(tokens.contains("Clone"));
    assert!(tokens.contains("validator :: Validate"));
    assert!(tokens.contains("oas3_gen_support :: Default"));
    assert!(!tokens.contains("Serialize"));
    assert!(!tokens.contains("Deserialize"));
  }

  #[test]
  fn test_derive_manager_request_body_request_only() {
    let manager = DeriveManager::for_struct(&[], StructKind::RequestBody, Some(&TypeUsage::RequestOnly));
    let tokens = manager.to_token_stream().to_string();

    assert!(tokens.contains("Debug"));
    assert!(tokens.contains("Clone"));
    assert!(tokens.contains("Serialize"));
    assert!(tokens.contains("validator :: Validate"));
    assert!(tokens.contains("oas3_gen_support :: Default"));
    assert!(!tokens.contains("Deserialize"));
  }

  #[test]
  fn test_derive_manager_request_body_response_only() {
    let manager = DeriveManager::for_struct(&[], StructKind::RequestBody, Some(&TypeUsage::ResponseOnly));
    let tokens = manager.to_token_stream().to_string();

    assert!(tokens.contains("Debug"));
    assert!(tokens.contains("Clone"));
    assert!(tokens.contains("Deserialize"));
    assert!(tokens.contains("oas3_gen_support :: Default"));
    assert!(!tokens.contains("Serialize"));
    assert!(!tokens.contains("validator :: Validate"));
  }

  #[test]
  fn test_derive_manager_request_body_bidirectional() {
    let manager = DeriveManager::for_struct(&[], StructKind::RequestBody, Some(&TypeUsage::Bidirectional));
    let tokens = manager.to_token_stream().to_string();

    assert!(tokens.contains("Debug"));
    assert!(tokens.contains("Clone"));
    assert!(tokens.contains("Serialize"));
    assert!(tokens.contains("Deserialize"));
    assert!(tokens.contains("validator :: Validate"));
    assert!(tokens.contains("oas3_gen_support :: Default"));
  }

  #[test]
  fn test_derive_manager_schema_request_only() {
    let initial = vec!["Debug".to_string(), "Clone".to_string()];
    let manager = DeriveManager::for_struct(&initial, StructKind::Schema, Some(&TypeUsage::RequestOnly));
    let tokens = manager.to_token_stream().to_string();

    assert!(tokens.contains("Debug"));
    assert!(tokens.contains("Clone"));
    assert!(tokens.contains("Serialize"));
    assert!(tokens.contains("validator :: Validate"));
    assert!(!tokens.contains("Deserialize"));
  }

  #[test]
  fn test_derive_manager_schema_response_only() {
    let initial = vec!["Debug".to_string(), "Clone".to_string()];
    let manager = DeriveManager::for_struct(&initial, StructKind::Schema, Some(&TypeUsage::ResponseOnly));
    let tokens = manager.to_token_stream().to_string();

    assert!(tokens.contains("Debug"));
    assert!(tokens.contains("Clone"));
    assert!(tokens.contains("Deserialize"));
    assert!(!tokens.contains("Serialize"));
  }

  #[test]
  fn test_derive_manager_enum() {
    let initial = vec![
      "Debug".to_string(),
      "Clone".to_string(),
      "Serialize".to_string(),
      "Deserialize".to_string(),
    ];
    let manager = DeriveManager::for_enum(&initial);
    let tokens = manager.to_token_stream().to_string();

    assert!(tokens.contains("Debug"));
    assert!(tokens.contains("Clone"));
    assert!(tokens.contains("Serialize"));
    assert!(tokens.contains("Deserialize"));
    assert_eq!(manager.derives.len(), 4);
  }

  #[test]
  fn test_derive_manager_no_duplicates() {
    let initial = vec!["Debug".to_string(), "Clone".to_string()];
    let mut manager = DeriveManager::new(&initial);
    manager.add(DEBUG);
    manager.add(CLONE);
    manager.add(SERIALIZE);

    assert_eq!(
      manager.derives.len(),
      3,
      "Should not add duplicates and should contain the new derive"
    );
    assert!(manager.derives.contains(SERIALIZE));
  }

  #[test]
  fn test_deterministic_ordering() {
    let mut manager1 = DeriveManager::new(&[]);
    manager1.add(VALIDATE).add(DEBUG).add(SERIALIZE).add(CLONE);

    let mut manager2 = DeriveManager::new(&[]);
    manager2.add(CLONE).add(SERIALIZE).add(DEBUG).add(VALIDATE);

    let tokens1 = manager1.to_token_stream().to_string();
    let tokens2 = manager2.to_token_stream().to_string();

    assert_eq!(
      tokens1, tokens2,
      "Derive order should be deterministic regardless of insertion order"
    );
  }
}