use quote::ToTokens;
use crate::generator::{
ast::{
DiscriminatedEnumDef, DiscriminatedVariant, Documentation, EnumDef, EnumMethod, EnumMethodKind, EnumToken,
EnumVariantToken, OuterAttr, ResponseEnumDef, ResponseMediaType, ResponseVariant, RustPrimitive, SerdeAttribute,
SerdeMode, StatusCodeToken, StructToken, TypeRef, VariantContent, VariantDef,
},
codegen::{
Visibility,
enums::{DiscriminatedEnumFragment, EnumFragment, ResponseEnumFragment},
},
converter::GenerationTarget,
naming::constants::{KNOWN_ENUM_VARIANT, OTHER_ENUM_VARIANT},
};
fn make_unit_variant(name: &str) -> VariantDef {
VariantDef::builder()
.name(EnumVariantToken::from(name))
.content(VariantContent::Unit)
.build()
}
fn make_simple_enum(name: &str, variants: Vec<VariantDef>) -> EnumDef {
EnumDef {
name: EnumToken::new(name),
variants,
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![],
generate_display: true,
..Default::default()
}
}
#[test]
fn test_basic_enum_generation() {
let def = make_simple_enum(
"Color",
vec![
make_unit_variant("Red"),
make_unit_variant("Green"),
make_unit_variant("Blue"),
],
);
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
let assertions = [
("pub enum Color", "should have pub enum declaration"),
("Red", "should have Red variant"),
("Green", "should have Green variant"),
("Blue", "should have Blue variant"),
("# [default]", "first variant should have #[default] attribute"),
("Debug", "should derive Debug"),
("Clone", "should derive Clone"),
("Serialize", "should derive Serialize"),
("Deserialize", "should derive Deserialize"),
];
for (expected, msg) in assertions {
assert!(code.contains(expected), "{msg}");
}
}
#[test]
fn test_simple_enum_display_impl() {
let simple_def = make_simple_enum(
"Color",
vec![
make_unit_variant("Red"),
make_unit_variant("Green"),
make_unit_variant("Blue"),
],
);
let code = EnumFragment::new(simple_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
code.contains("impl core :: fmt :: Display for Color"),
"should have Display impl for simple enum"
);
assert!(
code.contains("Self :: Red => write ! (f , \"Red\")"),
"should output variant name for Red"
);
assert!(
code.contains("Self :: Green => write ! (f , \"Green\")"),
"should output variant name for Green"
);
assert!(
code.contains("Self :: Blue => write ! (f , \"Blue\")"),
"should output variant name for Blue"
);
}
#[test]
fn test_simple_enum_display_impl_with_serde_rename() {
let renamed_def = EnumDef {
name: EnumToken::new("Status"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("InProgress"))
.content(VariantContent::Unit)
.serde_attrs(vec![SerdeAttribute::Rename("in_progress".to_string())])
.build(),
VariantDef::builder()
.name(EnumVariantToken::new("Completed"))
.content(VariantContent::Unit)
.serde_attrs(vec![SerdeAttribute::Rename("completed".to_string())])
.build(),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![],
generate_display: true,
..Default::default()
};
let code = EnumFragment::new(renamed_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
code.contains("impl core :: fmt :: Display for Status"),
"should have Display impl"
);
assert!(
code.contains("Self :: InProgress => write ! (f , \"in_progress\")"),
"should output serde rename value for InProgress"
);
assert!(
code.contains("Self :: Completed => write ! (f , \"completed\")"),
"should output serde rename value for Completed"
);
}
#[test]
fn test_tuple_enum_no_display_impl() {
let tuple_def = EnumDef {
name: EnumToken::new("Value"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("Text"))
.content(VariantContent::Tuple(vec![TypeRef::new(RustPrimitive::String)]))
.build(),
VariantDef::builder()
.name(EnumVariantToken::new("Number"))
.content(VariantContent::Tuple(vec![TypeRef::new(RustPrimitive::I64)]))
.build(),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![],
..Default::default()
};
let code = EnumFragment::new(tuple_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
!code.contains("impl core :: fmt :: Display for Value"),
"tuple enum should NOT have Display impl"
);
}
#[test]
fn test_enum_with_docs() {
let def = EnumDef {
name: EnumToken::new("Status"),
docs: Documentation::from_lines(["Represents the status of an item.", "Can be active or inactive."]),
variants: vec![make_unit_variant("Active"), make_unit_variant("Inactive")],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![],
..Default::default()
};
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
code.contains("# [doc = \" Represents the status of an item.\"]"),
"should have first doc line"
);
assert!(
code.contains("# [doc = \" Can be active or inactive.\"]"),
"should have second doc line"
);
}
#[test]
fn test_enum_tuple_variants() {
let cases = [
(
"single type tuple",
vec![
VariantDef::builder()
.name(EnumVariantToken::new("Text"))
.content(VariantContent::Tuple(vec![TypeRef::new(RustPrimitive::String)]))
.build(),
VariantDef::builder()
.name(EnumVariantToken::new("Number"))
.content(VariantContent::Tuple(vec![TypeRef::new(RustPrimitive::I64)]))
.build(),
make_unit_variant("Null"),
],
vec![
("Text (String)", "Text(String) variant"),
("Number (i64)", "Number(i64) variant"),
("Null", "Null unit variant"),
],
),
(
"multiple type tuple",
vec![
VariantDef::builder()
.name(EnumVariantToken::new("KeyValue"))
.content(VariantContent::Tuple(vec![
TypeRef::new(RustPrimitive::String),
TypeRef::new(RustPrimitive::I32),
]))
.build(),
],
vec![("KeyValue (String , i32)", "multi-type tuple variant")],
),
];
for (case_name, variants, expected_content) in cases {
let def = make_simple_enum("Value", variants);
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
for (expected, msg) in expected_content {
assert!(code.contains(expected), "{case_name}: should have {msg}");
}
}
}
#[test]
fn test_enum_variant_attributes() {
let deprecated_def = EnumDef {
name: EnumToken::new("ApiVersion"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("V1"))
.content(VariantContent::Unit)
.deprecated(true)
.build(),
make_unit_variant("V2"),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![],
..Default::default()
};
let deprecated_code = EnumFragment::new(deprecated_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
deprecated_code.contains("# [deprecated]"),
"should have deprecated attribute"
);
let outer_attrs_def = EnumDef {
name: EnumToken::new("Flagged"),
variants: vec![make_unit_variant("Yes"), make_unit_variant("No")],
serde_attrs: vec![],
outer_attrs: vec![OuterAttr::SkipSerializingNone],
case_insensitive: false,
methods: vec![],
..Default::default()
};
let outer_attrs_code = EnumFragment::new(outer_attrs_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
outer_attrs_code.contains("# [serde_with :: skip_serializing_none]"),
"should have skip_serializing_none attribute"
);
}
#[test]
fn test_enum_serde_attributes() {
let cases = [
(
"rename",
EnumDef {
name: EnumToken::new("Status"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("InProgress"))
.content(VariantContent::Unit)
.serde_attrs(vec![SerdeAttribute::Rename("in_progress".to_string())])
.build(),
VariantDef::builder()
.name(EnumVariantToken::new("Completed"))
.content(VariantContent::Unit)
.serde_attrs(vec![SerdeAttribute::Rename("completed".to_string())])
.build(),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![],
..Default::default()
},
vec![
("# [serde (rename = \"in_progress\")]", "serde rename for InProgress"),
("# [serde (rename = \"completed\")]", "serde rename for Completed"),
],
),
(
"untagged",
EnumDef {
name: EnumToken::new("AnyValue"),
variants: vec![make_unit_variant("StringVal"), make_unit_variant("NumberVal")],
serde_attrs: vec![SerdeAttribute::Untagged],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![],
..Default::default()
},
vec![("# [serde (untagged)]", "untagged serde attribute")],
),
];
for (case_name, def, expected_attrs) in cases {
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string()
.clone();
for (expected, msg) in expected_attrs {
assert!(code.contains(expected), "{case_name}: should have {msg}");
}
}
}
#[test]
fn test_case_insensitive_enum() {
let base_def = EnumDef {
name: EnumToken::new("Status"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("Active"))
.content(VariantContent::Unit)
.serde_attrs(vec![SerdeAttribute::Rename("active".to_string())])
.build(),
VariantDef::builder()
.name(EnumVariantToken::new("InProgress"))
.content(VariantContent::Unit)
.serde_attrs(vec![SerdeAttribute::Rename("in-progress".to_string())])
.build(),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: true,
methods: vec![],
..Default::default()
};
let code = EnumFragment::new(base_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
let parts: Vec<&str> = code.split("enum Status").collect();
assert_eq!(parts.len(), 2, "should split into derive and impl parts");
let derive_part = parts[0];
let impl_part = parts[1];
assert!(
!derive_part.contains("Deserialize"),
"Deserialize should not be in derive attribute"
);
assert!(
impl_part.contains("impl < 'de > serde :: Deserialize < 'de > for Status"),
"Should implement Deserialize manually"
);
assert!(
impl_part.contains("\"active\" => Ok (Status :: Active)"),
"should match active"
);
assert!(
impl_part.contains("\"in-progress\" => Ok (Status :: InProgress)"),
"should match in-progress"
);
let fallback_def = EnumDef {
name: EnumToken::new("Priority"),
variants: vec![
make_unit_variant("High"),
make_unit_variant("Medium"),
make_unit_variant("Low"),
make_unit_variant("Unknown"),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: true,
methods: vec![],
..Default::default()
};
let fallback_code = EnumFragment::new(fallback_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
fallback_code.contains("_ => Ok (Priority :: Unknown)"),
"should fallback to Unknown variant for unrecognized values"
);
}
#[test]
fn test_case_insensitive_enum_deserialize_only() {
let def = EnumDef {
name: EnumToken::new("Status"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("Active"))
.content(VariantContent::Unit)
.serde_attrs(vec![SerdeAttribute::Rename("active".to_string())])
.build(),
VariantDef::builder()
.name(EnumVariantToken::new("Inactive"))
.content(VariantContent::Unit)
.serde_attrs(vec![SerdeAttribute::Rename("inactive".to_string())])
.build(),
],
case_insensitive: true,
serde_mode: SerdeMode::DeserializeOnly,
..Default::default()
};
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
let parts: Vec<&str> = code.split("pub enum Status").collect();
assert_eq!(parts.len(), 2, "should split into derives and enum parts");
let derive_part = parts[0];
assert!(
!derive_part.contains("Serialize"),
"should NOT derive Serialize when DeserializeOnly"
);
assert!(
!derive_part.contains("Deserialize"),
"should NOT derive Deserialize (custom impl used for case-insensitive)"
);
assert!(
!code.contains("# [serde (rename"),
"should NOT have serde rename attrs when no serde derives"
);
assert!(
code.contains("impl < 'de > serde :: Deserialize < 'de > for Status"),
"should have custom Deserialize impl"
);
assert!(
code.contains("\"active\" => Ok (Status :: Active)"),
"custom deserialize should match 'active'"
);
}
#[test]
fn test_enum_visibility() {
let cases = [
(
Visibility::Crate,
"pub (crate) enum Internal",
true,
"pub(crate) visibility",
),
(
Visibility::File,
"pub enum",
false,
"no pub visibility for file-private",
),
(
Visibility::File,
"pub (crate)",
false,
"no pub(crate) visibility for file-private",
),
(Visibility::File, "enum Private", true, "private visibility"),
];
for (visibility, pattern, should_contain, msg) in cases {
let name = match visibility {
Visibility::Crate => "Internal",
Visibility::File => "Private",
Visibility::Public => "Public",
};
let def = make_simple_enum(name, vec![make_unit_variant("A"), make_unit_variant("B")]);
let code = EnumFragment::new(def, visibility, GenerationTarget::Client)
.into_token_stream()
.to_string();
if should_contain {
assert!(code.contains(pattern), "should have {msg}");
} else {
assert!(!code.contains(pattern), "should not have {msg}");
}
}
}
#[test]
fn test_enum_constructor_methods() {
let simple_def = EnumDef {
name: EnumToken::new("RequestBody"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("Json"))
.content(VariantContent::Tuple(vec![TypeRef::new(RustPrimitive::Custom(
"JsonPayload".into(),
))]))
.build(),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![EnumMethod::new(
"json",
EnumMethodKind::SimpleConstructor {
variant_name: "Json".into(),
wrapped_type: TypeRef::new("JsonPayload"),
},
Documentation::from_lines(["Creates an empty JSON body."]),
)],
..Default::default()
};
let simple_code = EnumFragment::new(simple_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(simple_code.contains("impl RequestBody"), "should have impl block");
assert!(
simple_code.contains("pub fn json () -> Self"),
"should have json constructor"
);
assert!(
simple_code.contains("Self :: Json (JsonPayload :: default ())"),
"should construct with default"
);
let param_def = EnumDef {
name: EnumToken::new("Request"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("Create"))
.content(VariantContent::Tuple(vec![TypeRef::new(RustPrimitive::Custom(
"CreateParams".into(),
))]))
.build(),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![EnumMethod::new(
"with_name",
EnumMethodKind::ParameterizedConstructor {
variant_name: "Create".into(),
wrapped_type: TypeRef::new("CreateParams"),
param_name: "name".to_string(),
param_type: TypeRef::new("String"),
},
Documentation::from_lines(["Creates a request with the given name."]),
)],
..Default::default()
};
let param_code = EnumFragment::new(param_def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
param_code.contains("pub fn with_name (name : String) -> Self"),
"should have parameterized constructor"
);
assert!(
param_code.contains("Self :: Create (CreateParams { name , .. Default :: default () })"),
"should construct with parameter"
);
}
#[test]
fn test_enum_constructor_methods_without_docs() {
let def = EnumDef {
name: EnumToken::new("RequestBody"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("Json"))
.content(VariantContent::Tuple(vec![TypeRef::new(RustPrimitive::Custom(
"JsonPayload".into(),
))]))
.build(),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![EnumMethod::new(
"json",
EnumMethodKind::SimpleConstructor {
variant_name: "Json".into(),
wrapped_type: TypeRef::new("JsonPayload"),
},
Documentation::default(),
)],
..Default::default()
};
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(code.contains("pub fn json () -> Self"), "should have json constructor");
assert!(
!code.contains("Creates a `"),
"should not emit generalized constructor docs"
);
assert!(
!code.contains("Convenience helper"),
"should not emit generalized helper docs"
);
}
#[test]
fn test_known_value_constructor_methods() {
let def = EnumDef {
name: EnumToken::new("ModelOption"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new(KNOWN_ENUM_VARIANT))
.content(VariantContent::Tuple(vec![TypeRef::new("ModelOptionKnown")]))
.build(),
VariantDef::builder()
.name(EnumVariantToken::new(OTHER_ENUM_VARIANT))
.content(VariantContent::Tuple(vec![TypeRef::new("String")]))
.build(),
],
serde_attrs: vec![],
outer_attrs: vec![],
case_insensitive: false,
methods: vec![
EnumMethod::new(
"gemini_25_pro",
EnumMethodKind::KnownValueConstructor {
known_type: EnumToken::new("ModelOptionKnown"),
known_variant: EnumVariantToken::new("Gemini25Pro"),
},
Documentation::from_lines(["Our best model."]),
),
EnumMethod::new(
"gemini_25_flash",
EnumMethodKind::KnownValueConstructor {
known_type: EnumToken::new("ModelOptionKnown"),
known_variant: EnumVariantToken::new("Gemini25Flash"),
},
Documentation::default(),
),
],
..Default::default()
};
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
code.contains("pub fn gemini_25_pro () -> Self"),
"should have gemini_25_pro constructor"
);
assert!(
code.contains("Self :: Known (ModelOptionKnown :: Gemini25Pro)"),
"should construct Known variant with inner enum value"
);
assert!(
code.contains("pub fn gemini_25_flash () -> Self"),
"should have gemini_25_flash constructor"
);
assert!(code.contains("Our best model"), "should include docs on method");
}
#[test]
fn test_discriminated_enum() {
let without_fallback = DiscriminatedEnumDef::builder()
.name("Pet".into())
.docs(Documentation::from_lines(["A pet can be a dog or cat."]))
.discriminator_field("petType".to_string())
.variants(vec![
DiscriminatedVariant::builder()
.discriminator_values(vec!["dog".to_string()])
.variant_name(EnumVariantToken::new("Dog"))
.type_name(TypeRef::new("DogData"))
.build(),
DiscriminatedVariant::builder()
.discriminator_values(vec!["cat".to_string()])
.variant_name(EnumVariantToken::new("Cat"))
.type_name(TypeRef::new("CatData"))
.build(),
])
.serde_mode(SerdeMode::Both)
.build();
let code = DiscriminatedEnumFragment::new(without_fallback, Visibility::Public)
.into_token_stream()
.to_string();
let assertions_without = [
(
"# [derive (Debug , Clone , PartialEq)]",
"should derive Debug, Clone, PartialEq",
),
("pub enum Pet", "should have pub enum declaration"),
("Dog (DogData)", "should have dog variant"),
("Cat (CatData)", "should have cat variant"),
(
"pub const DISCRIMINATOR_FIELD",
"should have discriminator field constant",
),
("\"petType\"", "should have discriminator field value"),
("impl Default for Pet", "should have Default impl"),
("impl serde :: Serialize for Pet", "should have Serialize impl"),
(
"impl < 'de > serde :: Deserialize < 'de > for Pet",
"should have Deserialize impl",
),
("Some (\"dog\")", "should have dog discriminator match"),
("Some (\"cat\")", "should have cat discriminator match"),
(
"missing_field (Self :: DISCRIMINATOR_FIELD)",
"should error on missing discriminator",
),
];
for (expected, msg) in assertions_without {
assert!(code.contains(expected), "{msg}:\n{code}");
}
let with_fallback = DiscriminatedEnumDef::builder()
.name("Message".into())
.discriminator_field("type".to_string())
.variants(vec![
DiscriminatedVariant::builder()
.discriminator_values(vec!["text".to_string()])
.variant_name(EnumVariantToken::new("Text"))
.type_name(TypeRef::new("TextMessage"))
.build(),
])
.fallback(
DiscriminatedVariant::builder()
.discriminator_values(vec![])
.variant_name(EnumVariantToken::new("Unknown"))
.type_name(TypeRef::new("serde_json::Value"))
.build(),
)
.serde_mode(SerdeMode::Both)
.build();
let code_with = DiscriminatedEnumFragment::new(with_fallback, Visibility::Public)
.into_token_stream()
.to_string();
let fallback_assertions = [
("Unknown (serde_json :: Value)", "should have fallback variant in enum"),
(
"Self :: Unknown (< serde_json :: Value >",
"should use fallback in Default impl",
),
(
"None => serde_json :: from_value (value) . map (Self :: Unknown)",
"should use fallback when discriminator missing",
),
];
for (expected, msg) in fallback_assertions {
assert!(code_with.contains(expected), "{msg}:\n{code_with}");
}
assert!(
!code_with.contains("missing_field"),
"should not error on missing field when fallback exists"
);
}
#[test]
fn test_discriminated_enum_serialize_only() {
let def = DiscriminatedEnumDef::builder()
.name("RequestType".into())
.discriminator_field("kind".to_string())
.variants(vec![
DiscriminatedVariant::builder()
.discriminator_values(vec!["create".to_string()])
.variant_name(EnumVariantToken::new("Create"))
.type_name(TypeRef::new("CreateRequest"))
.build(),
])
.serde_mode(SerdeMode::SerializeOnly)
.build();
let code = DiscriminatedEnumFragment::new(def, Visibility::Public)
.into_token_stream()
.to_string();
assert!(
code.contains("impl serde :: Serialize for RequestType"),
"should have Serialize impl"
);
assert!(
!code.contains("impl < 'de > serde :: Deserialize"),
"should NOT have Deserialize impl"
);
}
#[test]
fn test_discriminated_enum_deserialize_only() {
let def = DiscriminatedEnumDef::builder()
.name("ResponseType".into())
.discriminator_field("kind".to_string())
.variants(vec![
DiscriminatedVariant::builder()
.discriminator_values(vec!["success".to_string()])
.variant_name(EnumVariantToken::new("Success"))
.type_name(TypeRef::new("SuccessResponse"))
.build(),
])
.maybe_fallback(None)
.serde_mode(SerdeMode::DeserializeOnly)
.build();
let code = DiscriminatedEnumFragment::new(def, Visibility::Public)
.into_token_stream()
.to_string();
assert!(
!code.contains("impl serde :: Serialize"),
"should NOT have Serialize impl"
);
assert!(
code.contains("impl < 'de > serde :: Deserialize < 'de > for ResponseType"),
"should have Deserialize impl"
);
}
#[test]
fn test_response_enum_generation() {
let def = ResponseEnumDef {
name: EnumToken::new("GetUserResponse"),
docs: Documentation::from_lines(["Response for GET /users/{id}"]),
variants: vec![
ResponseVariant::builder()
.status_code(StatusCodeToken::Ok200)
.variant_name(EnumVariantToken::new("Ok"))
.description("User found".to_string())
.media_types(vec![ResponseMediaType::with_schema(
"application/json",
Some(TypeRef::new(RustPrimitive::Custom("User".into()))),
)])
.schema_type(TypeRef::new(RustPrimitive::Custom("User".into())))
.build(),
ResponseVariant::builder()
.status_code(StatusCodeToken::NotFound404)
.variant_name(EnumVariantToken::new("NotFound"))
.description("User not found".to_string())
.media_types(vec![ResponseMediaType::new("application/json")])
.build(),
ResponseVariant::builder()
.status_code(StatusCodeToken::InternalServerError500)
.variant_name(EnumVariantToken::new("InternalServerError"))
.media_types(vec![ResponseMediaType::with_schema(
"application/json",
Some(TypeRef::new(RustPrimitive::Custom("ErrorResponse".into()))),
)])
.schema_type(TypeRef::new(RustPrimitive::Custom("ErrorResponse".into())))
.build(),
],
request_type: Some(StructToken::new("GetUserRequest")),
try_from: vec![],
};
let code = ResponseEnumFragment::new(Visibility::Public, def)
.into_token_stream()
.to_string();
let assertions = [
("pub enum GetUserResponse", "should have pub enum declaration"),
("# [derive (Debug , Clone)]", "should derive Debug and Clone"),
("Ok (User)", "should have Ok variant with User type"),
("NotFound", "should have NotFound unit variant"),
(
"InternalServerError (ErrorResponse)",
"should have error variant with type",
),
(
"# [doc = \"200: User found\"]",
"should have doc with status and description",
),
("# [doc = \"404: User not found\"]", "should have doc for 404"),
(
"# [doc = \"500\"]",
"should have doc with just status when no description",
),
];
for (expected, msg) in assertions {
assert!(code.contains(expected), "{msg}");
}
}
#[test]
fn test_relaxed_wrapper_enum_generates_display() {
let def = EnumDef {
name: EnumToken::new("CuisineType"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new(KNOWN_ENUM_VARIANT))
.content(VariantContent::Tuple(vec![TypeRef::new("CuisineTypeKnown")]))
.build(),
VariantDef::builder()
.name(EnumVariantToken::new(OTHER_ENUM_VARIANT))
.content(VariantContent::Tuple(vec![TypeRef::new("String")]))
.build(),
],
generate_display: true,
..Default::default()
};
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
let assertions = [
(
"impl core :: fmt :: Display for CuisineType",
"should generate Display impl for relaxed wrapper enum",
),
(
"Self :: Known (v) => write ! (f , \"{v}\")",
"should delegate Known variant to inner Display",
),
(
"Self :: Other (v) => write ! (f , \"{v}\")",
"should delegate Other variant to inner Display",
),
];
for (expected, msg) in assertions {
assert!(code.contains(expected), "{msg}\nGenerated code:\n{code}");
}
}
#[test]
fn test_non_simple_enum_without_generate_display_has_no_display() {
let def = EnumDef {
name: EnumToken::new("StringOrNumber"),
variants: vec![
VariantDef::builder()
.name(EnumVariantToken::new("StringVal"))
.content(VariantContent::Tuple(vec![TypeRef::new("String")]))
.build(),
VariantDef::builder()
.name(EnumVariantToken::new("NumberVal"))
.content(VariantContent::Tuple(vec![TypeRef::new("f64")]))
.build(),
],
generate_display: false,
..Default::default()
};
let code = EnumFragment::new(def, Visibility::Public, GenerationTarget::Client)
.into_token_stream()
.to_string();
assert!(
!code.contains("impl core :: fmt :: Display"),
"should NOT generate Display impl for non-simple enum without generate_display flag"
);
}