feat: handle duplicate enum cases

This commit is contained in:
pyrite 2025-12-01 18:29:13 +01:00
parent 12cfcf7f11
commit 2e60e0a24f
Signed by: pyrite
GPG key ID: 7F1BA9170CD35D15
10 changed files with 1462 additions and 658 deletions

988
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
[package]
name = "torn-api-codegen"
authors = ["Pyrit [2111649]"]
version = "0.7.5"
version = "0.8.0"
edition = "2021"
description = "Contains the v2 torn API model descriptions and codegen for the bindings"
license = { workspace = true }

View file

@ -1,11 +1,16 @@
use std::collections::HashSet;
use heck::{ToSnakeCase, ToUpperCamelCase};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;
use crate::openapi::{
use crate::{
model::WarningReporter,
openapi::{
parameter::OpenApiParameterSchema,
r#type::{OpenApiType, OpenApiVariants},
},
};
use super::{object::PrimitiveType, Model, ResolvedSchema};
@ -25,7 +30,11 @@ pub enum EnumVariantTupleValue {
}
impl EnumVariantTupleValue {
pub fn from_schema(name: &str, schema: &OpenApiType) -> Option<Self> {
pub fn from_schema(
name: &str,
schema: &OpenApiType,
warnings: WarningReporter,
) -> Option<Self> {
match schema {
OpenApiType {
ref_path: Some(path),
@ -63,7 +72,7 @@ impl EnumVariantTupleValue {
} => {
let name = format!("{name}Variant");
Some(Self::Enum {
inner: Enum::from_schema(&name, schema)?,
inner: Enum::from_schema(&name, schema, warnings.child(name.clone()))?,
name,
})
}
@ -353,7 +362,11 @@ pub struct Enum {
}
impl Enum {
pub fn from_schema(name: &str, schema: &OpenApiType) -> Option<Self> {
pub fn from_schema(
name: &str,
schema: &OpenApiType,
warnings: WarningReporter,
) -> Option<Self> {
let mut result = Enum {
name: name.to_owned(),
description: schema.description.as_deref().map(ToOwned::to_owned),
@ -389,6 +402,19 @@ impl Enum {
}
})
.collect();
let mut visited = HashSet::with_capacity(result.variants.len());
let mut new_variants = Vec::with_capacity(result.variants.len());
for variant in result.variants {
if visited.contains(&variant.name) {
warnings.push(format!("Duplicate case `{}`", variant.name));
} else {
visited.insert(variant.name.clone());
new_variants.push(variant);
}
}
result.variants = new_variants;
}
None => return None,
}
@ -417,7 +443,11 @@ impl Enum {
Some(result)
}
pub fn from_one_of(name: &str, schemas: &[OpenApiType]) -> Option<Self> {
pub fn from_one_of(
name: &str,
schemas: &[OpenApiType],
warnings: WarningReporter,
) -> Option<Self> {
let mut result = Self {
name: name.to_owned(),
untagged: true,
@ -425,7 +455,8 @@ impl Enum {
};
for schema in schemas {
let value = EnumVariantTupleValue::from_schema(name, schema)?;
let value =
EnumVariantTupleValue::from_schema(name, schema, warnings.child(name.to_owned()))?;
let name = value.name();
result.variants.push(EnumVariant {

View file

@ -131,9 +131,10 @@ impl ResolvedSchema {
}
for (name, param) in &schema.components.parameters {
result
.parameters
.push(Parameter::from_schema(name, param).unwrap());
result.parameters.push(
Parameter::from_schema(name, param, result.warnings.child(name.to_owned()))
.unwrap(),
);
}
result
@ -193,7 +194,8 @@ pub fn resolve(
match r#type {
OpenApiType {
r#enum: Some(_), ..
} => Enum::from_schema(name, r#type).map_or(Model::Unresolved, Model::Enum),
} => Enum::from_schema(name, r#type, warnings.child(name))
.map_or(Model::Unresolved, Model::Enum),
OpenApiType {
r#type: Some("object"),
..
@ -209,7 +211,8 @@ pub fn resolve(
OpenApiType {
one_of: Some(types),
..
} => Enum::from_one_of(name, types).map_or(Model::Unresolved, Model::Enum),
} => Enum::from_one_of(name, types, warnings.child(name))
.map_or(Model::Unresolved, Model::Enum),
OpenApiType {
all_of: Some(types),
..

View file

@ -114,8 +114,11 @@ impl Property {
OpenApiType {
r#enum: Some(_), ..
} => {
let Some(r#enum) = Enum::from_schema(&name.clone().to_upper_camel_case(), schema)
else {
let Some(r#enum) = Enum::from_schema(
&name.clone().to_upper_camel_case(),
schema,
warnings.clone(),
) else {
warnings.push("Failed to create enum");
return None;
};
@ -154,7 +157,9 @@ impl Property {
Some(inner)
}
cases => {
let Some(r#enum) = Enum::from_one_of(&name.to_upper_camel_case(), cases) else {
let Some(r#enum) =
Enum::from_one_of(&name.to_upper_camel_case(), cases, warnings.clone())
else {
warnings.push("Failed to create oneOf enum");
return None;
};

View file

@ -4,9 +4,12 @@ use heck::ToUpperCamelCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use crate::openapi::parameter::{
use crate::{
model::WarningReporter,
openapi::parameter::{
OpenApiParameter, OpenApiParameterDefault, OpenApiParameterSchema,
ParameterLocation as SchemaLocation,
},
};
use super::{r#enum::Enum, ResolvedSchema};
@ -38,7 +41,11 @@ pub enum ParameterType {
}
impl ParameterType {
pub fn from_schema(name: &str, schema: &OpenApiParameterSchema) -> Option<Self> {
pub fn from_schema(
name: &str,
schema: &OpenApiParameterSchema,
warnings: WarningReporter,
) -> Option<Self> {
match schema {
OpenApiParameterSchema {
r#type: Some("integer"),
@ -97,7 +104,7 @@ impl ParameterType {
minimum: None,
maximum: None,
},
r#type: Enum::from_one_of(name, schemas)?,
r#type: Enum::from_one_of(name, schemas, warnings.child(name.to_owned()))?,
}),
OpenApiParameterSchema {
r#type: Some("string"),
@ -116,7 +123,11 @@ impl ParameterType {
items: Some(items),
..
} => Some(Self::Array {
items: Box::new(Self::from_schema(name, items)?),
items: Box::new(Self::from_schema(
name,
items,
warnings.child(name.to_owned()),
)?),
}),
_ => None,
}
@ -153,7 +164,11 @@ pub struct Parameter {
}
impl Parameter {
pub fn from_schema(name: &str, schema: &OpenApiParameter) -> Option<Self> {
pub fn from_schema(
name: &str,
schema: &OpenApiParameter,
warnings: WarningReporter,
) -> Option<Self> {
let name = match name {
"From" => "FromTimestamp".to_owned(),
"To" => "ToTimestamp".to_owned(),
@ -167,7 +182,7 @@ impl Parameter {
SchemaLocation::Path => ParameterLocation::Path,
};
let r#type = ParameterType::from_schema(&name, &schema.schema)?;
let r#type = ParameterType::from_schema(&name, &schema.schema, warnings)?;
Some(Self {
name,
@ -364,7 +379,7 @@ mod test {
for (name, desc) in &schema.components.parameters {
parameters += 1;
if Parameter::from_schema(name, desc).is_none() {
if Parameter::from_schema(name, desc, WarningReporter::new()).is_none() {
unresolved.push(name);
}
}
@ -394,7 +409,8 @@ mod test {
for param in &body.get.parameters {
if let OpenApiPathParameter::Inline(inline) = param {
params += 1;
if Parameter::from_schema(inline.name, inline).is_none() {
if Parameter::from_schema(inline.name, inline, WarningReporter::new()).is_none()
{
unresolved.push(format!("`{}.{}`", path, inline.name));
}
}
@ -423,7 +439,9 @@ mod test {
for param in &body.get.parameters {
if let OpenApiPathParameter::Inline(inline) = param {
if inline.r#in == SchemaLocation::Query {
let Some(param) = Parameter::from_schema(inline.name, inline) else {
let Some(param) =
Parameter::from_schema(inline.name, inline, WarningReporter::new())
else {
continue;
};
if matches!(

View file

@ -91,12 +91,14 @@ impl Path {
.to_owned();
let param = parameters.get(&name.as_str())?;
params.push(PathParameter::Component(Parameter::from_schema(
&name, param,
&name,
param,
warnings.child(&name),
)?));
}
OpenApiPathParameter::Inline(schema) => {
let name = schema.name.to_upper_camel_case();
let parameter = Parameter::from_schema(&name, schema)?;
let parameter = Parameter::from_schema(&name, schema, warnings.clone())?;
params.push(PathParameter::Inline(parameter));
}
};

View file

@ -1,6 +1,6 @@
[package]
name = "torn-api"
version = "4.2.0"
version = "4.7.0"
edition = "2021"
description = "Auto-generated bindings for the v2 torn api"
license = { workspace = true }
@ -43,7 +43,7 @@ strum = { version = "0.27.1", features = ["derive"], optional = true }
tokio = { version = "1", features = ["full"] }
[build-dependencies]
torn-api-codegen = { path = "../torn-api-codegen", version = "0.7.4" }
torn-api-codegen = { path = "../torn-api-codegen", version = "0.8" }
syn = { workspace = true, features = ["parsing"] }
proc-macro2 = { workspace = true }
prettyplease = "0.2"

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,7 @@ pub(super) mod test {
executor::{ExecutorExt, ReqwestClient},
models::{
faction_selection_name::FactionSelectionNameVariant, AttackCode,
PersonalStatsCategoryEnum, PersonalStatsStatName, UserListEnum,
PersonalStatsCategoryEnum, PersonalStatsStatName, UserId, UserListEnum,
},
};
@ -743,7 +743,7 @@ pub(super) mod test {
client
.user()
.bounties_for_id(986228.into(), |b| b)
.bounties_for_id(UserId(986228).into(), |b| b)
.await
.unwrap();
}
@ -807,7 +807,7 @@ pub(super) mod test {
client
.user()
.forumposts_for_id(1.into(), |b| b)
.forumposts_for_id(UserId(1).into(), |b| b)
.await
.unwrap();
}
@ -832,7 +832,7 @@ pub(super) mod test {
client
.user()
.forumthreads_for_id(1.into(), |b| b)
.forumthreads_for_id(UserId(1).into(), |b| b)
.await
.unwrap();
}
@ -848,7 +848,11 @@ pub(super) mod test {
async fn user_hof_for_id() {
let client = test_client().await;
client.user().hof_for_id(1.into(), |b| b).await.unwrap();
client
.user()
.hof_for_id(UserId(1).into(), |b| b)
.await
.unwrap();
}
#[tokio::test]
@ -1094,7 +1098,7 @@ pub(super) mod test {
client
.user()
.personalstats_for_id(1.into(), |b| b.cat(PersonalStatsCategoryEnum::All))
.personalstats_for_id(UserId(1).into(), |b| b.cat(PersonalStatsCategoryEnum::All))
.await
.unwrap();
}
@ -1147,7 +1151,11 @@ pub(super) mod test {
client.user().profile(|b| b).await.unwrap();
client.user().profile_for_id(4.into(), |b| b).await.unwrap();
client
.user()
.profile_for_id(UserId(4).into(), |b| b)
.await
.unwrap();
}
#[tokio::test]