Skip to content

Commit eeb1df3

Browse files
johnbattyclaude
andauthored
Fix inline enum query/path params to use typed enums instead of String (#779)
* Fix git items recursionLevel parameter to use VersionControlRecursionType enum The recursionLevel query parameter on git::items::get and git::items::list was typed as impl Into<String>. It now uses models::VersionControlRecursionType, a proper enum with variants None, OneLevel, OneLevelPlusNestedEmptyFolders, Full. Changes: - Patcher: add VersionControlRecursionType definition to git.json and replace the inline string enum on both the paths and x-ms-paths items operations - Codegen: generate Display impl for all enum types so they can be serialised as URL query parameter values - Examples: update git_items_list and git_repo_download_zip to use the new type Closes #78 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Refactor: detect inline enum params in codegen instead of patching specs Previously, the patcher was adding spec patches for each inline enum query parameter (e.g. recursionLevel). This replaces that approach with a proper code generator fix. Changes: - spec.rs: WebParameter::type_name() now returns TypeName::Reference(name) for parameters with a non-empty `enum` field and an `x-ms-enum.name`, instead of TypeName::String. - spec.rs: new Spec::inline_parameter_enum_schemas() collects synthetic Schema entries for all inline enum parameters across all operations so the models codegen can generate named enum types for them. - codegen_models.rs: all_schemas() calls inline_parameter_enum_schemas() to include those synthetic schemas alongside definition-based schemas. - patcher.rs: remove the now-unnecessary patch_git_items_recursion_level patch function. This automatically fixes all 105 inline enum query/path parameters across all API areas (build, git, release, wit, tfvc, wiki, etc.), generating proper typed enum types instead of impl Into<String> for each. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix import ordering in git_repo_download_zip example Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Update CHANGELOG.md for inline enum parameter changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent cec87f9 commit eeb1df3

66 files changed

Lines changed: 8975 additions & 549 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- All generated enum types now implement `std::fmt::Display`.
13+
14+
### Changed
15+
16+
- **Breaking:** Query and path parameters that accept a fixed set of values are
17+
now typed as enums instead of `impl Into<String>`. This affects 105 parameters
18+
across all API areas. Examples:
19+
- `git::items::list` / `git::items::get`: `recursion_level` now takes
20+
`models::VersionControlRecursionType` (also applies to `tfvc` and `wiki`)
21+
- `build::builds::list`: `status_filter` takes `models::BuildStatus`,
22+
`result_filter` takes `models::BuildResult`, `reason_filter` takes
23+
`models::BuildReason`, `query_order` takes `models::BuildQueryOrder`
24+
- `release`: `status_filter` takes `models::ReleaseStatus`, `query_order`
25+
takes `models::ReleaseQueryOrder`, `deployment_status` takes
26+
`models::DeploymentStatus`
27+
- `git::pull_requests::list`: `search_criteria_status` takes
28+
`models::PullRequestStatus`
29+
- `wit::work_items::get_work_item`: `expand` takes `models::WorkItemExpand`
30+
31+
The enum types are defined in each module's `models` and carry the same
32+
variant names as the API documentation (e.g. `VersionControlRecursionType::Full`,
33+
`BuildStatus::InProgress`).
34+
1035
## [0.36.0]
1136

1237
### Added

autorust/codegen/src/codegen_models.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,17 @@ fn all_schemas(spec: &Spec) -> Result<IndexMap<RefKey, SchemaGen>> {
391391
}
392392
}
393393

394+
// synthetic schemas for inline enum query parameters (e.g. `type: string` + `enum` + `x-ms-enum`)
395+
for (ref_key, schema) in spec.inline_parameter_enum_schemas()? {
396+
if !all_schemas.contains_key(&ref_key) {
397+
let doc_file = ref_key.file_path.clone();
398+
all_schemas.insert(
399+
ref_key.clone(),
400+
SchemaGen::new(Some(ref_key), schema, doc_file),
401+
);
402+
}
403+
}
404+
394405
Ok(all_schemas)
395406
}
396407

@@ -926,6 +937,29 @@ fn create_enum(
926937
quote! {}
927938
};
928939

940+
let mut display_arms = TokenStream::new();
941+
for enum_value in &enum_values {
942+
let value = &enum_value.value;
943+
let variant_nm = value.to_camel_case_ident()?;
944+
display_arms.extend(quote! {
945+
Self::#variant_nm => write!(f, #value),
946+
});
947+
}
948+
if property.is_model_as_string_enum() {
949+
display_arms.extend(quote! {
950+
Self::UnknownValue(s) => write!(f, "{}", s),
951+
});
952+
}
953+
let display_code = quote! {
954+
impl std::fmt::Display for #nm {
955+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
956+
match self {
957+
#display_arms
958+
}
959+
}
960+
}
961+
};
962+
929963
let doc_comment = DocCommentCode::from(&property.schema.common.description);
930964

931965
let code = quote! {
@@ -937,6 +971,7 @@ fn create_enum(
937971
}
938972
#custom_serde_code
939973
#default_code
974+
#display_code
940975
};
941976
let type_name = TypeNameCode::from(vec![namespace, Some(id)]);
942977

autorust/codegen/src/spec.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,61 @@ impl Spec {
322322
Ok(resolved)
323323
}
324324

325+
/// Collect synthetic schemas for inline enum parameters across all operations.
326+
///
327+
/// Parameters with `type: string`, a non-empty `enum` field, and an `x-ms-enum`
328+
/// extension carry enough information to generate a proper named Rust enum type.
329+
/// This method extracts those and returns them as schemas so the models codegen
330+
/// can emit a top-level enum definition for each one.
331+
pub fn inline_parameter_enum_schemas(&self) -> Result<Vec<(RefKey, Schema)>> {
332+
let mut result: Vec<(RefKey, Schema)> = Vec::new();
333+
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
334+
for (doc_file, doc) in self.docs() {
335+
if !self.is_input_file(doc_file) {
336+
continue;
337+
}
338+
let paths = self.resolve_path_map(doc_file, &doc.paths())?;
339+
for (_path, item) in &paths {
340+
let all_params: Vec<&autorust_openapi::Parameter> = item
341+
.parameters
342+
.iter()
343+
.filter_map(|p| match p {
344+
ReferenceOr::Item(p) => Some(p),
345+
_ => None,
346+
})
347+
.chain(item.operations().flat_map(|op| {
348+
op.parameters.iter().filter_map(|p| match p {
349+
ReferenceOr::Item(p) => Some(p),
350+
_ => None,
351+
})
352+
}))
353+
.collect();
354+
for param in all_params {
355+
if param.common.enum_.is_empty() {
356+
continue;
357+
}
358+
if let Some(x_ms_enum) = &param.common.x_ms_enum {
359+
let name = x_ms_enum.name.clone();
360+
if seen.insert(name.clone()) {
361+
let schema = Schema {
362+
common: param.common.clone(),
363+
..Default::default()
364+
};
365+
result.push((
366+
RefKey {
367+
file_path: doc_file.clone(),
368+
name,
369+
},
370+
schema,
371+
));
372+
}
373+
}
374+
}
375+
}
376+
}
377+
Ok(result)
378+
}
379+
325380
// only operations from listed input files
326381
fn operations_unresolved(&self) -> Result<Vec<WebOperationUnresolved>> {
327382
let mut operations: Vec<WebOperationUnresolved> = Vec::new();
@@ -629,6 +684,13 @@ impl WebParameter {
629684
}
630685

631686
pub fn type_name(&self) -> Result<TypeName> {
687+
// Inline enum parameters with an x-ms-enum name are generated as a named
688+
// enum type in models, referenced here by that name.
689+
if !self.0.common.enum_.is_empty() {
690+
if let Some(x_ms_enum) = &self.0.common.x_ms_enum {
691+
return Ok(TypeName::Reference(x_ms_enum.name.clone()));
692+
}
693+
}
632694
Ok(if let Some(_data_type) = self.data_type() {
633695
get_type_name_for_schema(&self.0.common)?
634696
} else if let Some(schema) = &self.0.schema {

azure_devops_rust_api/examples/git_items_list.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Git items (files and folders) list example.
66
use anyhow::Result;
77
use azure_devops_rust_api::git;
8+
use azure_devops_rust_api::git::models::VersionControlRecursionType;
89
use std::env;
910

1011
mod utils;
@@ -28,7 +29,7 @@ async fn main() -> Result<()> {
2829
let items = git_client
2930
.items_client()
3031
.list(organization, repository_name, project)
31-
.recursion_level("Full")
32+
.recursion_level(VersionControlRecursionType::Full)
3233
.await?
3334
.value;
3435

azure_devops_rust_api/examples/git_repo_download_zip.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use anyhow::Result;
77
use azure_devops_rust_api::git;
88
use azure_devops_rust_api::git::models::git_item::GitObjectType;
99
use azure_devops_rust_api::git::models::GitItem;
10+
use azure_devops_rust_api::git::models::VersionControlRecursionType;
1011
use std::env;
1112
use std::io::Write;
1213

@@ -23,7 +24,7 @@ async fn all_repo_items(
2324
let items = git_client
2425
.items_client()
2526
.list(organization, repository_name, project)
26-
.recursion_level("Full")
27+
.recursion_level(VersionControlRecursionType::Full)
2728
.await?
2829
.value;
2930
Ok(items)

azure_devops_rust_api/examples/wit_work_item_get.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async fn main() -> Result<()> {
7474
let work_item = wit_client
7575
.work_items_client()
7676
.get_work_item(&organization, work_item_id, &project)
77-
.expand("All")
77+
.expand(wit::models::WorkItemExpand::All)
7878
.await?;
7979

8080
println!("Work item [{work_item_id}]:\n{work_item:#?}");

azure_devops_rust_api/src/accounts/models.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ pub mod account {
125125
#[serde(rename = "moved")]
126126
Moved,
127127
}
128+
impl std::fmt::Display for AccountStatus {
129+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130+
match self {
131+
Self::None => write!(f, "none"),
132+
Self::Enabled => write!(f, "enabled"),
133+
Self::Disabled => write!(f, "disabled"),
134+
Self::Deleted => write!(f, "deleted"),
135+
Self::Moved => write!(f, "moved"),
136+
}
137+
}
138+
}
128139
#[doc = "Type of account: Personal, Organization"]
129140
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
130141
pub enum AccountType {
@@ -133,6 +144,14 @@ pub mod account {
133144
#[serde(rename = "organization")]
134145
Organization,
135146
}
147+
impl std::fmt::Display for AccountType {
148+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149+
match self {
150+
Self::Personal => write!(f, "personal"),
151+
Self::Organization => write!(f, "organization"),
152+
}
153+
}
154+
}
136155
}
137156
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
138157
pub struct AccountCreateInfoInternal {

0 commit comments

Comments
 (0)