oas3-gen 0.24.0

A rust type generator for OpenAPI v3.1.x specification.
use oas3::spec::{ObjectSchema, SchemaType, SchemaTypeSet};
use serde_json::json;

use crate::generator::{
  ast::{Documentation, RustPrimitive, TypeRef, ValidationAttribute},
  converter::{SchemaExt, fields::FieldConverter},
};

fn create_string_schema() -> ObjectSchema {
  ObjectSchema {
    schema_type: Some(SchemaTypeSet::Single(SchemaType::String)),
    ..Default::default()
  }
}

fn create_number_schema() -> ObjectSchema {
  ObjectSchema {
    schema_type: Some(SchemaTypeSet::Single(SchemaType::Number)),
    ..Default::default()
  }
}

fn create_integer_schema() -> ObjectSchema {
  ObjectSchema {
    schema_type: Some(SchemaTypeSet::Single(SchemaType::Integer)),
    ..Default::default()
  }
}

fn create_array_schema() -> ObjectSchema {
  ObjectSchema {
    schema_type: Some(SchemaTypeSet::Single(SchemaType::Array)),
    ..Default::default()
  }
}

#[test]
fn test_is_single_type() {
  let string_schema = create_string_schema();
  assert!(string_schema.is_single_type(SchemaType::String));
  assert!(!string_schema.is_single_type(SchemaType::Number));

  let number_schema = create_number_schema();
  assert!(number_schema.is_single_type(SchemaType::Number));
  assert!(!number_schema.is_single_type(SchemaType::String));
}

#[test]
fn test_extract_docs() {
  let schema = ObjectSchema::default();
  let docs = {
    let schema: &ObjectSchema = &schema;
    Documentation::from_optional(schema.description.as_ref())
  };
  assert!(docs.is_empty());

  let schema = ObjectSchema {
    description: Some("Test description\nSecond line".to_string()),
    ..Default::default()
  };
  let docs = {
    let schema: &ObjectSchema = &schema;
    Documentation::from_optional(schema.description.as_ref())
  };
  assert_eq!(docs.lines().len(), 2);
  assert_eq!(docs.lines()[0], "Test description");
  assert_eq!(docs.lines()[1], "Second line");
}

#[test]
fn test_extract_default_value() {
  let schema = ObjectSchema {
    default: Some(json!("default_value")),
    ..Default::default()
  };
  assert_eq!(
    FieldConverter::extract_default_value(&schema),
    Some(json!("default_value"))
  );

  let schema = ObjectSchema {
    const_value: Some(json!("const_value")),
    ..Default::default()
  };
  assert_eq!(
    FieldConverter::extract_default_value(&schema),
    Some(json!("const_value"))
  );

  let schema = ObjectSchema {
    enum_values: vec![json!("only_value")],
    ..Default::default()
  };
  assert_eq!(
    FieldConverter::extract_default_value(&schema),
    Some(json!("only_value"))
  );

  let schema = ObjectSchema {
    default: Some(json!("default")),
    const_value: Some(json!("const")),
    enum_values: vec![json!("enum")],
    ..Default::default()
  };
  assert_eq!(FieldConverter::extract_default_value(&schema), Some(json!("default")));

  let schema = ObjectSchema::default();
  assert_eq!(FieldConverter::extract_default_value(&schema), None);
}

#[test]
fn test_extract_all_validation_combined() {
  let mut schema = create_string_schema();
  schema.description = Some("Test field".to_string());
  schema.format = Some("email".to_string());

  let type_ref = TypeRef::new(RustPrimitive::String);

  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  let docs = {
    let schema: &ObjectSchema = &schema;
    Documentation::from_optional(schema.description.as_ref())
  };
  let default_value = FieldConverter::extract_default_value(&schema);

  assert!(!docs.is_empty());
  assert!(attrs.contains(&ValidationAttribute::Email));
  assert!(default_value.is_none());
}

#[test]
fn test_extract_parameter_metadata() {
  let mut schema = create_string_schema();
  schema.min_length = Some(5);
  schema.max_length = Some(50);
  schema.default = Some(json!("default_value"));

  let type_ref = TypeRef::new(RustPrimitive::String);

  let (validation_attrs, default_value) = FieldConverter::extract_parameter_metadata("param", true, &schema, &type_ref);

  assert_eq!(
    validation_attrs,
    vec![ValidationAttribute::Length {
      min: Some(5),
      max: Some(50)
    }]
  );
  assert_eq!(default_value, Some(json!("default_value")));
}

#[test]
fn test_extract_all_validation_email_format() {
  let mut schema = create_string_schema();
  schema.format = Some("email".to_string());
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(attrs.contains(&ValidationAttribute::Email));
}

#[test]
fn test_extract_all_validation_url_format() {
  let mut schema = create_string_schema();
  schema.format = Some("url".to_string());
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(attrs.contains(&ValidationAttribute::Url));

  schema.format = Some("uri".to_string());
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(attrs.contains(&ValidationAttribute::Url));
}

#[test]
fn test_extract_all_validation_integer_range() {
  let mut schema = create_integer_schema();
  schema.minimum = Some(json!(0).as_number().unwrap().clone());
  schema.maximum = Some(json!(100).as_number().unwrap().clone());
  let type_ref = TypeRef::new(RustPrimitive::I32);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert_eq!(attrs.len(), 1);
  assert_eq!(
    attrs[0],
    ValidationAttribute::Range {
      primitive: RustPrimitive::I32,
      min: Some(json!(0).as_number().unwrap().clone()),
      max: Some(json!(100).as_number().unwrap().clone()),
      exclusive_min: None,
      exclusive_max: None,
    }
  );
}

#[test]
fn test_extract_all_validation_exclusive_range() {
  let mut schema = create_number_schema();
  schema.exclusive_minimum = Some(json!(0).as_number().unwrap().clone());
  schema.exclusive_maximum = Some(json!(100).as_number().unwrap().clone());
  let type_ref = TypeRef::new(RustPrimitive::I32);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert_eq!(attrs.len(), 1);
  assert_eq!(
    attrs[0],
    ValidationAttribute::Range {
      primitive: RustPrimitive::I32,
      min: None,
      max: None,
      exclusive_min: Some(json!(0).as_number().unwrap().clone()),
      exclusive_max: Some(json!(100).as_number().unwrap().clone()),
    }
  );
}

#[test]
fn test_extract_all_validation_no_range_when_empty() {
  let schema = create_number_schema();
  let type_ref = TypeRef::new(RustPrimitive::I32);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(attrs.is_empty());
}

#[test]
fn test_extract_all_validation_string_length() {
  let mut schema = create_string_schema();
  schema.min_length = Some(1);
  schema.max_length = Some(100);
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert_eq!(
    attrs,
    vec![ValidationAttribute::Length {
      min: Some(1),
      max: Some(100)
    }]
  );
}

#[test]
fn test_extract_all_validation_string_length_min_only() {
  let mut schema = create_string_schema();
  schema.min_length = Some(5);
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert_eq!(
    attrs,
    vec![ValidationAttribute::Length {
      min: Some(5),
      max: None
    }]
  );
}

#[test]
fn test_extract_all_validation_string_length_max_only() {
  let mut schema = create_string_schema();
  schema.max_length = Some(20);
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert_eq!(
    attrs,
    vec![ValidationAttribute::Length {
      min: None,
      max: Some(20)
    }]
  );
}

#[test]
fn test_extract_all_validation_string_length_skipped_for_non_string_format() {
  let mut schema = create_string_schema();
  schema.min_length = Some(1);
  schema.format = Some("date".to_string());
  let type_ref = TypeRef::new(RustPrimitive::Date);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(attrs.is_empty());
}

#[test]
fn test_extract_all_validation_required_implies_min_length() {
  let schema = create_string_schema();
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", true, &schema, &type_ref);
  assert_eq!(
    attrs,
    vec![ValidationAttribute::Length {
      min: Some(1),
      max: None
    }]
  );
}

#[test]
fn test_extract_all_validation_regex() {
  let mut schema = create_string_schema();
  schema.pattern = Some("^[a-z]+$".to_string());
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(attrs.contains(&ValidationAttribute::Regex("^[a-z]+$".to_string())));
}

#[test]
fn test_extract_all_validation_regex_skipped_for_enums() {
  let mut schema = create_string_schema();
  schema.pattern = Some("^[a-z]+$".to_string());
  schema.enum_values = vec![json!("value1"), json!("value2")];
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(attrs.is_empty());
}

#[test]
fn test_extract_all_validation_regex_skipped_for_datetime() {
  let mut schema = create_string_schema();
  schema.pattern = Some("^[0-9]+$".to_string());
  let type_ref = TypeRef::new(RustPrimitive::DateTime);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(!attrs.iter().any(|a| matches!(a, ValidationAttribute::Regex(_))));
}

#[test]
fn test_extract_all_validation_regex_skipped_for_date() {
  let mut schema = create_string_schema();
  schema.pattern = Some("pattern".to_string());
  let type_ref = TypeRef::new(RustPrimitive::Date);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(!attrs.iter().any(|a| matches!(a, ValidationAttribute::Regex(_))));
}

#[test]
fn test_extract_all_validation_regex_skipped_for_uuid() {
  let mut schema = create_string_schema();
  schema.pattern = Some("pattern".to_string());
  let type_ref = TypeRef::new(RustPrimitive::Uuid);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(!attrs.iter().any(|a| matches!(a, ValidationAttribute::Regex(_))));
}

#[test]
fn test_extract_all_validation_invalid_regex_skipped() {
  let mut schema = create_string_schema();
  schema.pattern = Some("^[a-z+$".to_string());
  let type_ref = TypeRef::new(RustPrimitive::String);
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert!(!attrs.iter().any(|a| matches!(a, ValidationAttribute::Regex(_))));
}

#[test]
fn test_extract_all_validation_array_length() {
  let mut schema = create_array_schema();
  schema.min_items = Some(1);
  schema.max_items = Some(10);
  let type_ref = TypeRef::new(RustPrimitive::String).with_vec();
  let attrs = FieldConverter::extract_all_validation("test", false, &schema, &type_ref);
  assert_eq!(
    attrs,
    vec![ValidationAttribute::Length {
      min: Some(1),
      max: Some(10)
    }]
  );
}