├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── autorust ├── .gitignore ├── .vscode │ └── extensions.json ├── Cargo.lock ├── Cargo.toml ├── README.md ├── ado-autorust │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── autorust.toml ├── codegen │ ├── Cargo.toml │ ├── examples │ │ ├── cancelled.rs │ │ ├── list_crates.rs │ │ ├── mgmt_tags.rs │ │ ├── multiple_versions.rs │ │ ├── owned_crates.rs │ │ ├── resources_mgmt.rs │ │ ├── storage_mgmt.rs │ │ └── svc_tags.rs │ ├── src │ │ ├── Cargo.toml │ │ ├── autorust_toml.rs │ │ ├── bin │ │ │ └── gen_ado.rs │ │ ├── cargo_toml.rs │ │ ├── codegen.rs │ │ ├── codegen_models.rs │ │ ├── codegen_operations.rs │ │ ├── codegen_operations │ │ │ ├── create_client_and_builder.rs │ │ │ ├── function_code.rs │ │ │ ├── function_params.rs │ │ │ ├── new_request_code.rs │ │ │ ├── operation_module.rs │ │ │ ├── operations.rs │ │ │ ├── request_builder_into_future.rs │ │ │ ├── request_builder_send.rs │ │ │ ├── request_builder_setter.rs │ │ │ ├── request_builder_struct.rs │ │ │ ├── response_code.rs │ │ │ ├── response_headers.rs │ │ │ ├── set_request_code.rs │ │ │ ├── set_request_param_code.rs │ │ │ └── web_operation_gen.rs │ │ ├── config_parser.rs │ │ ├── content_type.rs │ │ ├── crates.rs │ │ ├── doc_comment.rs │ │ ├── error.rs │ │ ├── gen.rs │ │ ├── identifier.rs │ │ ├── io.rs │ │ ├── jinja.rs │ │ ├── lib.rs │ │ ├── lib_rs.rs │ │ ├── readme_md.rs │ │ ├── spec.rs │ │ └── status_codes.rs │ ├── templates │ │ ├── Cargo.toml.jinja │ │ ├── WorkspaceCargo.toml.jinja │ │ ├── check-all-services.yml.jinja │ │ ├── publish-sdks.yml.jinja │ │ ├── publish-services.yml.jinja │ │ └── readme.md.jinja │ └── tests │ │ ├── azure_rest_api_specs.rs │ │ └── redis_spec.rs └── openapi │ ├── .gitignore │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── examples │ ├── operation_ids.rs │ ├── prettyprint.rs │ ├── printer.rs │ └── read_example.rs │ ├── src │ ├── autorest.rs │ ├── contact.rs │ ├── example.rs │ ├── external_documentation.rs │ ├── header.rs │ ├── info.rs │ ├── lib.rs │ ├── license.rs │ ├── openapi.rs │ ├── operation.rs │ ├── parameter.rs │ ├── paths.rs │ ├── reference.rs │ ├── schema.rs │ ├── security.rs │ ├── status_code.rs │ └── tag.rs │ └── tests │ ├── azure_rest_api_specs.rs │ ├── common │ └── mod.rs │ └── openapi_spec_examples.rs ├── azure_devops_rust_api ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples │ ├── artifact_provenance.rs │ ├── artifacts_list.rs │ ├── build_get.rs │ ├── build_list.rs │ ├── build_list_continuation_token.rs │ ├── build_list_sync.rs │ ├── build_source_providers_list.rs │ ├── core_org_projects.rs │ ├── core_project_teams.rs │ ├── distributed_task.rs │ ├── extension_management_list.rs │ ├── git_commit_changes.rs │ ├── git_diff_files_between_base_and_target_branch.rs │ ├── git_items_get.rs │ ├── git_items_get_items_batch.rs │ ├── git_items_list.rs │ ├── git_policy_config_list.rs │ ├── git_pr_commits.rs │ ├── git_pr_create.rs │ ├── git_pr_files_changed.rs │ ├── git_pr_work_items.rs │ ├── git_push.rs │ ├── git_repo_download_zip.rs │ ├── git_repo_get.rs │ ├── git_repo_get_raw_rsp.rs │ ├── git_repo_list.rs │ ├── graph_query.rs │ ├── hooks_list.rs │ ├── ims_query.rs │ ├── member_entitlement_management.rs │ ├── permissions_report.rs │ ├── pipeline_preview.rs │ ├── pipelines.rs │ ├── policy.rs │ ├── release.rs │ ├── release_get_release.rs │ ├── release_logs.rs │ ├── search_code.rs │ ├── search_package.rs │ ├── search_repositories.rs │ ├── search_work_item.rs │ ├── service_endpoint.rs │ ├── status.rs │ ├── telemetry_git_repo_get.rs │ ├── test_plan.rs │ ├── test_runs_list.rs │ ├── utils │ │ └── mod.rs │ ├── wiki_pages_create_or_update.rs │ ├── wit_wiql.rs │ ├── wit_work_item_create.rs │ ├── wit_work_item_get.rs │ ├── wit_work_item_queries.rs │ └── work.rs ├── run_all_examples └── src │ ├── accounts │ ├── mod.rs │ └── models.rs │ ├── approvals_and_checks │ ├── mod.rs │ └── models.rs │ ├── artifacts │ ├── mod.rs │ └── models.rs │ ├── artifacts_package_types │ ├── mod.rs │ └── models.rs │ ├── audit │ ├── mod.rs │ └── models.rs │ ├── auth.rs │ ├── build │ ├── mod.rs │ └── models.rs │ ├── clt │ └── mod.rs │ ├── core │ ├── mod.rs │ └── models.rs │ ├── dashboard │ ├── mod.rs │ └── models.rs │ ├── date_time.rs │ ├── distributed_task │ ├── mod.rs │ └── models.rs │ ├── extension_management │ ├── mod.rs │ └── models.rs │ ├── favorite │ ├── mod.rs │ └── models.rs │ ├── git │ ├── mod.rs │ └── models.rs │ ├── graph │ ├── mod.rs │ └── models.rs │ ├── headers.rs │ ├── hooks │ ├── mod.rs │ └── models.rs │ ├── ims │ ├── mod.rs │ └── models.rs │ ├── lib.rs │ ├── member_entitlement_management │ ├── mod.rs │ └── models.rs │ ├── operations │ ├── mod.rs │ └── models.rs │ ├── permissions_report │ ├── mod.rs │ └── models.rs │ ├── pipelines │ ├── mod.rs │ └── models.rs │ ├── policy │ ├── mod.rs │ └── models.rs │ ├── processadmin │ ├── mod.rs │ └── models.rs │ ├── processes │ ├── mod.rs │ └── models.rs │ ├── profile │ ├── mod.rs │ └── models.rs │ ├── release │ ├── mod.rs │ └── models.rs │ ├── search │ ├── mod.rs │ └── models.rs │ ├── security │ ├── mod.rs │ └── models.rs │ ├── security_roles │ ├── mod.rs │ └── models.rs │ ├── serde.rs │ ├── service_endpoint │ ├── mod.rs │ └── models.rs │ ├── status │ ├── mod.rs │ └── models.rs │ ├── symbol │ ├── mod.rs │ └── models.rs │ ├── telemetry.rs │ ├── test │ ├── mod.rs │ └── models.rs │ ├── test_plan │ ├── mod.rs │ └── models.rs │ ├── test_results │ ├── mod.rs │ └── models.rs │ ├── tfvc │ ├── mod.rs │ └── models.rs │ ├── token_admin │ ├── mod.rs │ └── models.rs │ ├── tokens │ ├── mod.rs │ └── models.rs │ ├── wiki │ ├── mod.rs │ └── models.rs │ ├── wit │ ├── mod.rs │ └── models.rs │ └── work │ ├── mod.rs │ └── models.rs ├── build.ps1 ├── build.sh ├── publish.ps1 ├── publish.sh └── vsts-api-patcher ├── Cargo.lock ├── Cargo.toml └── src ├── main.rs └── patcher.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/autorust" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "cargo" # See documentation for possible values 13 | directory: "/vsts-api-patcher" # Location of package manifests 14 | schedule: 15 | interval: "weekly" 16 | - package-ecosystem: "cargo" # See documentation for possible values 17 | directory: "/azure_devops_rust_api" # Location of package manifests 18 | schedule: 19 | interval: "weekly" 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: vsts-api-patcher fmt 20 | run: cd vsts-api-patcher && cargo fmt --check 21 | - name: azure_devops_rust_api fmt 22 | run: cd azure_devops_rust_api && cargo fmt --check 23 | - name: codegen clippy 24 | run: cd autorust && cargo clippy 25 | - name: vsts-api-patcher clippy 26 | run: cd vsts-api-patcher && cargo clippy 27 | - name: codegen build and run autogeneration 28 | run: ./build.sh 29 | - name: check autogeneration matches git repo 30 | run: git diff --exit-code HEAD 31 | - name: cleanup code generator build artifacts 32 | run: rm -rf vsts-api-patcher/target && rm -rf autorust/target 33 | - name: azure_devops_rust_api clippy 34 | run: cd azure_devops_rust_api && cargo clippy --all-features -- --deny warnings 35 | - name: examples clippy 36 | run: cd azure_devops_rust_api && cargo clippy --all-features --examples -- --deny warnings 37 | - name: azure_devops_rust_api build 38 | run: cd azure_devops_rust_api && cargo build --all-features 39 | - name: cleanup azure_devops_rust_api build artifacts 40 | run: rm -rf azure_devops_rust_api/target 41 | - name: install wasm target 42 | run: rustup target add wasm32-unknown-unknown 43 | - name: azure_devops_rust_api check wasm build 44 | run: cd azure_devops_rust_api && cargo check --target wasm32-unknown-unknown --all-features 45 | - name: azure_devops_rust_api test 46 | run: cd azure_devops_rust_api && cargo test 47 | - name: azure_devops_rust_api documentation generation 48 | run: cd azure_devops_rust_api && cargo doc --all-features --no-deps 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files and executables 2 | target/ 3 | 4 | # Backup files generated by rustfmt 5 | **/*.rs.bk 6 | 7 | # Patched specs 8 | /vsts-rest-api-specs.patched 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vsts-rest-api-specs"] 2 | path = vsts-rest-api-specs 3 | url = https://github.com/MicrosoftDocs/vsts-rest-api-specs 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs, feature requests and questions. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | ## Microsoft Support Policy 10 | 11 | Support for this azure-devops-rust-api repo is limited to the resources listed above. 12 | -------------------------------------------------------------------------------- /autorust/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | _git2* 4 | b.rs 5 | *.txt 6 | client*.rs 7 | model*.rs 8 | generated 9 | node_modules 10 | package*.json 11 | -------------------------------------------------------------------------------- /autorust/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "matklad.rust-analyzer", 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /autorust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "codegen", 4 | "openapi", 5 | "ado-autorust", 6 | ] 7 | 8 | resolver = "2" 9 | 10 | [workspace.lints] 11 | 12 | -------------------------------------------------------------------------------- /autorust/README.md: -------------------------------------------------------------------------------- 1 | # AutoRust [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 2 | 3 | A code generator similar to [AutoRest](https://github.com/azure/autorest), but is written in Rust to generate Rust code. 4 | 5 | This is a fork of [`autorust` in azure-sdk-for-rust](https://github.com/Azure/azure-sdk-for-rust/tree/main/services/autorust), and has a few modifications to work for the Azure DevOps REST API specifications. 6 | 7 | ## Building 8 | 9 | To build the autorust code: 10 | 11 | ```sh 12 | cargo build 13 | ``` 14 | 15 | ## Running 16 | 17 | To autogenerate the `azure_devops_rust_api` code: 18 | 19 | ```sh 20 | cargo run --bin ado-autorust --release 21 | ``` 22 | 23 | -------------------------------------------------------------------------------- /autorust/ado-autorust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ado-autorust" 3 | version = "0.1.0" 4 | authors = ["John Batty "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [lib] 9 | 10 | [dependencies] 11 | autorust_openapi = { path = "../openapi" } 12 | quote = "1" 13 | proc-macro2 = { version = "1", default-features = false } 14 | serde_json = "1" 15 | serde_yaml = "0.9" 16 | heck = "0.5" 17 | regex = "1" 18 | indexmap = { version = "2", features = ["serde"] } 19 | path_abs = "0.5" 20 | comrak = "0.39" 21 | serde = "1" 22 | http-types = "2" 23 | once_cell = "1" 24 | syn = { version = "2", features = ["parsing"] } 25 | camino = "1" 26 | askama = "0.14" 27 | toml = "0.8" 28 | qstring = "0.7" 29 | cargo_toml = "0.22" 30 | 31 | [dev-dependencies] 32 | thiserror = "2" 33 | crates_io_api = "0.11" 34 | 35 | [lints] 36 | workspace = true 37 | 38 | [features] 39 | # Use the `azure_devops` feature to enable Azure DevOps specific modifications to the autorust codegen 40 | default = ["azure_devops"] 41 | azure_devops = [] 42 | -------------------------------------------------------------------------------- /autorust/codegen/examples/cancelled.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example cancelled 2 | // Analysis for https://github.com/Azure/azure-resource-manager-rpc/issues/144 3 | // List of specification that have a provisioningState of Cancelled 4 | 5 | use autorust_codegen::codegen_models::{all_schemas_resolved, SchemaGen}; 6 | use autorust_codegen::{get_mgmt_readmes, get_svc_readmes, io, Result, Spec, SpecReadme}; 7 | use std::collections::BTreeSet; 8 | 9 | fn main() -> Result<()> { 10 | check("Control Plane", &get_mgmt_readmes()?)?; 11 | println!(); 12 | check("Data Plane", &get_svc_readmes()?)?; 13 | Ok(()) 14 | } 15 | 16 | fn has_cancelled_enum_value(schema: &SchemaGen) -> bool { 17 | for property in schema.properties() { 18 | if "provisioningState" == property.name() { 19 | for ev in property.schema().enum_values() { 20 | if "Cancelled".eq_ignore_ascii_case(ev.value()) { 21 | return true; 22 | } 23 | } 24 | } 25 | } 26 | false 27 | } 28 | 29 | fn check(plane: &str, readmes: &[SpecReadme]) -> Result<()> { 30 | let mut services = BTreeSet::new(); 31 | for readme in readmes { 32 | let readme_path = readme.readme(); 33 | for tag in readme.config()?.tags() { 34 | let input_files = io::join_several(readme_path, &tag.input_files())?; 35 | match Spec::read_files(&input_files) { 36 | Ok(spec) => { 37 | if let Ok(schemas) = all_schemas_resolved(&spec) { 38 | for (_ref_key, schema) in &schemas { 39 | if has_cancelled_enum_value(schema) { 40 | services.insert(readme.spec()); 41 | } 42 | } 43 | } 44 | } 45 | Err(_err) => {} 46 | } 47 | } 48 | } 49 | println!("{} {} services:", plane, services.len()); 50 | for service in services { 51 | println!(" {service}"); 52 | } 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /autorust/codegen/examples/list_crates.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example list_crates 2 | // This list all crates that are generated. 3 | 4 | // cargo run --example list_crates -- 0.1.0 5 | // If a version is passed in as an option, it will filter out any that 6 | // are already published to crates.io. 7 | // An updated clone of crates.io-index is required. 8 | // git clone https://github.com/rust-lang/crates.io-index 9 | 10 | use autorust_codegen::crates::has_version; 11 | use autorust_codegen::crates::list_crate_names; 12 | pub type Result = std::result::Result>; 13 | 14 | fn main() -> Result<()> { 15 | let version = std::env::args().nth(1); 16 | let names = list_crate_names()?; 17 | match &version { 18 | Some(version) => { 19 | for name in names.iter() { 20 | if !has_version(name, version)? { 21 | println!("{name}"); 22 | // println!("cargo owner --add github:Azure:azure-sdk-publish-rust -- {}", name); 23 | } 24 | } 25 | } 26 | None => { 27 | for (i, name) in names.iter().enumerate() { 28 | println!("{} {}", i + 1, name); 29 | } 30 | } 31 | } 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /autorust/codegen/examples/mgmt_tags.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example mgmt_tags 2 | // prints all the mgmt (control plane, resource-manager) tags 3 | 4 | use autorust_codegen::get_mgmt_readmes; 5 | use autorust_codegen::Result; 6 | 7 | fn main() -> Result<()> { 8 | let mut tag_count = 0; 9 | for (i, spec) in get_mgmt_readmes()?.iter().enumerate() { 10 | println!("{} {}", i + 1, spec.spec()); 11 | for tag in spec.config()?.tags() { 12 | println!(" {}", &tag.name()); 13 | for input_file in &tag.input_files() { 14 | println!(" {input_file}"); 15 | } 16 | tag_count += 1; 17 | } 18 | } 19 | println!("{tag_count} tags"); 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /autorust/codegen/examples/multiple_versions.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example multiple_versions 2 | // report tags that use multiple versions 3 | // in general, we want to avoid this 4 | // https://github.com/Azure/azure-sdk-for-rust/issues/563 5 | 6 | use autorust_codegen::{get_mgmt_readmes, get_svc_readmes, io, Result, Spec, SpecReadme}; 7 | use std::collections::BTreeSet; 8 | 9 | fn main() -> Result<()> { 10 | println!("CONTROL PLANE"); 11 | check(&get_mgmt_readmes()?)?; 12 | println!(); 13 | println!("DATA PLANE"); 14 | check(&get_svc_readmes()?)?; 15 | Ok(()) 16 | } 17 | 18 | fn check(readmes: &[SpecReadme]) -> Result<()> { 19 | let mut services = BTreeSet::new(); 20 | let mut tags = 0; 21 | for readme in readmes { 22 | let readme_path = readme.readme(); 23 | for tag in readme.config()?.tags() { 24 | let input_files = io::join_several(readme_path, &tag.input_files())?; 25 | match Spec::read_files(&input_files) { 26 | Ok(spec) => { 27 | let versions = spec.api_versions(); 28 | if versions.len() > 1 { 29 | println!("{} {}", readme.spec(), &tag.name()); 30 | for version in versions { 31 | println!(" {version}"); 32 | } 33 | tags += 1; 34 | services.insert(readme.spec()); 35 | } 36 | } 37 | // Err(err) => println!("Error {}", err), 38 | Err(_err) => {} 39 | } 40 | } 41 | } 42 | println!(); 43 | println!("{tags} tags"); 44 | println!("{} services:", services.len()); 45 | for service in services { 46 | println!(" {service}"); 47 | } 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /autorust/codegen/examples/owned_crates.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example owned_crates 2 | // Validates that the GitHub team has ownership of all crates. 3 | // If not, it prints the command to add the crate. 4 | 5 | use autorust_codegen::crates::list_crate_names; 6 | 7 | /// https://github.com/orgs/Azure/teams/azure-sdk-publish-rust 8 | /// https://crates.io/teams/github:azure:azure-sdk-publish-rust 9 | const TEAM: &str = "github:azure:azure-sdk-publish-rust"; 10 | 11 | use crates_io_api::SyncClient; 12 | pub type Result = std::result::Result>; 13 | 14 | fn main() -> Result<()> { 15 | let client = &SyncClient::new("azure-sdk-for-rust", std::time::Duration::from_millis(1000))?; 16 | for crate_name in &list_crate_names()? { 17 | if !is_owner(client, crate_name)? { 18 | println!("cargo owner --add {TEAM} -- {crate_name}") 19 | } 20 | } 21 | Ok(()) 22 | } 23 | 24 | // This looks up each crate individually. 25 | // It would be more efficient to get a list of crates for a user if possible. 26 | // https://github.com/theduke/crates-io-api/issues/52 27 | fn is_owner(client: &SyncClient, crate_name: &str) -> Result { 28 | let cr = client.full_crate(crate_name, false)?; 29 | Ok(cr.owners.iter().any(|owner| owner.login == TEAM)) 30 | } 31 | -------------------------------------------------------------------------------- /autorust/codegen/examples/resources_mgmt.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example resources_mgmt 2 | // https://github.com/Azure/azure-rest-api-specs/tree/master/specification/resources/resource-manager 3 | 4 | use autorust_codegen::{autorust_toml::PackageConfig, *}; 5 | 6 | fn main() -> Result<()> { 7 | let output_folder = "../azure-sdk-for-rust/services/resources/mgmt/src/v2020_06_01"; 8 | let input_files = 9 | ["../../../azure-rest-api-specs/specification/resources/resource-manager/Microsoft.Resources/stable/2020-06-01/resources.json"]; 10 | run( 11 | &CrateConfig { 12 | run_config: &RunConfig::new("azure_mgmt_"), 13 | output_folder: output_folder.into(), 14 | input_files: input_files.iter().map(Into::into).collect(), 15 | }, 16 | &PackageConfig::default(), 17 | )?; 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /autorust/codegen/examples/storage_mgmt.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example storage_mgmt 2 | // https://github.com/Azure/azure-rest-api-specs/tree/master/specification/storage/resource-manager 3 | 4 | use autorust_codegen::{autorust_toml::PackageConfig, *}; 5 | 6 | fn main() -> Result<()> { 7 | let output_folder = "../azure-sdk-for-rust/services/storage/mgmt/src/v2020_08_01_preview"; 8 | let input_files = [ 9 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/preview/2020-08-01-preview/storage.json", 10 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/preview/2020-08-01-preview/blob.json", 11 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/preview/2020-08-01-preview/file.json", 12 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/preview/2020-08-01-preview/queue.json", 13 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/preview/2020-08-01-preview/table.json", 14 | ]; 15 | run( 16 | &CrateConfig { 17 | run_config: &RunConfig::new("azure_mgmt_"), 18 | output_folder: output_folder.into(), 19 | input_files: input_files.iter().map(Into::into).collect(), 20 | }, 21 | &PackageConfig::default(), 22 | )?; 23 | 24 | let output_folder = "../azure-sdk-for-rust/services/storage/mgmt/src/v2019_06_01"; 25 | let input_files = [ 26 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/stable/2019-06-01/storage.json", 27 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/stable/2019-06-01/blob.json", 28 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/stable/2019-06-01/file.json", 29 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/stable/2019-06-01/queue.json", 30 | "../../../azure-rest-api-specs/specification/storage/resource-manager/Microsoft.Storage/stable/2019-06-01/table.json", 31 | ]; 32 | run( 33 | &CrateConfig { 34 | run_config: &RunConfig::new("azure_mgmt_"), 35 | output_folder: output_folder.into(), 36 | input_files: input_files.iter().map(Into::into).collect(), 37 | }, 38 | &PackageConfig::default(), 39 | )?; 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /autorust/codegen/examples/svc_tags.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example svc_tags 2 | // prints all the svc (data plane) tags 3 | 4 | use autorust_codegen::Result; 5 | use autorust_codegen::{self, get_svc_readmes}; 6 | 7 | fn main() -> Result<()> { 8 | let mut tag_count = 0; 9 | for (i, spec) in get_svc_readmes()?.iter().enumerate() { 10 | println!("{} {}", i + 1, spec.spec()); 11 | for tag in spec.config()?.tags() { 12 | println!(" {}", &tag.name()); 13 | for input_file in &tag.input_files() { 14 | println!(" {input_file}"); 15 | } 16 | tag_count += 1; 17 | } 18 | } 19 | println!("{tag_count} tags"); 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /autorust/codegen/src/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autorust_codegen" 3 | version = "0.1.0" 4 | authors = ["Cameron Taggart "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [lib] 9 | 10 | [dependencies] 11 | autorust_openapi = { path = "../openapi" } 12 | quote = "1.0" 13 | proc-macro2 = { version = "1.0", default-features = false } 14 | serde_json = "1.0" 15 | serde_yaml = "0.9" 16 | heck = "0.5" 17 | regex = "1.7" 18 | indexmap = { version = "2.0", features = ["serde"] } 19 | path_abs = "0.5" 20 | comrak = "0.22" 21 | serde = "1.0" 22 | http-types = "2.12" 23 | once_cell = "1.16" 24 | syn = { version = "2.0", features = ["parsing"] } 25 | camino = "1.1" 26 | askama = "0.12" 27 | toml = "0.8" 28 | qstring = "0.7" 29 | cargo_toml = "0.20" 30 | 31 | [dev-dependencies] 32 | thiserror = "1.0" 33 | crates_io_api = "0.11" 34 | 35 | [lints] 36 | workspace = true 37 | 38 | -------------------------------------------------------------------------------- /autorust/codegen/src/codegen_operations/new_request_code.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | 4 | use crate::spec::WebVerb; 5 | 6 | /// Calls `azure_core::http::Request::new` and set the authentication. 7 | pub struct NewRequestCode { 8 | pub auth: AuthCode, 9 | pub verb: WebVerb, 10 | pub path: String, 11 | } 12 | 13 | impl ToTokens for NewRequestCode { 14 | fn to_tokens(&self, tokens: &mut TokenStream) { 15 | let auth = &self.auth; 16 | let verb = verb_to_tokens(&self.verb); 17 | tokens.extend(quote! { 18 | let mut req = azure_core::http::Request::new(url, #verb); 19 | #auth 20 | }) 21 | } 22 | } 23 | 24 | /// Sets the authentication. 25 | /// Only bearer token authentication is supported right now. 26 | /// TODO: move authentication within generated crates to use policies instead of adding to requests. 27 | pub(crate) struct AuthCode {} 28 | 29 | impl ToTokens for AuthCode { 30 | fn to_tokens(&self, tokens: &mut TokenStream) { 31 | tokens.extend(quote! { 32 | // Note: Changed for azure-devops-rust-api 33 | if let Some(auth_header) = this.client.token_credential().http_authorization_header(&this.client.scopes()).await? { 34 | req.insert_header(azure_core::http::headers::AUTHORIZATION, auth_header); 35 | } 36 | }) 37 | } 38 | } 39 | 40 | fn verb_to_tokens(verb: &WebVerb) -> TokenStream { 41 | match verb { 42 | WebVerb::Get => quote! { azure_core::http::Method::Get }, 43 | WebVerb::Post => quote! { azure_core::http::Method::Post }, 44 | WebVerb::Put => quote! { azure_core::http::Method::Put }, 45 | WebVerb::Patch => quote! { azure_core::http::Method::Patch }, 46 | WebVerb::Delete => quote! { azure_core::http::Method::Delete }, 47 | WebVerb::Options => quote! { azure_core::http::Method::Option }, 48 | WebVerb::Head => quote! { azure_core::http::Method::Head }, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /autorust/codegen/src/codegen_operations/operation_module.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, TokenStream}; 2 | use quote::{quote, ToTokens}; 3 | 4 | use super::{ 5 | request_builder_into_future::RequestBuilderIntoFutureCode, request_builder_send::RequestBuilderSendCode, 6 | request_builder_setter::RequestBuilderSettersCode, request_builder_struct::RequestBuilderStructCode, response_code::ResponseCode, 7 | }; 8 | pub struct OperationModuleCode { 9 | pub module_name: Ident, 10 | pub response_code: ResponseCode, 11 | pub request_builder_struct_code: RequestBuilderStructCode, 12 | pub request_builder_setters_code: RequestBuilderSettersCode, 13 | pub request_builder_send_code: RequestBuilderSendCode, 14 | pub request_builder_intofuture_code: RequestBuilderIntoFutureCode, 15 | } 16 | impl ToTokens for OperationModuleCode { 17 | fn to_tokens(&self, tokens: &mut TokenStream) { 18 | let Self { 19 | module_name, 20 | response_code, 21 | request_builder_struct_code, 22 | request_builder_setters_code, 23 | request_builder_send_code, 24 | request_builder_intofuture_code, 25 | } = &self; 26 | tokens.extend(quote! { 27 | pub mod #module_name { 28 | use super::models; 29 | #[cfg(target_arch = "wasm32")] 30 | use futures::future::LocalBoxFuture as BoxFuture; 31 | #[cfg(not(target_arch = "wasm32"))] 32 | use futures::future::BoxFuture as BoxFuture; 33 | 34 | #response_code 35 | 36 | #request_builder_struct_code 37 | 38 | impl RequestBuilder { 39 | #request_builder_setters_code 40 | #request_builder_send_code 41 | } 42 | 43 | #request_builder_intofuture_code 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /autorust/codegen/src/codegen_operations/operations.rs: -------------------------------------------------------------------------------- 1 | use crate::{content_type, CodeGen, Result}; 2 | 3 | use super::{ 4 | function_code::ClientFunctionCode, 5 | function_params::FunctionParams, 6 | new_request_code::{AuthCode, NewRequestCode}, 7 | operation_module::OperationModuleCode, 8 | request_builder_into_future::RequestBuilderIntoFutureCode, 9 | request_builder_send::RequestBuilderSendCode, 10 | request_builder_setter::RequestBuilderSettersCode, 11 | request_builder_struct::RequestBuilderStructCode, 12 | response_code::ResponseCode, 13 | set_request_code::SetRequestCode, 14 | web_operation_gen::WebOperationGen, 15 | }; 16 | pub struct OperationCode { 17 | pub client_functions: Vec, 18 | pub module_code: Vec, 19 | } 20 | 21 | impl OperationCode { 22 | // Create code for the web operation 23 | pub fn new(cg: &CodeGen, operation: &WebOperationGen) -> Result { 24 | let parameters = &FunctionParams::new(cg, operation)?; 25 | 26 | let verb = operation.0.verb.clone(); 27 | let auth = AuthCode {}; 28 | let new_request_code = NewRequestCode { 29 | verb, 30 | auth, 31 | path: operation.0.path.clone(), 32 | }; 33 | 34 | // get the content-types from the operation, else the spec, else default to json 35 | let consumes = operation 36 | .pick_consumes() 37 | .unwrap_or_else(|| cg.spec.pick_consumes().unwrap_or(content_type::APPLICATION_JSON)) 38 | .to_string(); 39 | let produces = operation 40 | .pick_produces() 41 | .unwrap_or_else(|| cg.spec.pick_produces().unwrap_or(content_type::APPLICATION_JSON)) 42 | .to_string(); 43 | 44 | let lro = operation.0.long_running_operation; 45 | let lro_options = operation.0.long_running_operation_options.clone(); 46 | 47 | let request_builder = SetRequestCode::new(operation, parameters, consumes); 48 | let in_operation_group = operation.0.in_group(); 49 | let client_function_code = ClientFunctionCode::new(operation, parameters, in_operation_group)?; 50 | let request_builder_struct_code = RequestBuilderStructCode::new(parameters, in_operation_group, lro, lro_options.clone()); 51 | let request_builder_setters_code = RequestBuilderSettersCode::new(parameters); 52 | let response_code = ResponseCode::new(cg, operation, produces)?; 53 | let request_builder_send_code = RequestBuilderSendCode::new(new_request_code, request_builder, response_code.clone())?; 54 | let request_builder_intofuture_code = RequestBuilderIntoFutureCode::new(response_code.clone(), lro, lro_options)?; 55 | 56 | let module_code = OperationModuleCode { 57 | module_name: operation.function_name()?, 58 | response_code, 59 | request_builder_struct_code, 60 | request_builder_setters_code, 61 | request_builder_send_code, 62 | request_builder_intofuture_code, 63 | }; 64 | 65 | Ok(OperationCode { 66 | client_functions: vec![client_function_code], 67 | module_code: vec![module_code], 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /autorust/codegen/src/codegen_operations/request_builder_setter.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | 4 | use super::function_params::{FunctionParam, FunctionParams}; 5 | 6 | /// The setter functions for the request builder. 7 | #[derive(Clone)] 8 | pub struct RequestBuilderSettersCode { 9 | parameters: FunctionParams, 10 | } 11 | 12 | impl RequestBuilderSettersCode { 13 | pub fn new(parameters: &FunctionParams) -> Self { 14 | Self { 15 | parameters: parameters.clone(), 16 | } 17 | } 18 | } 19 | 20 | impl ToTokens for RequestBuilderSettersCode { 21 | fn to_tokens(&self, tokens: &mut TokenStream) { 22 | for param in self.parameters.optional_params() { 23 | let FunctionParam { 24 | variable_name, type_name, .. 25 | } = param; 26 | let is_vec = type_name.is_vec(); 27 | let mut type_name = type_name.clone(); 28 | type_name.optional(false); 29 | type_name.impl_into(!is_vec); 30 | let mut value = if type_name.has_impl_into() { 31 | quote! { #variable_name.into() } 32 | } else { 33 | quote! { #variable_name } 34 | }; 35 | if !is_vec { 36 | value = quote! { Some(#value) }; 37 | } 38 | let doc_comment = match ¶m.description { 39 | Some(desc) if !desc.is_empty() => quote! { #[ doc = #desc ] }, 40 | _ => quote! {}, 41 | }; 42 | tokens.extend(quote! { 43 | #doc_comment 44 | pub fn #variable_name(mut self, #variable_name: #type_name) -> Self { 45 | self.#variable_name = #value; 46 | self 47 | } 48 | }); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /autorust/codegen/src/codegen_operations/set_request_code.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | 4 | use crate::spec::WebVerb; 5 | 6 | use super::{ 7 | function_params::FunctionParams, set_request_param_code::SetRequestParamsCode, 8 | web_operation_gen::WebOperationGen, 9 | }; 10 | /// Set all body and parameters for the request. 11 | pub struct SetRequestCode { 12 | pub has_param_api_version: bool, 13 | pub has_param_x_ms_version: bool, 14 | pub api_version: String, 15 | consumes: String, 16 | pub parameters: FunctionParams, 17 | has_body_parameter: bool, 18 | is_post: bool, 19 | } 20 | 21 | impl SetRequestCode { 22 | pub fn new(operation: &WebOperationGen, parameters: &FunctionParams, consumes: String) -> Self { 23 | let is_post = operation.0.verb == WebVerb::Post; 24 | Self { 25 | has_param_api_version: parameters.has_api_version(), 26 | has_param_x_ms_version: parameters.has_x_ms_version(), 27 | api_version: operation.api_version().to_string(), 28 | consumes, 29 | parameters: parameters.clone(), 30 | has_body_parameter: operation.0.has_body_parameter(), 31 | is_post, 32 | } 33 | } 34 | } 35 | 36 | impl ToTokens for SetRequestCode { 37 | fn to_tokens(&self, tokens: &mut TokenStream) { 38 | if self.has_param_x_ms_version { 39 | let api_version = &self.api_version; 40 | tokens.extend(quote! { 41 | req.insert_header(azure_core::http::headers::VERSION, #api_version); 42 | }); 43 | } 44 | 45 | // params 46 | let build_request_params = SetRequestParamsCode { 47 | content_type: self.consumes.clone(), 48 | params: self.parameters.clone(), 49 | }; 50 | tokens.extend(build_request_params.into_token_stream()); 51 | 52 | if !self.has_body_parameter { 53 | tokens.extend(quote! { 54 | let req_body = azure_core::Bytes::new(); 55 | }); 56 | } 57 | 58 | // if it is a post and there is no body, set the Content-Length to 0 59 | if self.is_post && !self.has_body_parameter { 60 | tokens.extend(quote! { 61 | req.insert_header(azure_core::http::headers::CONTENT_LENGTH, "0"); 62 | }); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /autorust/codegen/src/content_type.rs: -------------------------------------------------------------------------------- 1 | pub const APPLICATION_JSON: &str = "application/json"; 2 | pub const APPLICATION_XML: &str = "application/xml"; 3 | pub const APPLICATION_OCTET_STREAM: &str = "application/octet-stream"; 4 | 5 | /// Pick `application/json` if it is an option, else the first one in the list 6 | pub fn pick<'a>(mut list: impl Iterator) -> Option<&'a str> { 7 | let value = list.next(); 8 | if value == Some(APPLICATION_JSON) { 9 | return value; 10 | } 11 | for next in list { 12 | if next == APPLICATION_JSON { 13 | return Some(next); 14 | } 15 | } 16 | value 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | use crate::Result; 23 | 24 | #[test] 25 | fn test_consumes_xml() -> Result<()> { 26 | let consumes = vec![APPLICATION_XML]; 27 | assert_eq!(Some(APPLICATION_XML), pick(consumes.into_iter())); 28 | Ok(()) 29 | } 30 | 31 | #[test] 32 | fn test_consumes_none() -> Result<()> { 33 | let consumes = vec![]; 34 | assert_eq!(None, pick(consumes.into_iter())); 35 | Ok(()) 36 | } 37 | 38 | #[test] 39 | fn test_consumes_json() -> Result<()> { 40 | let consumes = vec![APPLICATION_XML, APPLICATION_JSON]; 41 | assert_eq!(Some(APPLICATION_JSON), pick(consumes.into_iter())); 42 | Ok(()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /autorust/codegen/src/crates.rs: -------------------------------------------------------------------------------- 1 | use crate::{ErrorKind, Result, ResultExt}; 2 | use camino::{Utf8Path, Utf8PathBuf}; 3 | use cargo_toml::Manifest; 4 | use serde::Deserialize; 5 | use std::{ 6 | collections::BTreeSet, 7 | fs::{self, File}, 8 | io::{BufRead, BufReader}, 9 | path::Path, 10 | str::FromStr, 11 | }; 12 | 13 | /// Get all directories below the given directory. 14 | fn list_dirs_in(dir: impl AsRef) -> Result> { 15 | let mut dirs = Vec::new(); 16 | let paths = fs::read_dir(dir.as_ref())?; 17 | for path in paths.flatten() { 18 | if let Some(path) = Utf8Path::from_path(&path.path()) { 19 | if path.is_dir() && path.join("Cargo.toml").exists() { 20 | dirs.push(path.to_path_buf()); 21 | } 22 | } 23 | } 24 | Ok(dirs) 25 | } 26 | 27 | pub fn list_crates(services_dir: &Path) -> Result> { 28 | let mut package_names = BTreeSet::new(); 29 | let base_path = services_dir.join("Cargo.toml"); 30 | let manifest = Manifest::from_path(base_path)?; 31 | if let Some(workspaces) = manifest.workspace { 32 | for member in workspaces.members { 33 | let member_path = services_dir.join(member).join("Cargo.toml"); 34 | let Ok(manifest) = Manifest::from_path(member_path) else { continue }; 35 | let Some(package) = manifest.package else { 36 | continue; 37 | }; 38 | package_names.insert(package.name); 39 | } 40 | } 41 | Ok(package_names) 42 | } 43 | 44 | pub fn list_dirs() -> Result> { 45 | let mut names: Vec<_> = list_dirs_in("../mgmt")?.into_iter().collect(); 46 | names.extend(list_dirs_in("../svc")?); 47 | names.sort(); 48 | Ok(names) 49 | } 50 | 51 | pub fn has_version(name: &str, version: &str) -> Result { 52 | Ok(get_versions(name)?.iter().any(|v| v.vers.as_str() == version)) 53 | } 54 | 55 | /// Gets all the versions for a given crate 56 | /// Expects https://github.com/rust-lang/crates.io-index to be cloned as a sibling directory. 57 | fn get_versions(crate_name: &str) -> Result> { 58 | // all of these crates begin with "azure". 59 | let path = format!("../../../crates.io-index/az/ur/{crate_name}"); 60 | let path = Utf8PathBuf::from_str(&path).map_kind(ErrorKind::Parse)?; 61 | let mut versions = Vec::new(); 62 | if path.exists() { 63 | let file = File::open(path)?; 64 | let reader = BufReader::new(file); 65 | for line in reader.lines() { 66 | let version: CrateVersion = serde_json::from_str(&line?)?; 67 | versions.push(version); 68 | } 69 | } 70 | Ok(versions) 71 | } 72 | 73 | #[derive(Debug, Deserialize)] 74 | pub struct CrateVersion { 75 | pub name: String, 76 | pub vers: String, 77 | } 78 | -------------------------------------------------------------------------------- /autorust/codegen/src/doc_comment.rs: -------------------------------------------------------------------------------- 1 | //! Provides the [DocCommentCode] struct, which can be used to generate doc comment tokens. 2 | use proc_macro2::TokenStream; 3 | use quote::{quote, ToTokens}; 4 | 5 | #[derive(Clone)] 6 | pub struct DocCommentCode { 7 | comment: Option, 8 | } 9 | 10 | impl DocCommentCode { 11 | pub fn new(comment: Option) -> Self { 12 | Self { comment } 13 | } 14 | pub fn is_empty(&self) -> bool { 15 | if let Some(comment) = &self.comment { 16 | comment.is_empty() 17 | } else { 18 | true 19 | } 20 | } 21 | } 22 | 23 | impl From<&str> for DocCommentCode { 24 | fn from(comment: &str) -> Self { 25 | Self { 26 | comment: Some(comment.to_string()), 27 | } 28 | } 29 | } 30 | 31 | impl From> for DocCommentCode { 32 | fn from(comment: Option) -> Self { 33 | Self { comment } 34 | } 35 | } 36 | 37 | impl From<&Option> for DocCommentCode { 38 | fn from(comment: &Option) -> Self { 39 | Self { 40 | comment: comment.clone(), 41 | } 42 | } 43 | } 44 | 45 | impl ToTokens for DocCommentCode { 46 | fn to_tokens(&self, tokens: &mut TokenStream) { 47 | if self.is_empty() { 48 | return; 49 | } 50 | if let Some(comment) = &self.comment { 51 | tokens.extend(quote! { #[doc = #comment] }) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /autorust/codegen/src/io.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, ErrorKind, Result, ResultExt}; 2 | use camino::{Utf8Path, Utf8PathBuf}; 3 | use path_abs::PathMut; 4 | use std::path::PathBuf; 5 | 6 | /// Joins two files paths together 7 | /// 8 | /// If the first path ends with a file name (i.e., the last component has a file extension), 9 | /// the file component is dropped from that path. 10 | pub fn join, P2: AsRef>(a: P1, b: P2) -> Result { 11 | let mut c = a.as_ref(); 12 | if c.extension().is_some() { 13 | c = c 14 | .parent() 15 | .ok_or_else(|| Error::with_message(ErrorKind::Io, || "unable to get parent path of {c}"))?; 16 | // to directory 17 | } 18 | let mut c = PathBuf::from(c); 19 | let b = b.as_ref(); 20 | c.append(b).with_context(ErrorKind::Io, || format!("append path {b} to {c:?}"))?; 21 | Utf8PathBuf::from_path_buf(c).map_err(|path| Error::with_message(ErrorKind::Io, || format!("converting path to UTF-8: {path:?}"))) 22 | } 23 | 24 | pub fn join_several>(a: P1, b: &[Utf8PathBuf]) -> Result> { 25 | b.iter().map(|b| join(&a, b)).collect() 26 | } 27 | 28 | pub fn read_file>(path: P) -> Result> { 29 | let path = path.as_ref(); 30 | std::fs::read(path).with_context(ErrorKind::Io, || format!("reading file {path}")) 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | 37 | #[test] 38 | fn test_path_join() -> Result<()> { 39 | let a = "../../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json"; 40 | let b = "../../../../../common-types/resource-management/v1/types.json"; 41 | let c = join(a, b)?; 42 | assert_eq!( 43 | c, 44 | Utf8PathBuf::from("../../../azure-rest-api-specs/specification/common-types/resource-management/v1/types.json") 45 | ); 46 | Ok(()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /autorust/codegen/src/jinja.rs: -------------------------------------------------------------------------------- 1 | use crate::{ErrorKind, Result, ResultExt}; 2 | use askama::Template; 3 | use camino::Utf8Path; 4 | use std::{fs::File, io::Write}; 5 | 6 | #[derive(Template)] 7 | #[template(path = "publish-services.yml.jinja")] 8 | pub struct PublishServicesYml<'a> { 9 | pub packages: &'a Vec<&'a str>, 10 | } 11 | 12 | pub fn render(template: &T, path: impl AsRef) -> Result<()> { 13 | let rendered = template.render().with_context(ErrorKind::Io, || "render {path}")?; 14 | let mut file = File::create(path.as_ref())?; 15 | write!(file, "{rendered}")?; 16 | Ok(()) 17 | } 18 | 19 | impl<'a> PublishServicesYml<'a> { 20 | pub fn create(&self, path: impl AsRef) -> Result<()> { 21 | render(self, path) 22 | } 23 | } 24 | 25 | #[derive(Template)] 26 | #[template(path = "publish-sdks.yml.jinja")] 27 | pub struct PublishSdksYml<'a> { 28 | pub packages: &'a Vec<&'a str>, 29 | } 30 | 31 | impl<'a> PublishSdksYml<'a> { 32 | pub fn create(&self, path: impl AsRef) -> Result<()> { 33 | render(self, path) 34 | } 35 | } 36 | 37 | #[derive(Template)] 38 | #[template(path = "check-all-services.yml.jinja")] 39 | pub struct CheckAllServicesYml<'a> { 40 | pub packages: &'a Vec<&'a str>, 41 | } 42 | 43 | impl<'a> CheckAllServicesYml<'a> { 44 | pub fn create(&self, path: impl AsRef) -> Result<()> { 45 | render(self, path) 46 | } 47 | } 48 | 49 | #[derive(Template)] 50 | #[template(path = "Cargo.toml.jinja")] 51 | pub struct CargoToml<'a> { 52 | pub package_name: &'a str, 53 | pub default_tag: &'a str, 54 | pub features: Vec, 55 | pub azure_core_features: Vec<&'a str>, 56 | } 57 | 58 | impl<'a> CargoToml<'a> { 59 | pub fn create(&self, path: impl AsRef) -> Result<()> { 60 | render(self, path) 61 | } 62 | } 63 | 64 | #[derive(Template)] 65 | #[template(path = "WorkspaceCargo.toml.jinja")] 66 | pub struct WorkspaceCargoToml { 67 | pub dirs: Vec, 68 | } 69 | 70 | impl WorkspaceCargoToml { 71 | pub fn create(&self, path: impl AsRef) -> Result<()> { 72 | render(self, path) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /autorust/codegen/src/lib_rs.rs: -------------------------------------------------------------------------------- 1 | use crate::{config_parser::Tag, identifier::parse_ident, write_file}; 2 | use crate::{ErrorKind, Result, ResultExt}; 3 | use camino::Utf8Path; 4 | use proc_macro2::{Ident, TokenStream}; 5 | use quote::{quote, ToTokens}; 6 | use std::convert::{TryFrom, TryInto}; 7 | 8 | pub fn create(tags: &[&Tag], default_tag: &Tag, path: &Utf8Path, print_writing_file: bool) -> Result<()> { 9 | write_file(path, &create_body(tags, default_tag)?.into_token_stream(), print_writing_file) 10 | } 11 | 12 | struct Feature { 13 | pub feature_name: String, 14 | pub mod_name: Ident, 15 | } 16 | 17 | impl TryFrom<&&Tag> for Feature { 18 | type Error = crate::Error; 19 | fn try_from(tag: &&Tag) -> Result { 20 | let feature_name = tag.rust_feature_name(); 21 | let mod_name = parse_ident(&tag.rust_mod_name()).context(ErrorKind::Parse, "mod name")?; 22 | Ok(Feature { feature_name, mod_name }) 23 | } 24 | } 25 | 26 | struct BodyCode { 27 | pub default: Feature, 28 | pub features: Vec, 29 | } 30 | 31 | fn create_body(tags: &[&Tag], default_tag: &Tag) -> Result { 32 | let features: Vec = tags.iter().map(|tag| tag.try_into()).collect::>()?; 33 | let default = (&default_tag).try_into()?; 34 | 35 | Ok(BodyCode { features, default }) 36 | } 37 | 38 | impl ToTokens for BodyCode { 39 | fn to_tokens(&self, tokens: &mut TokenStream) { 40 | let mut cfgs = TokenStream::new(); 41 | 42 | for feature in &self.features { 43 | let Feature { feature_name, mod_name } = feature; 44 | cfgs.extend(quote! { 45 | #[cfg(feature = #feature_name)] 46 | pub mod #mod_name; 47 | }); 48 | } 49 | 50 | { 51 | let Feature { feature_name, mod_name } = &self.default; 52 | cfgs.extend(quote! { 53 | #[cfg(all(feature="default_tag", feature = #feature_name))] 54 | pub use #mod_name::*; 55 | }); 56 | } 57 | tokens.extend(quote! { 58 | #![allow(clippy::module_inception)] 59 | #![allow(clippy::too_many_arguments)] 60 | #![allow(clippy::ptr_arg)] 61 | #![allow(clippy::large_enum_variant)] 62 | #![allow(clippy::derive_partial_eq_without_eq)] 63 | #![allow(clippy::new_without_default)] 64 | #![allow(rustdoc::bare_urls)] 65 | #![allow(rustdoc::invalid_html_tags)] 66 | #![allow(rustdoc::broken_intra_doc_links)] 67 | #cfgs 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /autorust/codegen/src/readme_md.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | use crate::{config_parser::Tag, jinja}; 3 | use askama::Template; 4 | use camino::Utf8Path; 5 | use std::collections::HashMap; 6 | 7 | // https://djc.github.io/askama/ 8 | 9 | #[derive(Template)] 10 | #[template(path = "readme.md.jinja")] 11 | pub struct ReadmeMd<'a> { 12 | pub package_name: &'a str, 13 | pub readme_url: String, 14 | pub tags: &'a Vec<&'a Tag>, 15 | pub default_tag: &'a Tag, 16 | pub operation_totals: HashMap<&'a str, usize>, 17 | pub api_version_totals: HashMap<&'a str, usize>, 18 | pub api_versions: HashMap<&'a str, String>, 19 | } 20 | 21 | impl<'a> ReadmeMd<'a> { 22 | pub fn operation_total(&self, tag: &'a Tag) -> &usize { 23 | self.operation_totals.get(tag.name()).unwrap_or(&0) 24 | } 25 | pub fn api_version_total(&self, tag: &'a Tag) -> &usize { 26 | self.api_version_totals.get(tag.name()).unwrap_or(&0) 27 | } 28 | pub fn api_versions(&self, tag: &'a Tag) -> &str { 29 | self.api_versions.get(tag.name()).map(String::as_str).unwrap_or_default() 30 | } 31 | } 32 | 33 | impl<'a> ReadmeMd<'a> { 34 | pub fn create(&self, path: impl AsRef) -> Result<()> { 35 | jinja::render(self, path) 36 | } 37 | } 38 | 39 | pub fn url(path: &str) -> String { 40 | let url = path.replace('\\', "/"); 41 | url.replace( 42 | "../../../azure-rest-api-specs/", 43 | "https://github.com/Azure/azure-rest-api-specs/blob/main/", 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /autorust/codegen/src/status_codes.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_doc_comments)] 2 | 3 | use crate::identifier::parse_ident; 4 | use crate::{Error, ErrorKind, Result}; 5 | use autorust_openapi::StatusCode; 6 | use heck::ToPascalCase; 7 | use http_types::StatusCode as HttpStatusCode; 8 | use proc_macro2::Ident; 9 | use std::convert::TryFrom; 10 | 11 | fn try_from_u16(status_code: u16) -> Result { 12 | HttpStatusCode::try_from(status_code) 13 | .map_err(|_| Error::with_message(ErrorKind::Parse, || format!("invalid status code '{status_code}'"))) 14 | } 15 | 16 | /// Get the status code canonical reason 17 | pub fn get_status_code_name(status_code: &StatusCode) -> Result<&'static str> { 18 | match status_code { 19 | StatusCode::Code(status_code) => Ok(try_from_u16(*status_code)?.canonical_reason()), 20 | StatusCode::Default => Err(Error::with_message(ErrorKind::Parse, || "no status code name for default")), 21 | } 22 | } 23 | 24 | /// The canonical name in camel case. 25 | /// examples: Ok, Created, LoopDetected 26 | pub fn get_status_code_ident(status_code: &StatusCode) -> Result { 27 | match status_code { 28 | StatusCode::Code(_) => parse_ident(&get_status_code_name(status_code)?.to_pascal_case()), 29 | StatusCode::Default => parse_ident("Default"), 30 | } 31 | } 32 | 33 | pub fn is_success(status_code: &StatusCode) -> bool { 34 | match status_code { 35 | StatusCode::Code(status_code) => match try_from_u16(*status_code) { 36 | Ok(status_code) => status_code.is_success(), 37 | Err(_) => false, 38 | }, 39 | StatusCode::Default => false, 40 | } 41 | } 42 | 43 | pub fn is_error(status_code: &StatusCode) -> bool { 44 | match status_code { 45 | StatusCode::Code(status_code) => match try_from_u16(*status_code) { 46 | Ok(status_code) => !status_code.is_success(), 47 | Err(_) => false, 48 | }, 49 | StatusCode::Default => false, 50 | } 51 | } 52 | 53 | pub fn is_default(status_code: &StatusCode) -> bool { 54 | match status_code { 55 | StatusCode::Code(_status_code) => false, 56 | StatusCode::Default => true, 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | 64 | #[test] 65 | fn test_get_status_code_name() -> Result<()> { 66 | assert_eq!("Loop Detected", get_status_code_name(&StatusCode::Code(508))?); 67 | Ok(()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /autorust/codegen/templates/Cargo.toml.jinja: -------------------------------------------------------------------------------- 1 | # generated by AutoRust 2 | [package] 3 | name = "{{package_name}}" 4 | version = "0.20.0" 5 | edition = "2021" 6 | license = "MIT" 7 | description = "generated REST API bindings" 8 | repository = "https://github.com/azure/azure-sdk-for-rust" 9 | homepage = "https://github.com/azure/azure-sdk-for-rust" 10 | documentation = "https://docs.rs/{{package_name}}" 11 | # Require 1.64.0 for IntoFuture support 12 | # https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#enhancing-await-with-intofuture 13 | rust-version = "1.64.0" 14 | 15 | [lib] 16 | doctest = false 17 | 18 | [dependencies] 19 | azure_core = { path = "../../../sdk/core", version = "0.20", features = [{%- for feature in azure_core_features -%}"{{feature}}", {%- endfor -%}] } 20 | serde = { version = "1.0", features = ["derive"] } 21 | serde_json = "1.0" 22 | bytes = "1.3" 23 | futures = "0.3" 24 | time = "0.3" 25 | log = "0.4" 26 | once_cell = "1.18" 27 | 28 | [dev-dependencies] 29 | azure_identity = { path = "../../../sdk/identity" } 30 | tokio = { version = "1.23", features = ["macros", "rt-multi-thread"] } 31 | env_logger = "0.10" 32 | anyhow = "1.0" 33 | 34 | [package.metadata.docs.rs] 35 | all-features = true 36 | 37 | [features] 38 | default = ["default_tag", "enable_reqwest"] 39 | enable_reqwest = ["azure_core/enable_reqwest"] 40 | enable_reqwest_rustls = ["azure_core/enable_reqwest_rustls"] 41 | default_tag = ["{{default_tag}}"] 42 | {%- for feature in features %} 43 | "{{feature}}" = [] 44 | {%- endfor %} 45 | 46 | [lints] 47 | workspace = true 48 | -------------------------------------------------------------------------------- /autorust/codegen/templates/WorkspaceCargo.toml.jinja: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | {%- for dir in dirs %} 4 | "{{dir}}", 5 | {%- endfor %} 6 | ] 7 | 8 | resolver = "2" 9 | -------------------------------------------------------------------------------- /autorust/codegen/templates/check-all-services.yml.jinja: -------------------------------------------------------------------------------- 1 | name: Check All Features for Services 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | # the goal is to remove warnings from the generated code 8 | # https://github.com/Azure/azure-sdk-for-rust/issues/553 9 | RUSTFLAGS: -Dwarnings -Aunused_attributes -Aunreachable-code -Aunused-assignments -Adead-code -Aclippy::new-without-default 10 | CARGO_INCREMENTAL: 0 11 | 12 | jobs: 13 | {% for package in packages %} 14 | {{package}}: 15 | if: always() 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | profile: minimal 23 | override: true 24 | - uses: Swatinem/rust-cache@v2 25 | - name: check all features 26 | run: | 27 | cd services 28 | cargo check --all-features -p {{package}} 29 | {% endfor %} 30 | -------------------------------------------------------------------------------- /autorust/codegen/templates/publish-sdks.yml.jinja: -------------------------------------------------------------------------------- 1 | name: Publish SDKs 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | # Do NOT add your CARGO_REGISTRY_TOKEN to Azure/azure-sdk-for-rust repository. Only add it to your repository. 7 | # The publish actions are intended for running in your own fork where only you have access to run the actions. 8 | env: 9 | CARGO_REGISTRY_TOKEN: {% raw %}${{ secrets.CARGO_REGISTRY_TOKEN }}{% endraw %} 10 | 11 | jobs: 12 | {% for package in packages %} 13 | {{package}}: 14 | if: always() 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: publish 19 | run: | 20 | cargo publish -p {{package}} 21 | {% endfor %} 22 | -------------------------------------------------------------------------------- /autorust/codegen/templates/publish-services.yml.jinja: -------------------------------------------------------------------------------- 1 | name: Publish All Services 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | # Do NOT add your CARGO_REGISTRY_TOKEN to Azure/azure-sdk-for-rust repository. Only add it to your repository. 7 | # The publish actions are intended for running in your own fork where only you have access to run the actions. 8 | env: 9 | CARGO_REGISTRY_TOKEN: {% raw %}${{ secrets.CARGO_REGISTRY_TOKEN }}{% endraw %} 10 | 11 | jobs: 12 | {% for package in packages %} 13 | {{package}}: 14 | if: always() 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: publish 19 | run: | 20 | cd services 21 | cargo publish -p {{package}} 22 | {% endfor %} 23 | -------------------------------------------------------------------------------- /autorust/codegen/templates/readme.md.jinja: -------------------------------------------------------------------------------- 1 | # {{ package_name }} crate 2 | 3 | This is a generated [Azure SDK for Rust](https://github.com/Azure/azure-sdk-for-rust) crate from the Azure REST API specifications listed in: 4 | 5 | {{ readme_url }} 6 | 7 | To get started with these generated service crates, see the [examples](https://github.com/Azure/azure-sdk-for-rust/blob/main/services/README.md#examples). 8 | 9 | The default tag is `{{default_tag.name()}}`. 10 | 11 | The following [tags](https://github.com/Azure/azure-sdk-for-rust/blob/main/services/tags.md) are available: 12 | {% for tag in tags %} 13 | - `{{tag.name()}}` has {{self.operation_total(tag)}} operations from {{self.api_version_total(tag)}} API versions: {{self.api_versions(tag)}}. Use crate feature `{{tag.rust_feature_name()}}` to enable. The operations will be in the `{{tag.rust_mod_name()}}` module. 14 | {%- endfor -%} 15 | -------------------------------------------------------------------------------- /autorust/codegen/tests/azure_rest_api_specs.rs: -------------------------------------------------------------------------------- 1 | // cargo test --test azure_rest_api_specs 2 | // These tests require cloning azure-rest-api-specs. 3 | // git clone git@github.com:Azure/azure-rest-api-specs.git ../azure-rest-api-specs 4 | 5 | use autorust_codegen::*; 6 | use autorust_openapi::Reference; 7 | use camino::{Utf8Path, Utf8PathBuf}; 8 | use spec::TypedReference; 9 | 10 | type Result = std::result::Result>; 11 | 12 | const COMMON_TYPES_SPEC: &str = "../../../../azure-rest-api-specs/specification/security/resource-manager/common/v1/types.json"; 13 | const VMWARE_SPEC: &str = 14 | "../../../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json"; 15 | 16 | #[test] 17 | fn refs_count_security_common() -> Result<()> { 18 | let doc_file = COMMON_TYPES_SPEC; 19 | let api = &spec::openapi::parse(doc_file)?; 20 | let refs = spec::openapi::get_references(doc_file, api); 21 | assert_eq!(15, refs.len()); 22 | Ok(()) 23 | } 24 | 25 | #[test] 26 | fn refs_count_avs() -> Result<()> { 27 | let doc_file = VMWARE_SPEC; 28 | let api = &spec::openapi::parse(doc_file)?; 29 | let refs = spec::openapi::get_references(doc_file, api); 30 | assert_eq!(199, refs.len()); 31 | Ok(()) 32 | } 33 | 34 | #[test] 35 | fn ref_files() -> Result<()> { 36 | let doc_file = VMWARE_SPEC; 37 | let api = &spec::openapi::parse(doc_file)?; 38 | let files = spec::openapi::get_reference_file_paths(doc_file, api); 39 | assert_eq!(1, files.len()); 40 | assert!(files.contains("../../../../../common-types/resource-management/v1/types.json")); 41 | Ok(()) 42 | } 43 | 44 | #[test] 45 | fn read_spec_avs() -> Result<()> { 46 | let spec = &Spec::read_files(&[VMWARE_SPEC])?; 47 | assert_eq!(2, spec.docs().len()); 48 | assert!(spec.docs().contains_key(Utf8Path::new( 49 | "../../../../azure-rest-api-specs/specification/common-types/resource-management/v1/types.json" 50 | ))); 51 | Ok(()) 52 | } 53 | 54 | #[test] 55 | fn test_resolve_schema_ref() -> Result<()> { 56 | let file = Utf8PathBuf::from(VMWARE_SPEC); 57 | let spec = &Spec::read_files(&[&file])?; 58 | spec.resolve_schema_ref(&file, &Reference::parse("#/definitions/OperationList").unwrap())?; 59 | spec.resolve_schema_ref( 60 | &file, 61 | &Reference::parse("../../../../../common-types/resource-management/v1/types.json#/definitions/ErrorResponse").unwrap(), 62 | )?; 63 | Ok(()) 64 | } 65 | 66 | #[test] 67 | fn test_resolve_parameter_ref() -> Result<()> { 68 | let file = VMWARE_SPEC; 69 | let spec = &Spec::read_files(&[&file])?; 70 | spec.resolve_parameter_ref( 71 | file, 72 | Reference::parse("../../../../../common-types/resource-management/v1/types.json#/parameters/ApiVersionParameter").unwrap(), 73 | )?; 74 | Ok(()) 75 | } 76 | 77 | #[test] 78 | fn test_resolve_all_refs() -> Result<()> { 79 | let doc_file = VMWARE_SPEC; 80 | let spec = &Spec::read_files(&[&doc_file])?; 81 | for (doc_file, api) in spec.docs() { 82 | let refs = spec::openapi::get_references(doc_file, api); 83 | for rs in refs { 84 | match rs { 85 | TypedReference::PathItem(_) => {} 86 | TypedReference::Example(_) => {} 87 | TypedReference::Parameter(reference) => { 88 | spec.resolve_parameter_ref(doc_file, reference)?; 89 | } 90 | TypedReference::Schema(reference) => { 91 | spec.resolve_schema_ref(doc_file, &reference)?; 92 | } 93 | } 94 | } 95 | } 96 | Ok(()) 97 | } 98 | -------------------------------------------------------------------------------- /autorust/codegen/tests/redis_spec.rs: -------------------------------------------------------------------------------- 1 | // cargo test --test redis_specs 2 | // These tests require cloning azure-rest-api-specs. 3 | // git clone git@github.com:Azure/azure-rest-api-specs.git ../azure-rest-api-specs 4 | 5 | use autorust_codegen::{ 6 | spec::{self, TypedReference}, 7 | Spec, 8 | }; 9 | use camino::{Utf8Path, Utf8PathBuf}; 10 | 11 | type Result = std::result::Result>; 12 | 13 | const REDIS_SPEC: &str = 14 | "../../../../azure-rest-api-specs/specification/redis/resource-manager/Microsoft.Cache/stable/2020-06-01/redis.json"; 15 | const LINKS_SPEC: &str = "../../../../azure-rest-api-specs/specification/common-types/resource-management/v1/privatelinks.json"; 16 | 17 | #[test] 18 | fn test_redis_ref_files() -> Result<()> { 19 | let doc_file = REDIS_SPEC; 20 | let api = &spec::openapi::parse(doc_file)?; 21 | let files = spec::openapi::get_reference_file_paths(doc_file, api); 22 | println!("{files:#?}"); 23 | assert_eq!(2, files.len()); 24 | assert!(files.contains("../../../../../common-types/resource-management/v2/types.json")); 25 | Ok(()) 26 | } 27 | 28 | #[test] 29 | fn test_redis_read_spec() -> Result<()> { 30 | let spec = &Spec::read_files(&[REDIS_SPEC])?; 31 | println!("{:#?}", spec.docs().keys()); 32 | assert_eq!(4, spec.docs().len()); 33 | assert!(spec.docs().contains_key(Utf8Path::new( 34 | "../../../../azure-rest-api-specs/specification/common-types/resource-management/v2/types.json" 35 | ))); 36 | Ok(()) 37 | } 38 | 39 | #[test] 40 | fn test_links_ref_files() -> Result<()> { 41 | let doc_file = LINKS_SPEC; 42 | let api = &spec::openapi::parse(doc_file)?; 43 | let files = spec::openapi::get_reference_file_paths(doc_file, api); 44 | println!("{files:#?}"); 45 | assert_eq!(1, files.len()); 46 | assert!(files.contains("./types.json")); 47 | Ok(()) 48 | } 49 | 50 | #[test] 51 | fn test_links_refs_count() -> Result<()> { 52 | let doc_file = LINKS_SPEC; 53 | let api = &spec::openapi::parse(doc_file)?; 54 | let refs = spec::openapi::get_references(doc_file, api); 55 | assert_eq!(10, refs.len()); 56 | Ok(()) 57 | } 58 | 59 | #[test] 60 | fn test_redis_resolve_all_refs() -> Result<()> { 61 | let doc_file = Utf8PathBuf::from(REDIS_SPEC); 62 | let spec = &Spec::read_files(&[&doc_file])?; 63 | for (doc_file, api) in spec.docs() { 64 | let refs = spec::openapi::get_references(doc_file, api); 65 | for rs in refs { 66 | match rs { 67 | TypedReference::PathItem(_) => {} 68 | TypedReference::Example(_) => {} 69 | TypedReference::Parameter(reference) => { 70 | spec.resolve_parameter_ref(doc_file, reference)?; 71 | } 72 | TypedReference::Schema(reference) => { 73 | spec.resolve_schema_ref(doc_file, &reference)?; 74 | } 75 | } 76 | } 77 | } 78 | Ok(()) 79 | } 80 | -------------------------------------------------------------------------------- /autorust/openapi/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.bk 4 | -------------------------------------------------------------------------------- /autorust/openapi/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.1 2 | * public Response internals when no response type 3 | 4 | # 0.2 5 | * forked as autorust_openapi 6 | * removed OpenAPI v3 support. Use openapiv3 crate 7 | * moved v2 module to be the root module 8 | * removed serde_yaml, url, serde_url, semver, error-chain, & failure dependencies 9 | * switched CI from Travis CI to GitHub Actions 10 | * removed pretty_assertions dev-dependencies 11 | * added serde_ignored dev-dependency 12 | * added integration tests for OpenAPI v2 specification examples 13 | * added integration tests for azure-rest-api-specs specifications 14 | * added --example ignored 15 | * add `ReferenceOr` from openapiv3 16 | * replace `ParameterOrRef` with `ReferenceOr` 17 | * rename `Spec` to `OpenAPI` to match openapiv3 18 | * replace `Option>` with `Vec` and skip if `is_empty` like openapiv3 19 | * add dependency on `indexmap` like openapiv3 20 | * use `is_empty` on `IndexMap` instead of wrapping in `Option` like openapiv3 21 | * added missing Schema Object & Parameter Object fields 22 | * added StatusCode based on openapiv3 23 | 24 | * expose security definition as an enum type 25 | * Adds License object 26 | * Adds Contact object 27 | * Derives Default for all structs 28 | * Derives Clone for all structs 29 | * Changes the order of the output to be more similar to OpenAPI examples 30 | * switch to 2018 edition 31 | 32 | # 0.1.5 33 | 34 | * expose other schema types as public interfaces 35 | * re-export Result and ResultExt as top level interfaces 36 | 37 | # 0.1.4 38 | 39 | * added operational `parameters` field to `Operations` object 40 | 41 | # 0.1.3 42 | 43 | * added optional `required` and `enum_values` fields to `Schema` object 44 | 45 | # 0.1.2 46 | 47 | * added optional `format` fields to `Parameter` object 48 | 49 | # 0.1.1 50 | 51 | * added optional `summary` field to `Operation` object 52 | * made schemes and tags optional fields on `Operation` object 53 | 54 | # 0.1.0 55 | 56 | * initial release 57 | -------------------------------------------------------------------------------- /autorust/openapi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autorust_openapi" 3 | version = "0.2.0" 4 | authors = ["Cameron Taggart ", "softprops "] 5 | description = "Rust bindings for OpenAPI v2 as needed by autorust" 6 | documentation = "https://docs.rs/autorust_openapi" 7 | homepage = "https://github.com/ctaggart/autorust_openapi" 8 | repository = "https://github.com/ctaggart/autorust_openapi" 9 | keywords = ["openapi", "swagger", "AutoRest", "autorust"] 10 | license = "MIT" 11 | edition = "2018" 12 | publish = false 13 | 14 | [dependencies] 15 | serde = { version = "1", features = ["derive"] } 16 | serde_json = "1" 17 | indexmap = {version = "2", features = ["serde"]} 18 | thiserror = "2" 19 | 20 | [dev-dependencies] 21 | assert-json-diff = "2" 22 | serde_yaml = "0.9" 23 | camino = "1" 24 | 25 | [lints] 26 | workspace = true 27 | 28 | -------------------------------------------------------------------------------- /autorust/openapi/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Cameron Taggart 2 | Copyright (c) 2017 Doug Tangren 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /autorust/openapi/README.md: -------------------------------------------------------------------------------- 1 | # autorust_openapi [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 2 | 3 | Rust crate for deserializing [OpenAPI](http://swagger.io/specification/) documents as needed by [autorust](https://github.com/ctaggart/autorust/), an AutoRest extension. The goal is to be able to deserialize all of the documents found in [Azure/azure-rest-api-specs/specification](https://github.com/Azure/azure-rest-api-specs/tree/master/specification). They follow [OpenAPI Specification Version 2.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameter-object) and use several [extensions](https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md). 4 | 5 | ## Install 6 | 7 | Add the following to your `Cargo.toml` file: 8 | 9 | ```toml 10 | [dependencies] 11 | autorust_openapi = { git = "https://github.com/ctaggart/autorust_openapi" } 12 | ``` 13 | 14 | ## Similar Crates 15 | 16 | - This is a fork of the [openapi crate](https://crates.io/crates/openapi), maintained at [softprops/openapi](https://github.com/softprops/openapi). It was created by Doug Tangren (softprops) in 2017. 17 | - The [openapiv3 crate](https://github.com/glademiller/openapiv3) was created by Glade Miller in 2019 and is maintained at [glademiller/openapiv3](https://github.com/glademiller/openapiv3). 18 | -------------------------------------------------------------------------------- /autorust/openapi/examples/operation_ids.rs: -------------------------------------------------------------------------------- 1 | // Print the operation IDs alphabetically 2 | // cargo run --example operation_ids -- ../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json 3 | 4 | use autorust_openapi::*; 5 | use camino::Utf8Path; 6 | use std::{fs, process::exit}; 7 | 8 | fn main() -> Result<(), Box> { 9 | match std::env::args().nth(1) { 10 | None => { 11 | eprintln!("Please pass in the spec path."); 12 | exit(1); 13 | } 14 | Some(file_in) => { 15 | let file_in = Utf8Path::new(&file_in); 16 | let bytes = fs::read(file_in)?; 17 | let api: OpenAPI = serde_json::from_slice(&bytes)?; 18 | 19 | let mut operation_ids = Vec::new(); 20 | for (_path, item) in api.paths() { 21 | match item { 22 | ReferenceOr::Reference { .. } => (), 23 | ReferenceOr::Item(item) => { 24 | for op in item.operations() { 25 | if let Some(operation_id) = &op.operation_id { 26 | operation_ids.push(operation_id.clone()); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | 33 | operation_ids.sort(); 34 | for operation_id in operation_ids { 35 | println!("{operation_id}"); 36 | } 37 | } 38 | } 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /autorust/openapi/examples/prettyprint.rs: -------------------------------------------------------------------------------- 1 | use autorust_openapi::OpenAPI; 2 | use camino::{Utf8Path, Utf8PathBuf}; 3 | use std::{ 4 | fs::{self, create_dir_all, File}, 5 | io::Write, 6 | process::exit, 7 | }; 8 | 9 | pub type Error = Box; 10 | pub type Result = std::result::Result; 11 | 12 | // Pass in a output directory as the second parameter if you want to write the file instead of printing it to stdout. 13 | // cargo run --example prettyprint -- ../azure-rest-api-specs/specification/security/resource-manager/common/v1/types.json 14 | // cargo run --example prettyprint -- ../OpenAPI-Specification/examples/v2.0/json/petstore.json tmp 15 | fn main() -> Result<()> { 16 | match std::env::args().nth(1) { 17 | None => { 18 | eprintln!("Please pass in the spec path."); 19 | exit(1); 20 | } 21 | Some(file_in) => { 22 | let file_in = Utf8Path::new(&file_in); 23 | // reading the whole file upfront is much faster than using a BufReader 24 | // https://github.com/serde-rs/json/issues/160 25 | let bytes = fs::read(file_in)?; 26 | let spec: OpenAPI = serde_json::from_slice(&bytes)?; 27 | let json = serde_json::to_string_pretty(&spec)?; 28 | 29 | match std::env::args().nth(2) { 30 | Some(dir_out) => { 31 | create_dir_all(&dir_out)?; 32 | let mut file_out = Utf8PathBuf::new(); 33 | file_out.push(&dir_out); 34 | file_out.push(file_in.file_name().unwrap()); 35 | let mut file = File::create(file_out)?; 36 | file.write_all(json.as_bytes())?; 37 | } 38 | None => { 39 | println!("{json}"); 40 | } 41 | } 42 | } 43 | } 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /autorust/openapi/examples/printer.rs: -------------------------------------------------------------------------------- 1 | use autorust_openapi::OpenAPI; 2 | use std::{fs, process::exit}; 3 | 4 | pub type Error = Box; 5 | pub type Result = std::result::Result; 6 | 7 | // cargo run --example printer -- data/v2/k8s.json 8 | fn main() -> Result<()> { 9 | match std::env::args().nth(1) { 10 | None => { 11 | eprintln!("Please pass in the spec path."); 12 | exit(1); 13 | } 14 | Some(path) => { 15 | // reading the whole file upfront is much faster than using a BufReader 16 | // https://github.com/serde-rs/json/issues/160 17 | let bytes = fs::read(path)?; 18 | let spec: OpenAPI = serde_json::from_slice(&bytes)?; 19 | println!("# of paths: {}", spec.paths().len()); 20 | for (path, _op) in spec.paths() { 21 | println!(" {path}"); 22 | } 23 | println!("# of definitions: {}", spec.definitions.len()); 24 | for (name, _definition) in spec.definitions { 25 | println!(" {name}"); 26 | } 27 | } 28 | } 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /autorust/openapi/examples/read_example.rs: -------------------------------------------------------------------------------- 1 | use autorust_openapi::example::Example; 2 | use std::{fs, process::exit}; 3 | 4 | pub type Error = Box; 5 | pub type Result = std::result::Result; 6 | 7 | // cargo run --example read_example -- ../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/examples/PrivateClouds_List.json 8 | fn main() -> Result<()> { 9 | match std::env::args().nth(1) { 10 | None => { 11 | eprintln!("Please pass in the spec path."); 12 | exit(1); 13 | } 14 | Some(path) => { 15 | let bytes = fs::read(path)?; 16 | let example: Example = serde_json::from_slice(&bytes)?; 17 | println!("# of parameters: {}", example.parameters.len()); 18 | println!("# of responses: {}", example.responses.len()); 19 | for (code, response) in &example.responses { 20 | match (code, &response.body) { 21 | (code, Some(body)) => { 22 | let body = serde_json::to_vec(body)?; 23 | println!(" {}: {} bytes", code, body.len()); 24 | } 25 | (code, None) => println!(" {code}: 0 bytes"), 26 | } 27 | } 28 | } 29 | } 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /autorust/openapi/src/contact.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#contactObject 4 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] 5 | pub struct Contact { 6 | #[serde(skip_serializing_if = "Option::is_none")] 7 | pub name: Option, 8 | // TODO: Make sure the url is a valid URL 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub url: Option, 11 | // TODO: Make sure the email is a valid email 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub email: Option, 14 | } 15 | -------------------------------------------------------------------------------- /autorust/openapi/src/example.rs: -------------------------------------------------------------------------------- 1 | use indexmap::IndexMap; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// This is the root document object for an example. 5 | /// https://github.com/Azure/azure-rest-api-specs/blob/master/documentation/x-ms-examples.md 6 | /// https://github.com/Azure/autorest/blob/main/packages/libs/autorest-schemas/example-schema.json 7 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] 8 | pub struct Example { 9 | pub parameters: IndexMap, 10 | pub responses: IndexMap, 11 | } 12 | 13 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] 14 | pub struct Response { 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub body: Option, 17 | } 18 | -------------------------------------------------------------------------------- /autorust/openapi/src/external_documentation.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] 4 | pub struct ExternalDocumentation { 5 | pub url: String, 6 | #[serde(skip_serializing_if = "Option::is_none")] 7 | pub description: Option, 8 | } 9 | -------------------------------------------------------------------------------- /autorust/openapi/src/header.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::MsEnum; 4 | 5 | /// see Response Headers https://swagger.io/docs/specification/2-0/describing-responses/ 6 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] 7 | pub struct Header { 8 | #[serde(rename = "type")] 9 | pub type_: String, 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub format: Option, 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub description: Option, 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub pattern: Option, 16 | 17 | // https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-client-name 18 | #[serde(rename = "x-ms-client-name", skip_serializing_if = "Option::is_none")] 19 | pub x_ms_client_name: Option, 20 | 21 | #[serde(rename = "x-ms-enum", skip_serializing_if = "Option::is_none")] 22 | pub x_ms_enum: Option, 23 | 24 | #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")] 25 | pub enum_: Vec, 26 | 27 | /// https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-header-collection-prefix 28 | #[serde(rename = "x-ms-header-collection-prefix", skip_serializing_if = "Option::is_none")] 29 | pub x_ms_header_collection_prefix: Option, 30 | } 31 | -------------------------------------------------------------------------------- /autorust/openapi/src/info.rs: -------------------------------------------------------------------------------- 1 | use crate::{Contact, License, MsCodeGenerationSetting}; 2 | use indexmap::IndexMap; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// General information about the API. 6 | /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#info-object 7 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] 8 | #[serde(rename_all = "lowercase")] 9 | pub struct Info { 10 | /// A unique and precise title of the API. 11 | #[serde(skip_serializing_if = "Option::is_none")] 12 | pub title: Option, 13 | /// A semantic version number of the API. 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub description: Option, 16 | #[serde(rename = "termsOfService", skip_serializing_if = "Option::is_none")] 17 | pub terms_of_service: Option, 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub contact: Option, 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub license: Option, 22 | #[serde(skip_serializing_if = "Option::is_none")] 23 | pub version: Option, 24 | 25 | /// enables passing code generation settings via OpenAPI definition 26 | /// (deprecated! Please use configuration files instead.) 27 | /// https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-code-generation-settings 28 | #[serde(rename = "x-ms-code-generation-settings", default, skip_serializing_if = "IndexMap::is_empty")] 29 | pub x_ms_code_generation_settings: IndexMap, 30 | } 31 | -------------------------------------------------------------------------------- /autorust/openapi/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod autorest; 2 | mod contact; 3 | pub mod example; 4 | mod external_documentation; 5 | mod header; 6 | mod info; 7 | mod license; 8 | mod openapi; 9 | mod operation; 10 | mod parameter; 11 | mod paths; 12 | mod reference; 13 | mod schema; 14 | mod security; 15 | mod status_code; 16 | mod tag; 17 | 18 | pub use self::{ 19 | autorest::*, contact::*, external_documentation::*, header::*, info::*, license::*, openapi::*, operation::*, parameter::*, paths::*, 20 | reference::*, schema::*, security::*, status_code::*, tag::*, 21 | }; 22 | 23 | #[derive(Debug, thiserror::Error)] 24 | pub enum Error { 25 | #[error("api-version is missing")] 26 | MissingApiVersion, 27 | } 28 | -------------------------------------------------------------------------------- /autorust/openapi/src/license.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#licenseObject 4 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] 5 | pub struct License { 6 | /// The name of the license type. It's encouraged to use an OSI 7 | /// compatible license. 8 | #[serde(skip_serializing_if = "Option::is_none")] 9 | pub name: Option, 10 | /// The URL pointing to the license. 11 | // TODO: Make sure the url is a valid URL 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub url: Option, 14 | } 15 | -------------------------------------------------------------------------------- /autorust/openapi/src/openapi.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use indexmap::IndexMap; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// This is the root document object for the API specification. 6 | /// It combines what previously was the Resource Listing and API Declaration (version 1.2 and earlier) together into one document. 7 | /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object 8 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct OpenAPI { 11 | /// The Swagger version of this document. 12 | pub swagger: String, 13 | pub info: Info, 14 | /// The host (name or ip) of the API. Example: 'swagger.io' 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub host: Option, 17 | /// The base path to the API. Example: '/api'. 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub base_path: Option, 20 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 21 | pub schemes: Vec, 22 | /// A list of MIME types accepted by the API. 23 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 24 | pub consumes: Vec, 25 | /// A list of MIME types the API can produce. 26 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 27 | pub produces: Vec, 28 | /// Relative paths to the individual endpoints. They must be relative to the 'basePath'. 29 | // #[serde(default, skip_serializing_if = "IndexMap::is_empty")] // do not skip 30 | pub paths: IndexMap>, 31 | /// Relative paths to the individual endpoints. They must be relative to the 'basePath'. 32 | #[serde(default, rename = "x-ms-paths", skip_serializing_if = "IndexMap::is_empty")] 33 | pub x_ms_paths: IndexMap>, 34 | #[serde(default, skip_serializing_if = "IndexMap::is_empty")] 35 | pub definitions: IndexMap>, 36 | #[serde(default, skip_serializing_if = "IndexMap::is_empty")] 37 | pub parameters: IndexMap, 38 | /// mappings to http response codes or "default" 39 | #[serde(default, skip_serializing_if = "IndexMap::is_empty")] 40 | pub responses: IndexMap>, 41 | #[serde(default, skip_serializing_if = "IndexMap::is_empty")] 42 | pub security_definitions: IndexMap, 43 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 44 | pub security: Vec>>, 45 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 46 | pub tags: Vec, 47 | #[serde(skip_serializing_if = "Option::is_none")] 48 | pub external_docs: Option, 49 | 50 | /// replaces the fixed host with a host template that can be replaced with variable parameters 51 | /// https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-parameterized-host 52 | #[serde(rename = "x-ms-parameterized-host", skip_serializing_if = "Option::is_none")] 53 | pub x_ms_parameterized_host: Option, 54 | } 55 | 56 | impl OpenAPI { 57 | pub fn paths(&self) -> IndexMap> { 58 | let mut result = IndexMap::new(); 59 | for (k, v) in self.x_ms_paths.iter() { 60 | result.insert(k.clone(), v.clone()); 61 | } 62 | for (k, v) in self.paths.iter() { 63 | if !self.x_ms_paths.contains_key(k) { 64 | result.insert(k.clone(), v.clone()); 65 | } 66 | } 67 | result 68 | } 69 | pub fn version(&self) -> Result<&str, Error> { 70 | Ok(self.info.version.as_ref().ok_or(Error::MissingApiVersion)?.as_str()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /autorust/openapi/src/operation.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use indexmap::IndexMap; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object 6 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 7 | pub struct Operation { 8 | #[serde(skip_serializing_if = "Option::is_none")] 9 | pub summary: Option, 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub description: Option, 12 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 13 | pub consumes: Vec, 14 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 15 | pub produces: Vec, 16 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 17 | pub schemes: Vec, 18 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 19 | pub tags: Vec, 20 | #[serde(rename = "operationId", skip_serializing_if = "Option::is_none")] 21 | pub operation_id: Option, 22 | /// Required. The list of possible responses as they are returned from executing this operation. 23 | pub responses: IndexMap, 24 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 25 | pub parameters: Vec>, 26 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 27 | pub security: Vec, 28 | 29 | #[serde(rename = "x-ms-pageable", skip_serializing_if = "Option::is_none")] 30 | pub x_ms_pageable: Option, 31 | #[serde(rename = "x-ms-examples", default, skip_serializing_if = "IndexMap::is_empty")] 32 | pub x_ms_examples: MsExamples, 33 | #[serde(rename = "x-ms-long-running-operation", skip_serializing_if = "Option::is_none")] 34 | pub x_ms_long_running_operation: Option, 35 | #[serde(rename = "x-ms-long-running-operation-options", skip_serializing_if = "Option::is_none")] 36 | pub x_ms_long_running_operation_options: Option, 37 | #[serde(rename = "x-ms-request-id", skip_serializing_if = "Option::is_none")] 38 | pub x_ms_request_id: Option, 39 | 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub deprecated: Option, 42 | 43 | #[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")] 44 | pub external_docs: Option, 45 | 46 | /// A reference to the definition that describes object used in the odata filter 47 | #[serde(rename = "x-ms-odata", skip_serializing_if = "Option::is_none")] 48 | pub x_ms_odata: Option, 49 | } 50 | -------------------------------------------------------------------------------- /autorust/openapi/src/parameter.rs: -------------------------------------------------------------------------------- 1 | use crate::{MsParameterGrouping, ReferenceOr, Schema, SchemaCommon}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameter-object 5 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct Parameter { 8 | #[serde(flatten)] 9 | pub common: SchemaCommon, 10 | 11 | /// The name of the parameter. 12 | pub name: String, 13 | 14 | /// may be `header`, `query`, 'path`, `formData` 15 | #[serde(rename = "in")] 16 | pub in_: ParameterIn, 17 | 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub required: Option, 20 | 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub schema: Option>, 23 | 24 | #[serde(skip_serializing_if = "Option::is_none")] 25 | pub allow_empty_value: Option, 26 | 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub collection_format: Option, 29 | 30 | /// provides a mechanism to specify that the global parameter is actually a parameter on the operation and not a client property 31 | /// https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-parameter-location 32 | #[serde(rename = "x-ms-parameter-location", skip_serializing_if = "Option::is_none")] 33 | pub x_ms_parameter_location: Option, 34 | 35 | /// skips URL encoding for path and query parameters 36 | /// https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-skip-url-encoding 37 | #[serde(rename = "x-ms-skip-url-encoding", skip_serializing_if = "Option::is_none")] 38 | pub x_ms_skip_url_encoding: Option, 39 | 40 | /// groups method parameters in generated clients 41 | /// https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-parameter-grouping 42 | #[serde(rename = "x-ms-parameter-grouping", skip_serializing_if = "Option::is_none")] 43 | pub x_ms_parameter_grouping: Option, 44 | 45 | #[serde(rename = "x-ms-client-request-id", skip_serializing_if = "Option::is_none")] 46 | pub x_ms_client_request_id: Option, 47 | 48 | /// https://github.com/Azure/autorest/blob/main/docs/extensions/readme.md#x-ms-header-collection-prefix 49 | #[serde(rename = "x-ms-header-collection-prefix", skip_serializing_if = "Option::is_none")] 50 | pub x_ms_header_collection_prefix: Option, 51 | } 52 | 53 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] 54 | #[serde(rename_all = "camelCase")] 55 | pub enum ParameterIn { 56 | Path, 57 | Query, 58 | Header, 59 | Body, 60 | /// https://swagger.io/docs/specification/2-0/describing-parameters/#form-parameters 61 | FormData, 62 | } 63 | 64 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] 65 | #[serde(rename_all = "camelCase")] 66 | pub enum CollectionFormat { 67 | Csv, 68 | Ssv, 69 | Tsv, 70 | Pipes, 71 | Multi, 72 | } 73 | -------------------------------------------------------------------------------- /autorust/openapi/src/paths.rs: -------------------------------------------------------------------------------- 1 | use crate::{Operation, Parameter, ReferenceOr}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#path-item-object 5 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 6 | pub struct PathItem { 7 | #[serde(skip_serializing_if = "Option::is_none")] 8 | pub get: Option, 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub post: Option, 11 | #[serde(skip_serializing_if = "Option::is_none")] 12 | pub put: Option, 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub patch: Option, 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub delete: Option, 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub options: Option, 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub head: Option, 21 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 22 | pub parameters: Vec>, 23 | } 24 | 25 | impl PathItem { 26 | /// Returns all operations 27 | pub fn operations(&self) -> impl Iterator { 28 | vec![ 29 | self.get.as_ref(), 30 | self.post.as_ref(), 31 | self.put.as_ref(), 32 | self.patch.as_ref(), 33 | self.delete.as_ref(), 34 | self.options.as_ref(), 35 | self.head.as_ref(), 36 | ] 37 | .into_iter() 38 | .flatten() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /autorust/openapi/src/tag.rs: -------------------------------------------------------------------------------- 1 | use crate::ExternalDocumentation; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#tagObject 5 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] 6 | pub struct Tag { 7 | pub name: String, 8 | #[serde(skip_serializing_if = "Option::is_none")] 9 | pub description: Option, 10 | #[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")] 11 | pub external_docs: Option, 12 | } 13 | -------------------------------------------------------------------------------- /autorust/openapi/tests/azure_rest_api_specs.rs: -------------------------------------------------------------------------------- 1 | // cargo test --test azure_rest_api_specs 2 | // These tests require cloning azure-rest-api-specs. 3 | // git clone git@github.com:Azure/azure-rest-api-specs.git ../../../../azure-rest-api-specs 4 | 5 | mod common; 6 | use common::*; 7 | 8 | const PATHS: &[&str] = &[ 9 | // empty tags[] & consumes[] 10 | // "../../../../azure-rest-api-specs/specification/machinelearningservices/data-plane/Microsoft.MachineLearningServices/preview/2019-09-30/execution.json", 11 | "../../../../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json", 12 | "../../../../azure-rest-api-specs/specification/batch/data-plane/Microsoft.Batch/stable/2020-03-01.11.0/BatchService.json", 13 | "../../../../azure-rest-api-specs/specification/security/resource-manager/common/v1/types.json", 14 | "../../../../azure-rest-api-specs/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/stable/2020-04-01/cosmos-db.json", 15 | "../../../../azure-rest-api-specs/specification/alertsmanagement/resource-manager/Microsoft.AlertsManagement/preview/2019-05-05-preview/ActionRules.json", 16 | // https://github.com/Azure/azure-sdk-for-rust/issues/330 17 | // "../../../../azure-rest-api-specs/specification/apimanagement/resource-manager/Microsoft.ApiManagement/stable/2019-12-01/apimapis.json", 18 | "../../../../azure-rest-api-specs/specification/communication/data-plane/Chat/stable/2021-03-07/communicationserviceschat.json", 19 | "../../../../azure-rest-api-specs/specification/eventgrid/data-plane/Microsoft.EventGrid/stable/2018-01-01/EventGrid.json", 20 | "../../../../azure-rest-api-specs/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-02-12/blob.json", 21 | "../../../../azure-rest-api-specs/specification/storage/data-plane/Microsoft.FileStorage/preview/2021-02-12/file.json", 22 | "../../../../azure-rest-api-specs/specification/storage/data-plane/Microsoft.QueueStorage/preview/2018-03-28/queue.json", 23 | "../../../../azure-rest-api-specs/specification/storage/data-plane/Microsoft.StorageDataLake/preview/2020-10-02/DataLakeStorage.json", 24 | "../../../../azure-rest-api-specs/specification/deviceupdate/data-plane/Microsoft.DeviceUpdate/preview/2020-09-01/deviceupdate.json", 25 | "../../../../azure-rest-api-specs/specification/digitaltwins/data-plane/Microsoft.DigitalTwins/stable/2020-10-31/digitaltwins.json", 26 | ]; 27 | 28 | #[test] 29 | fn can_roundtrip_azure_rest_api_specs() -> Result<()> { 30 | assert_roundtrip_eq(PATHS) 31 | } 32 | -------------------------------------------------------------------------------- /autorust/openapi/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use assert_json_diff::assert_json_eq; 2 | use autorust_openapi::OpenAPI; 3 | use camino::Utf8Path; 4 | use serde_json::Value; 5 | use std::fs; 6 | 7 | pub type Error = Box; 8 | pub type Result = std::result::Result; 9 | 10 | pub fn assert_roundtrip_eq + std::fmt::Debug>(paths: &[P]) -> Result<()> { 11 | for path in paths { 12 | println!(" test {path:?}"); 13 | let bytes = fs::read(path.as_ref())?; 14 | let spec: OpenAPI = serde_json::from_slice(&bytes)?; 15 | let a = serde_json::to_value(spec)?; 16 | let b: Value = serde_json::from_slice(&bytes)?; 17 | assert_json_eq!(a, b); 18 | } 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /autorust/openapi/tests/openapi_spec_examples.rs: -------------------------------------------------------------------------------- 1 | // cargo test --test openapi_spec_examples 2 | // These tests require cloning OpenAPI-Specification. 3 | // git clone git@github.com:OAI/OpenAPI-Specification.git ../OpenAPI-Specification 4 | 5 | mod common; 6 | use common::*; 7 | 8 | const PATHS: &[&str] = &[ 9 | "../../../../OpenAPI-Specification/examples/v2.0/json/petstore-minimal.json", 10 | "../../../../OpenAPI-Specification/examples/v2.0/json/petstore.json", 11 | "../../../../OpenAPI-Specification/examples/v2.0/json/petstore-simple.json", 12 | "../../../../OpenAPI-Specification/examples/v2.0/json/petstore-with-external-docs.json", 13 | "../../../../OpenAPI-Specification/examples/v2.0/json/uber.json", 14 | ]; 15 | 16 | #[test] 17 | fn can_roundtrip_openapi_spec_examples() -> Result<()> { 18 | assert_roundtrip_eq(PATHS) 19 | } 20 | -------------------------------------------------------------------------------- /azure_devops_rust_api/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | .env 4 | *.zip 5 | -------------------------------------------------------------------------------- /azure_devops_rust_api/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/artifact_provenance.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // artifact_provenance.rs 5 | // Artifact provenance example, demonstrating how to obtain json 6 | // information about the package's origin; such as identity of publisher 7 | // and, if available, package code repository information. 8 | use anyhow::Result; 9 | use azure_devops_rust_api::artifacts; 10 | use std::env; 11 | 12 | mod utils; 13 | 14 | #[tokio::main] 15 | async fn main() -> Result<()> { 16 | // Get authentication credential 17 | let credential = utils::get_credential()?; 18 | 19 | // Get ADO configuration via environment variables 20 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 21 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 22 | 23 | // Create an artifacts client 24 | println!("Create artifacts client"); 25 | let artifacts_client = artifacts::ClientBuilder::new(credential).build(); 26 | 27 | // Query all the artifact feeds 28 | let feeds = artifacts_client 29 | .feed_management_client() 30 | .get_feeds(&organization, &project) 31 | .await? 32 | .value; 33 | 34 | if let Some(feed) = feeds.first() { 35 | if let Some(feed_id) = &feed.feed_core.id { 36 | let packages = artifacts_client 37 | .artifact_details_client() 38 | .get_packages(&organization, feed_id, &project) 39 | .await? 40 | .value; 41 | 42 | if let Some(package) = packages.first() { 43 | let name = package.name.as_deref().unwrap_or(""); 44 | let id = package.id.as_deref().unwrap_or(""); 45 | let version_id = package 46 | .versions 47 | .first() 48 | .expect("No package version information available") 49 | .id 50 | .as_deref() 51 | .unwrap_or(""); 52 | println!("{:30}{:40}{:40}", name, id, version_id); 53 | 54 | let provenance = artifacts_client 55 | .artifact_details_client() 56 | .get_package_version_provenance( 57 | &organization, 58 | feed_id, 59 | id, 60 | version_id, 61 | &project, 62 | ) 63 | .await? 64 | .provenance; 65 | 66 | println!("{:#?}", provenance); 67 | } 68 | } 69 | } 70 | 71 | Ok(()) 72 | } 73 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/artifacts_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // artifacts_list.rs 5 | // Artifacts list example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::artifacts; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | // Create an artifacts client 22 | println!("Create artifacts client"); 23 | let artifacts_client = artifacts::ClientBuilder::new(credential).build(); 24 | 25 | // Query all the artifact feeds 26 | let feeds = artifacts_client 27 | .feed_management_client() 28 | .get_feeds(&organization, &project) 29 | .await? 30 | .value; 31 | 32 | println!("Found {} feeds", feeds.len()); 33 | for feed in &feeds { 34 | let id = feed.feed_core.id.as_deref().unwrap_or(""); 35 | let name = feed.feed_core.name.as_deref().unwrap_or(""); 36 | let url = feed.url.as_deref().unwrap_or(""); 37 | println!("{:40}{:30}{}", id, name, url); 38 | } 39 | 40 | if let Some(feed) = feeds.first() { 41 | println!("\nExample feed struct:\n{:#?}", feed); 42 | 43 | if let Some(feed_id) = &feed.feed_core.id { 44 | println!("\nFeed packages:"); 45 | let packages = artifacts_client 46 | .artifact_details_client() 47 | .get_packages(&organization, feed_id, &project) 48 | .await? 49 | .value; 50 | 51 | for package in &packages { 52 | let id = package.id.as_deref().unwrap_or(""); 53 | let name = package.name.as_deref().unwrap_or(""); 54 | let url = package.url.as_deref().unwrap_or(""); 55 | println!("{:40}{:30}{}", id, name, url); 56 | } 57 | } 58 | } 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/build_get.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // build_get.rs 5 | // Build get example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::build; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | let build_id: i32 = env::args() 21 | .nth(1) 22 | .expect("Usage: build_get ") 23 | .parse()?; 24 | 25 | // Create a build client 26 | println!("Create build client"); 27 | let build_client = build::ClientBuilder::new(credential).build(); 28 | 29 | // Get specified build 30 | println!("Get build {build_id}"); 31 | let build = build_client 32 | .builds_client() 33 | .get(organization, project, build_id) 34 | .await?; 35 | 36 | println!("Build:\n{:#?}", build); 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/build_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // build_list.rs 5 | // Repository list example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::build; 8 | use std::env; 9 | use time::{ext::NumericalDuration, OffsetDateTime}; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 21 | 22 | // Create a build client 23 | println!("Create build client"); 24 | let build_client = build::ClientBuilder::new(credential).build(); 25 | 26 | // Query all the builds in the past hour 27 | let end_time = OffsetDateTime::now_utc(); 28 | let start_time = end_time - 1.hours(); 29 | 30 | // Get all builds in the specified organization/project in the past hour 31 | println!("Get list"); 32 | let builds = build_client 33 | .builds_client() 34 | .list(organization, project) 35 | .min_time(start_time) 36 | .max_time(end_time) 37 | .await? 38 | .value; 39 | 40 | println!("Found {} builds", builds.len()); 41 | if let Some(build) = builds.first() { 42 | println!("Example build struct: {:#?}", build); 43 | } 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/build_list_continuation_token.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // build_list_continuation_token.rs 5 | // Example demonstrating how to make large queries using continuation tokens. 6 | use anyhow::{anyhow, Context, Result}; 7 | use azure_core::http::headers::HeaderName; 8 | use azure_core::http::StatusCode; 9 | use azure_devops_rust_api::build; 10 | use azure_devops_rust_api::build::models::{Build, BuildList}; 11 | use std::env; 12 | use time::format_description::well_known::Rfc3339; 13 | 14 | mod utils; 15 | 16 | const NUM_BUILD_BATCHES: usize = 5; 17 | 18 | async fn get_builds( 19 | build_client: &build::Client, 20 | organization: &str, 21 | project: &str, 22 | continuation_token: &Option, 23 | ) -> Result<(Vec, Option)> { 24 | let mut list_builder = build_client.builds_client().list(organization, project); 25 | 26 | if let Some(continuation_token) = continuation_token { 27 | println!( 28 | "Query builds with continuation_token: {}", 29 | continuation_token 30 | ); 31 | list_builder = list_builder.continuation_token(continuation_token) 32 | } else { 33 | println!("Query builds with no continuation_token"); 34 | } 35 | 36 | let (status, headers, body) = list_builder.send().await?.into_raw_response().deconstruct(); 37 | 38 | if status != StatusCode::Ok { 39 | println!("Request failed"); 40 | return Err(anyhow!("Request failed")); 41 | } 42 | 43 | let new_continuation_token = 44 | headers.get_optional_string(&HeaderName::from_static("x-ms-continuationtoken")); 45 | 46 | let body_data = body.collect_string().await?; 47 | let build_list: BuildList = serde_json::from_str(&body_data) 48 | .with_context(|| format!("Failed to parse BuildList: {}", &body_data))?; 49 | 50 | println!("Received {} builds", build_list.count.unwrap_or(0)); 51 | 52 | Ok((build_list.value, new_continuation_token)) 53 | } 54 | 55 | #[tokio::main] 56 | async fn main() -> Result<()> { 57 | // Get authentication credential 58 | let credential = utils::get_credential()?; 59 | 60 | // Get ADO server configuration via environment variables 61 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 62 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 63 | 64 | // Create a build client 65 | let build_client = build::ClientBuilder::new(credential).build(); 66 | 67 | let mut continuation_token = None; 68 | 69 | // Query several batches of builds. Each batch has 1000 builds (by default) 70 | println!("Num build batches: {}", NUM_BUILD_BATCHES); 71 | for batch in 0..NUM_BUILD_BATCHES { 72 | let (builds, new_continuation_token) = 73 | get_builds(&build_client, &organization, &project, &continuation_token).await?; 74 | 75 | if let Some(build) = builds.first() { 76 | println!( 77 | "First build of batch {} start time: {}\n", 78 | batch, 79 | build.start_time.unwrap().format(&Rfc3339)? 80 | ); 81 | } 82 | continuation_token = new_continuation_token; 83 | 84 | if continuation_token.is_none() { 85 | println!("continuation_token is None - exiting"); 86 | } 87 | } 88 | 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/build_list_sync.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // build_list_sync.rs 5 | // Repository list example, demonstrating how to call the async API from sync code 6 | // For more info see: https://tokio.rs/tokio/topics/bridging 7 | use anyhow::Result; 8 | use azure_devops_rust_api::build; 9 | use std::env; 10 | use time::{ext::NumericalDuration, OffsetDateTime}; 11 | 12 | mod utils; 13 | 14 | fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 21 | 22 | // Create a `current_thread` tokio runtime to use when invoking async functions 23 | let rt = tokio::runtime::Builder::new_current_thread() 24 | .enable_all() 25 | .build()?; 26 | 27 | // Create a build client 28 | println!("Create build client"); 29 | let build_client = build::ClientBuilder::new(credential.clone()).build(); 30 | 31 | // Query all the builds in the past hour 32 | let end_time = OffsetDateTime::now_utc(); 33 | let start_time = end_time - 1.hours(); 34 | 35 | // Get all builds in the specified organization/project in the past hour 36 | let builds = rt 37 | .block_on(async { 38 | println!("Get list"); 39 | build_client 40 | .builds_client() 41 | .list(&organization, &project) 42 | .min_time(start_time) 43 | .max_time(end_time) 44 | .await 45 | })? 46 | .value; 47 | 48 | println!("Found {} builds", builds.len()); 49 | if let Some(build) = builds.first() { 50 | println!("Example build struct: {:#?}", build); 51 | } 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/build_source_providers_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // build_source_provider_list.rs 5 | // Source provider list example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::build; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | // Create a build client 22 | println!("Create build client"); 23 | let build_client = build::ClientBuilder::new(credential).build(); 24 | 25 | // Get all source providers in the specified organization/project 26 | println!("Get source providers list"); 27 | let source_providers = build_client 28 | .source_providers_client() 29 | .list(organization, project) 30 | .await? 31 | .value; 32 | 33 | println!("Found {} source_providers", source_providers.len()); 34 | println!("\nSource Providers:\n {:#?}", source_providers); 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/core_org_projects.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // core_org_projects.rs 5 | // Projects from Orgs example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::core; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | 20 | // Max number of projects to be fetched, default max is 100 21 | let top_projects: i32 = 500; 22 | 23 | // Create core client 24 | let core_client = core::ClientBuilder::new(credential).build(); 25 | 26 | let org_projects = core_client 27 | .projects_client() 28 | .list(&organization) 29 | .top(top_projects) 30 | .await?; 31 | 32 | println!("{:#?}", org_projects); 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/core_project_teams.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // core_project_teams.rs 5 | // Project Teams from organization example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::core; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | // Max number of teams to be fetched, default max is 100 22 | let top_teams: i32 = 500; 23 | 24 | // Create core client 25 | let core_client = core::ClientBuilder::new(credential).build(); 26 | 27 | let project_teams = core_client 28 | .teams_client() 29 | .get_teams(&organization, &project) 30 | .top(top_teams) 31 | .await? 32 | .value; 33 | 34 | // Display team names 35 | println!("\nProject teams:"); 36 | for team in project_teams.iter() { 37 | if let Some(name) = &team.web_api_team_ref.name { 38 | println!("{}", name); 39 | } 40 | } 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/distributed_task.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | // distributed_task.rs 4 | // Distributed Task example 5 | use anyhow::Result; 6 | use azure_devops_rust_api::distributed_task; 7 | use std::env; 8 | 9 | mod utils; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | // Get authentication credential 14 | let credential = utils::get_credential()?; 15 | 16 | // Get ADO server configuration via environment variables 17 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 18 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 19 | 20 | // Create distributed task client 21 | let distributed_task_client = distributed_task::ClientBuilder::new(credential).build(); 22 | 23 | // Get a list of agent pools for the org 24 | println!("Agents pools for the org are:"); 25 | let distributed_task_agents_pools = distributed_task_client 26 | .pools_client() 27 | .get_agent_pools(&organization) 28 | .await? 29 | .value; 30 | println!("{:#?}", distributed_task_agents_pools); 31 | 32 | // Get a list of agent queues for the project 33 | println!("Agents queues for the project are:"); 34 | let distributed_task_agent_queues = distributed_task_client 35 | .queues_client() 36 | .get_agent_queues(&organization, &project) 37 | .await? 38 | .value; 39 | println!("{:#?}", distributed_task_agent_queues); 40 | 41 | // Get all variable groups for the project 42 | println!("Variable groups for the project are:"); 43 | let distributed_task_variable_groups = distributed_task_client 44 | .variablegroups_client() 45 | .get_variable_groups(&organization, &project) 46 | .await? 47 | .value; 48 | println!("{:#?}", distributed_task_variable_groups); 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/extension_management_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // extension_management_list.rs 5 | // extension_management_list example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::extension_management; 8 | use azure_devops_rust_api::extension_management::models::InstalledExtension; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO server configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | 21 | // Create a extension_management_client 22 | let extension_management_client = extension_management::ClientBuilder::new(credential).build(); 23 | 24 | // Get all the installed extensions 25 | let installed_extensions = extension_management_client 26 | .installed_extensions_client() 27 | .list(organization) 28 | .await? 29 | .value; 30 | 31 | println!("Installed extensions:"); 32 | for extension in installed_extensions.iter() { 33 | if let InstalledExtension { 34 | extension_name: Some(name), 35 | publisher_name: Some(publisher), 36 | version: Some(version), 37 | .. 38 | } = extension 39 | { 40 | println!("{:65}{:24}{:40}", name, version, publisher); 41 | } 42 | } 43 | 44 | if let Some(extension) = installed_extensions.first() { 45 | println!("\nExample extension:\n{:#?}", extension); 46 | } 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_commit_changes.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // get_commits.rs 5 | // Getting all the commits from a PR example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let repository_name = env::args() 20 | .nth(1) 21 | .expect("Usage: git_commit_changes "); 22 | let commit_id = env::args() 23 | .nth(2) 24 | .expect("Usage: git_commit_changes "); 25 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 26 | 27 | // Create a git client 28 | let git_client = git::ClientBuilder::new(credential).build(); 29 | 30 | let commit_changes = git_client 31 | .commits_client() 32 | .get_changes(&organization, &commit_id, &repository_name, &project) 33 | .await?; 34 | println!("Commit changes:\n{:#?}", commit_changes); 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_diff_files_between_base_and_target_branch.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_diff_files_between_base_and_target_branch.rs 5 | // Getting files modified in the branch 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::collections::HashSet; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO server configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 21 | 22 | // Getting repo, base and target branch name from command line 23 | let repository_name = env::args() 24 | .nth(1) 25 | .expect("Usage: git_diff_files_between_base_and_target_branch "); 26 | 27 | let base_branch_name = env::args() 28 | .nth(2) 29 | .expect("Usage: git_diff_files_between_base_and_target_branch "); 30 | 31 | let target_branch_name = env::args() 32 | .nth(3) 33 | .expect("Usage: git_diff_files_between_base_and_target_branch "); 34 | 35 | // Set the max number of commits to get, default is 100 36 | let top_commits: i32 = 500; 37 | 38 | // Create a git client 39 | let git_client = git::ClientBuilder::new(credential).build(); 40 | 41 | // Get diff client between branches 42 | let diffs = git_client 43 | .diffs_client() 44 | .get(organization, repository_name, project) 45 | .top(top_commits) 46 | .base_version(base_branch_name) 47 | .target_version(target_branch_name) 48 | .await? 49 | .changes; 50 | 51 | // Record unique filenames which are changed in the PR 52 | let mut files_diff_between_branches = HashSet::::new(); 53 | 54 | // Get files name which are present in the target branch 55 | for diff in diffs.iter() { 56 | if let Some(item) = &diff.change.item { 57 | let git_object_type = item["gitObjectType"].as_str().unwrap(); 58 | if git_object_type == "blob" { 59 | let file_name = item["path"].as_str().unwrap(); 60 | files_diff_between_branches.insert(file_name.to_string()); 61 | } 62 | } 63 | } 64 | 65 | // Unique files changed in the PR 66 | println!("These files are modified in pr(target) branch:"); 67 | for file_name in files_diff_between_branches.iter() { 68 | println!("{}", file_name) 69 | } 70 | 71 | Ok(()) 72 | } 73 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_items_get.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_items_get.rs 5 | // Git items (files and folders) get example. 6 | use anyhow::Context as AnyhowContext; 7 | use anyhow::Result; 8 | use azure_devops_rust_api::git; 9 | use futures::StreamExt; 10 | use std::env; 11 | use std::io::Write; 12 | 13 | mod utils; 14 | use utils::AcceptZipPolicy; 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<()> { 18 | // Get authentication credential 19 | let credential = utils::get_credential()?; 20 | 21 | // Get ADO server configuration via environment variables 22 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 23 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 24 | let repository_name = env::args() 25 | .nth(1) 26 | .expect("Usage: git_items_get "); 27 | let file_path = env::args() 28 | .nth(2) 29 | .expect("Usage: git_items_get "); 30 | 31 | // Create a git client 32 | let git_client = git::ClientBuilder::new(credential.clone()).build(); 33 | 34 | // To get the file metadata, it appears that you need to specify format as "json" 35 | let item = git_client 36 | .items_client() 37 | .get(&organization, &repository_name, &file_path, &project) 38 | .format("json") 39 | .await?; 40 | 41 | println!("\n{file_path} metadata:\n{:#?}", item); 42 | 43 | // If no format is specified, the file contents are returned 44 | let rsp = git_client 45 | .items_client() 46 | .get(&organization, &repository_name, &file_path, &project) 47 | .send() 48 | .await? 49 | .into_raw_response(); 50 | 51 | let file_data = rsp.into_raw_body().collect_string().await?; 52 | println!("\n{file_path} contents:\n{}", file_data); 53 | 54 | // Download the entire repo as a zip archive. 55 | 56 | // It is possible to download files as a zip archive by setting the 57 | // `accept` header to `application/zip`. The API doesn't directly 58 | // support this, but we can work around this by creating a new client 59 | // with a policy to set the `accept` header. 60 | let git_zip_client = git::ClientBuilder::new(credential) 61 | .per_call_policies(vec![AcceptZipPolicy::new_policy()]) 62 | .build(); 63 | 64 | // If the accept header specifies "application/zip", the files are returned in a zip archive. 65 | // If the `file_path` parameter is empty, the entire repository is returned. 66 | let rsp = git_zip_client 67 | .items_client() 68 | .get(&organization, &repository_name, "", &project) 69 | .download(true) 70 | .send() 71 | .await? 72 | .into_raw_response(); 73 | 74 | let mut body = rsp.into_raw_body(); 75 | let mut file = std::fs::File::create("full_repo.zip")?; 76 | while let Some(chunk_result) = body.next().await { 77 | let chunk = chunk_result.context("Error reading chunk")?; 78 | println!("Writing chunk"); 79 | file.write_all(&chunk)?; 80 | } 81 | println!("Done writing full_repo.zip"); 82 | 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_items_get_items_batch.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_items_get_items_batch.rs 5 | // Git get items (files and folders) batch example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use azure_devops_rust_api::git::models::{GitItemDescriptor, GitItemRequestData}; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO server configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 21 | let repository_name = env::args() 22 | .nth(1) 23 | .expect("Usage: git_items_get "); 24 | let file_path = env::args() 25 | .nth(2) 26 | .expect("Usage: git_items_get "); 27 | 28 | let git_item_request_data = GitItemRequestData { 29 | include_content_metadata: Some(true), 30 | include_links: Some(true), 31 | item_descriptors: vec![GitItemDescriptor { 32 | path: Some(file_path.clone()), 33 | recursion_level: None, 34 | version: None, 35 | version_options: None, 36 | version_type: None, 37 | }], 38 | latest_processed_change: Some(true), 39 | }; 40 | 41 | // Create a git client 42 | let git_client = git::ClientBuilder::new(credential.clone()).build(); 43 | 44 | let items_batch = git_client 45 | .items_client() 46 | .get_items_batch( 47 | &organization, 48 | git_item_request_data, 49 | &repository_name, 50 | &project, 51 | ) 52 | .await?; 53 | 54 | println!("\n{file_path} metadata items batch:\n{:#?}", items_batch); 55 | 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_items_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_items_list.rs 5 | // Git items (files and folders) list example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | let repository_name = env::args() 21 | .nth(1) 22 | .expect("Usage: git_items_list "); 23 | 24 | // Create a git client 25 | let git_client = git::ClientBuilder::new(credential).build(); 26 | 27 | // Get all items (files and folders) in the specified repository 28 | let items = git_client 29 | .items_client() 30 | .list(organization, repository_name, project) 31 | .recursion_level("Full") 32 | .await? 33 | .value; 34 | 35 | for item in items.iter() { 36 | if let Some(path) = &item.item_model.path { 37 | println!("{path}"); 38 | } 39 | } 40 | println!("{} items found", items.len()); 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_policy_config_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_policy_config_list.rs 5 | // Git policy configuration list example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | let repo_name = env::args() 21 | .nth(1) 22 | .expect("Usage: git_items_list "); 23 | 24 | // Create a git client 25 | let git_client = git::ClientBuilder::new(credential).build(); 26 | 27 | // Get the specified repo by name, as we need to obtain the repo ID. 28 | let repo = git_client 29 | .repositories_client() 30 | .get_repository(&organization, &repo_name, &project) 31 | .await?; 32 | 33 | // Get all policies configured on the specified repository. 34 | // Many git repository APIs take either a repository ID or a repository name, 35 | // but this particular API only accepts a repository ID. 36 | let policies = git_client 37 | .policy_configurations_client() 38 | .get(organization, project) 39 | .repository_id(repo.id) 40 | .await? 41 | .value; 42 | 43 | println!("Policies:\n{:#?}", policies); 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_pr_commits.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_pr_commits.rs 5 | // Getting all the commits from a PR example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let repository_name = env::args() 20 | .nth(1) 21 | .expect("Usage: git_pr_commits "); 22 | let pull_request_id: i32 = env::args() 23 | .nth(2) 24 | .expect("Usage: git_pr_commits ") 25 | .parse() 26 | .unwrap(); 27 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 28 | // Set the max number of commits to get, default is 100 29 | let top_commits: i32 = 500; 30 | 31 | // Create a git client 32 | let git_client = git::ClientBuilder::new(credential).build(); 33 | 34 | // Get Commits for the PR 35 | let pr_commits = git_client 36 | .pull_request_commits_client() 37 | .get_pull_request_commits(&organization, &repository_name, pull_request_id, &project) 38 | .top(top_commits) 39 | .await?; 40 | println!("{:#?}", pr_commits); 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_pr_create.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_pr_create.rs 5 | // Create Pull Request example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use git::models::{GitPullRequestCreateOptions, WebApiCreateTagRequestData}; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | const USAGE: &str = 19 | "Usage: git_pr_create <description>"; 20 | 21 | // Get ADO server configuration via environment variables 22 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 23 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 24 | 25 | let repo_name = env::args().nth(1).expect(USAGE); 26 | let src_branch: String = env::args().nth(2).expect(USAGE); 27 | let target_branch: String = env::args().nth(3).expect(USAGE); 28 | let title: String = env::args().nth(4).expect(USAGE); 29 | let description: String = env::args().nth(5).expect(USAGE); 30 | 31 | // Create a git client 32 | let git_client = git::ClientBuilder::new(credential).build(); 33 | 34 | // Create GitPullRequestCreateOptions with all the mandatory parameters 35 | println!("Create PR to merge {} => {}", src_branch, target_branch); 36 | let mut pr_create_options = GitPullRequestCreateOptions::new( 37 | // Need to specify full git refs path 38 | format!("refs/heads/{src_branch}"), 39 | format!("refs/heads/{target_branch}"), 40 | title, 41 | ); 42 | 43 | // Set any additional optional parameters 44 | pr_create_options.description = Some(description); 45 | // Label creation is unfortunately currently not very ergonomic... 46 | pr_create_options.labels = vec![ 47 | WebApiCreateTagRequestData::new("example_label1".to_string()), 48 | WebApiCreateTagRequestData::new("example_label2".to_string()), 49 | ]; 50 | 51 | // Define the new PR 52 | let pr = git_client 53 | .pull_requests_client() 54 | .create(organization, repo_name, project, pr_create_options) 55 | .await?; 56 | println!("Created PR:\n{:#?}", pr); 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_pr_files_changed.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_pr_files_changed.rs 5 | // Getting all the files changed in a PR example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::collections::HashSet; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO server configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let repository_name = env::args() 21 | .nth(1) 22 | .expect("Usage: git_pr_files_changed <repository-name> <pull_request_id>"); 23 | let pull_request_id: i32 = env::args() 24 | .nth(2) 25 | .expect("Usage: git_pr_files_changed <repository-name> <pull_request_id>") 26 | .parse() 27 | .unwrap(); 28 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 29 | 30 | // Set the max number of commits to get, default is 100 31 | let top_commits: i32 = 500; 32 | 33 | // Create a git client 34 | let git_client = git::ClientBuilder::new(credential).build(); 35 | 36 | // Get Commits for the PR 37 | let pr_commits = git_client 38 | .pull_request_commits_client() 39 | .get_pull_request_commits(&organization, &repository_name, pull_request_id, &project) 40 | .top(top_commits) 41 | .await? 42 | .value; 43 | 44 | // Record unique filenames which are changed in the PR 45 | let mut files_changed = HashSet::<String>::new(); 46 | 47 | // Get each commit in the PR 48 | println!("\nCommits:"); 49 | for commit in pr_commits.iter() { 50 | let commit_id = commit.commit_id.clone().unwrap_or("".to_string()); 51 | let comment = match &commit.comment { 52 | Some(comment) => comment.clone(), 53 | _ => "".to_string(), 54 | }; 55 | println!("{} {}", commit_id, comment); 56 | 57 | // Get the commit changes in a commit 58 | let pr_commits_changes = git_client 59 | .commits_client() 60 | .get_changes(&organization, commit_id, &repository_name, &project) 61 | .await? 62 | .changes; 63 | 64 | // Get files changed in the commit 65 | for change in pr_commits_changes.iter() { 66 | if let Some(item) = &change.change.item { 67 | // We are only interested in files not directories. 68 | // files are "blob" type, directories are "folder" type. 69 | if let (Some("blob"), Some(filename)) = 70 | (item["git_object_type"].as_str(), item["path"].as_str()) 71 | { 72 | files_changed.insert(filename.to_string()); 73 | } 74 | } 75 | } 76 | } 77 | println!("\nChanged files:"); 78 | // Unique files changed in the PR 79 | for filename in files_changed.iter() { 80 | println!("{}", filename) 81 | } 82 | 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_pr_work_items.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_pr_work_items.rs 5 | // Example: Getting work items(s) associated with a PR 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let repository_name = env::args() 20 | .nth(1) 21 | .expect("Usage: git_pr_work_items <repository-name> <pull_request_id>"); 22 | let pull_request_id: i32 = env::args() 23 | .nth(2) 24 | .expect("Usage: git_pr_work_items <repository-name> <pull_request_id>") 25 | .parse() 26 | .unwrap(); 27 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 28 | 29 | // Create a "git" client 30 | let git_client = git::ClientBuilder::new(credential).build(); 31 | 32 | // Get work item(s) associated with PR 33 | let pr_work_items = git_client 34 | .pull_request_work_items_client() 35 | .list(&organization, &repository_name, pull_request_id, &project) 36 | .await?; 37 | 38 | println!("PR work items:"); 39 | println!("{:#?}", pr_work_items); 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_repo_download_zip.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_items_list.rs 5 | // Git items (files and folders) list example. 6 | use anyhow::Context as AnyhowContext; 7 | use anyhow::Result; 8 | use azure_devops_rust_api::git; 9 | use azure_devops_rust_api::git::models::git_item::GitObjectType; 10 | use azure_devops_rust_api::git::models::GitItem; 11 | use futures::StreamExt; 12 | use std::env; 13 | use std::io::Write; 14 | 15 | mod utils; 16 | use utils::AcceptZipPolicy; 17 | 18 | /// Get all items (files and folders) in the specified repository 19 | async fn all_repo_items( 20 | git_client: &git::Client, 21 | organization: &str, 22 | repository_name: &str, 23 | project: &str, 24 | ) -> Result<Vec<GitItem>> { 25 | let items = git_client 26 | .items_client() 27 | .list(organization, repository_name, project) 28 | .recursion_level("Full") 29 | .await? 30 | .value; 31 | Ok(items) 32 | } 33 | 34 | /// Get the object ids of all blobs in the specified git items 35 | fn repo_blob_ids(items: &[GitItem]) -> Vec<String> { 36 | items 37 | .iter() 38 | .filter(|item| item.git_object_type == Some(GitObjectType::Blob)) 39 | .filter_map(|item| item.object_id.clone()) 40 | .collect() 41 | } 42 | 43 | #[tokio::main] 44 | async fn main() -> Result<()> { 45 | // Get authentication credential 46 | let credential = utils::get_credential()?; 47 | 48 | // Get ADO server configuration via environment variables 49 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 50 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 51 | let repository_name = env::args() 52 | .nth(1) 53 | .expect("Usage: git_items_list <repository-name>"); 54 | 55 | // Create a git client 56 | let git_client = git::ClientBuilder::new(credential.clone()).build(); 57 | 58 | // Get all items (files and folders) in the specified repository 59 | let items = all_repo_items(&git_client, &organization, &repository_name, &project).await?; 60 | let blob_ids = repo_blob_ids(&items); 61 | 62 | for blob_id in blob_ids.iter() { 63 | println!("{blob_id}"); 64 | } 65 | println!("{} blobs found", blob_ids.len()); 66 | 67 | // Download all the repo blobs as a zip archive. 68 | // 69 | // Note that this is not the same as downloading the entire repo as a zip archive. 70 | // The repo zip archive includes the repo metadata, whereas this only includes the blobs. 71 | // 72 | // This is useful if you want to download the blobs for a subset of the repo. 73 | // For example, if you want to download the blobs for a specific branch, you can 74 | // first get the branch name, then get the commit id for the branch, then get the 75 | // tree id for the commit, then get the items for the tree, then get the blob ids 76 | // for the items, then download the blobs. 77 | // 78 | // NOTE: The filenames in the zip archive are the blob ids. 79 | 80 | // The current version of the API doesn't set the `accept` header correctly for `get_blobs_zip()`. 81 | // Work around this by adding a custom policy to set the header. 82 | let git_zip_client = git::ClientBuilder::new(credential) 83 | .per_call_policies(vec![AcceptZipPolicy::new_policy()]) 84 | .build(); 85 | 86 | let mut body = git_zip_client 87 | .blobs_client() 88 | .get_blobs_zip(organization, blob_ids, repository_name, project) 89 | .send() 90 | .await? 91 | .into_raw_response() 92 | .into_raw_body(); 93 | 94 | let mut file = std::fs::File::create("blobs.zip")?; 95 | while let Some(chunk_result) = body.next().await { 96 | let chunk = chunk_result.context("Error reading chunk")?; 97 | println!("Writing chunk"); 98 | file.write_all(&chunk)?; 99 | } 100 | println!("Done writing blobs.zip"); 101 | 102 | Ok(()) 103 | } 104 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_repo_get.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_repo_get.rs 5 | // Repository get example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | let repo_name = env::args() 21 | .nth(1) 22 | .expect("Usage: git_repo_get <repository-name>"); 23 | 24 | // Create a "git" client 25 | let git_client = git::ClientBuilder::new(credential).build(); 26 | 27 | // Get the specified repo 28 | let repo = git_client 29 | .repositories_client() 30 | .get_repository(&organization, &repo_name, &project) 31 | .await?; 32 | println!("{:#?}", repo); 33 | 34 | // Get up to 10 pull requests on the specified repo 35 | let prs = git_client 36 | .pull_requests_client() 37 | .get_pull_requests(&organization, &repo.id, &project) 38 | .top(10) 39 | .await? 40 | .value; 41 | 42 | println!("\nFound {} pull requests", prs.len()); 43 | for pr in &prs { 44 | println!("{:<8}{}", pr.pull_request_id, pr.title.as_ref().unwrap()); 45 | } 46 | 47 | if let Some(pr) = prs.first() { 48 | println!("\nExample PR struct:"); 49 | println!("{:#?}", pr); 50 | } 51 | 52 | // Get up to 10 refs on the specified repo 53 | let git_refs = git_client 54 | .refs_client() 55 | .list(&organization, &repo.id, &project) 56 | .top(10) 57 | .await? 58 | .value; 59 | 60 | println!("\nGot {} refs", git_refs.len()); 61 | for git_ref in &git_refs { 62 | println!("{:<50}{}", git_ref.name, git_ref.object_id); 63 | } 64 | 65 | if let Some(git_ref) = git_refs.first() { 66 | println!("\nExample ref struct:"); 67 | println!("{:#?}", git_ref); 68 | } 69 | 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_repo_get_raw_rsp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_repo_get_raw_rsp.rs 5 | // Repository get example, demonstrating how to obtain the raw response 6 | // which enables inspection of the response status, headers and body. 7 | use anyhow::Result; 8 | use azure_devops_rust_api::git; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 21 | let repo_name = env::args() 22 | .nth(1) 23 | .expect("Usage: git_repo_get_raw <repository-name>"); 24 | 25 | // Create a "git" client 26 | let git_client = git::ClientBuilder::new(credential).build(); 27 | 28 | // Invoke the operation via `send()` rather than `into_future()` to get a raw response 29 | let rsp = git_client 30 | .repositories_client() 31 | .get_repository(&organization, &repo_name, &project) 32 | .send() 33 | .await? 34 | .into_raw_response(); 35 | 36 | // Display the raw response details 37 | println!("status: {:#?}", rsp.status()); 38 | println!("headers:\n{:#?}", rsp.headers()); 39 | println!("body:\n{:#?}", rsp.into_raw_body().collect_string().await?); 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/git_repo_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // git_repo_list.rs 5 | // Repository list example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::git; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | // Create a git client 22 | let git_client = git::ClientBuilder::new(credential).build(); 23 | 24 | // Get all repositories in the specified organization/project 25 | let repos = git_client 26 | .repositories_client() 27 | .list(organization, project) 28 | .await? 29 | .value; 30 | 31 | for repo in repos.iter() { 32 | println!("{}", repo.name); 33 | } 34 | println!("{} repos found", repos.len()); 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/graph_query.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // graph_query.rs 5 | // Graph example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::graph; 8 | use azure_devops_rust_api::graph::models::GraphSubjectQuery; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let name = env::args().nth(1).expect("Usage: graph_query <name>"); 21 | 22 | // Create a "graph" client 23 | let graph_client = graph::ClientBuilder::new(credential).build(); 24 | 25 | // Create a query for a user with the specified name 26 | let query = GraphSubjectQuery { 27 | query: Some(name.to_string()), 28 | scope_descriptor: None, 29 | subject_kind: vec!["User".to_string()], 30 | }; 31 | 32 | // Query the specified user 33 | let subjects = graph_client 34 | .subject_query_client() 35 | .query(&organization, query) 36 | .await? 37 | .value; 38 | 39 | println!("Found {} subjects", subjects.len()); 40 | if let Some(subject) = subjects.first() { 41 | println!("subject: {:#?}", subject); 42 | } 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/hooks_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | // hooks_list.rs 4 | // Service hooks example 5 | use anyhow::Result; 6 | use azure_devops_rust_api::hooks; 7 | use std::env; 8 | 9 | mod utils; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | // Get authentication credential 14 | let credential = utils::get_credential()?; 15 | 16 | // Get ADO server configuration via environment variables 17 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 18 | // Create service hook client 19 | let hook_client = hooks::ClientBuilder::new(credential).build(); 20 | 21 | // Get all consumers 22 | println!("The service hook consumers are:"); 23 | let service_hook_consumers = hook_client 24 | .consumers_client() 25 | .list(&organization) 26 | .await? 27 | .value; 28 | println!("{:#?}", service_hook_consumers); 29 | 30 | // Get all publishers 31 | println!("The service hook publishers are:"); 32 | let service_hook_publishers = hook_client 33 | .publishers_client() 34 | .list(&organization) 35 | .await? 36 | .value; 37 | println!("{:#?}", service_hook_publishers); 38 | 39 | // Get all subscriptions 40 | println!("The service hook subscriptions are:"); 41 | let service_hook_subscriptions = hook_client 42 | .subscriptions_client() 43 | .list(&organization) 44 | .await? 45 | .value; 46 | println!("{:#?}", service_hook_subscriptions); 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/ims_query.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // ims_query.rs 5 | // Identity query example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::ims; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let user_email = env::args() 20 | .nth(1) 21 | .expect("Usage: identity_query <email_address>"); 22 | 23 | // Create an "ims" client 24 | let ims_client = ims::ClientBuilder::new(credential).build(); 25 | 26 | // Query the specified user 27 | let identities = ims_client 28 | .identities_client() 29 | .read_identities(organization) 30 | .search_filter("General") 31 | .filter_value(&user_email) 32 | .await? 33 | .value; 34 | 35 | println!("Found {} identities", identities.len()); 36 | for identity in &identities { 37 | if let Some(id) = &identity.identity_base.id { 38 | println!("{id} {user_email}"); 39 | } 40 | } 41 | 42 | if let Some(identity) = identities.first() { 43 | println!("{:#?}", identity); 44 | } 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/member_entitlement_management.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | // member_entitlement_management.rs 4 | // Member entitlement management example 5 | use anyhow::Result; 6 | use azure_devops_rust_api::member_entitlement_management; 7 | use std::env; 8 | 9 | mod utils; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | // Get authentication credential 14 | let credential = utils::get_credential()?; 15 | 16 | // Get ADO server configuration via environment variables 17 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 18 | 19 | // Create member_entitlement_management client 20 | let mem_client = member_entitlement_management::ClientBuilder::new(credential).build(); 21 | 22 | // Get entitlement summary 23 | println!("Entitlement summary:"); 24 | let entitlement_summary = mem_client 25 | .user_entitlement_summary_client() 26 | .get(organization) 27 | .select("AccessLevels,Licenses,Projects,Groups") 28 | .await?; 29 | println!("{:#?}", entitlement_summary); 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/permissions_report.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | // permissions_report.rs 4 | // Permissions report example 5 | use anyhow::Result; 6 | use azure_devops_rust_api::permissions_report; 7 | use std::env; 8 | 9 | mod utils; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | // Get authentication credential 14 | let credential = utils::get_credential()?; 15 | 16 | // Get ADO server configuration via environment variables 17 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 18 | 19 | // Create permissions_report client 20 | let permissions_report_client = permissions_report::ClientBuilder::new(credential).build(); 21 | 22 | // Get Permissions reports 23 | println!("Permissions reports:"); 24 | let permissions_reports = permissions_report_client 25 | .permissions_report_client() 26 | .list(&organization) 27 | .await? 28 | .value; 29 | println!("{:#?}", permissions_reports); 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/pipeline_preview.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // pipeline_preview.rs 5 | // Pipeline preview example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::pipelines; 8 | use azure_devops_rust_api::pipelines::models::{Pipeline, RunPipelineParameters}; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 21 | let pipeline_name = env::args().nth(1).expect("Usage: pipeline_preview <name>"); 22 | 23 | // Create a pipelines client 24 | let pipelines_client = pipelines::ClientBuilder::new(credential).build(); 25 | 26 | // List all pipelines in the specified organization/project 27 | let pipelines = pipelines_client 28 | .pipelines_client() 29 | .list(&organization, &project) 30 | .await? 31 | .value; 32 | println!("Total pipelines: {}", pipelines.len()); 33 | 34 | // Filter the pipelines to just those that contain the specified name 35 | println!("\nMatched pipelines:"); 36 | let matched_pipelines: Vec<Pipeline> = pipelines 37 | .iter() 38 | .filter(|pipeline| pipeline.name.contains(&pipeline_name)) 39 | .cloned() 40 | .collect(); 41 | 42 | if let Some(pipeline) = matched_pipelines.first() { 43 | // Demonstrate how to query a preview of pipeline YAML... 44 | // Define the pipeline params 45 | let run_pipeline_params = RunPipelineParameters { 46 | preview_run: Some(true), 47 | resources: None, 48 | stages_to_skip: vec![], 49 | template_parameters: None, 50 | variables: None, 51 | yaml_override: None, 52 | }; 53 | 54 | // Create a preview client 55 | let preview_client = pipelines_client.preview_client(); 56 | 57 | // Request a preview of the specified pipeline 58 | let preview = preview_client 59 | .preview(&organization, run_pipeline_params, &project, pipeline.id) 60 | .await?; 61 | 62 | // Display the full pipeline YAML 63 | if let Some(final_yaml) = preview.final_yaml { 64 | println!("Pipeline preview:\n{}", final_yaml); 65 | } 66 | } 67 | 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/pipelines.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // pipelines.rs 5 | // Pipelines example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::pipelines; 8 | use azure_devops_rust_api::pipelines::models::Pipeline; 9 | use std::env; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // Get authentication credential 16 | let credential = utils::get_credential()?; 17 | 18 | // Get ADO server configuration via environment variables 19 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 20 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 21 | let pipeline_name = env::args().nth(1).expect("Usage: pipelines <name>"); 22 | 23 | // Create a pipelines client 24 | let pipelines_client = pipelines::ClientBuilder::new(credential).build(); 25 | 26 | // List all pipelines in the specified organization/project 27 | let pipelines = pipelines_client 28 | .pipelines_client() 29 | .list(&organization, &project) 30 | .await? 31 | .value; 32 | println!("Total pipelines: {}", pipelines.len()); 33 | 34 | // Filter the pipelines to just those that contain the specified name 35 | println!("\nMatched pipelines:"); 36 | let matched_pipelines: Vec<Pipeline> = pipelines 37 | .iter() 38 | .filter(|pipeline| pipeline.name.contains(&pipeline_name)) 39 | .cloned() 40 | .collect(); 41 | 42 | for pipeline in matched_pipelines.iter() { 43 | println!("{:4} {}", pipeline.id, pipeline.name); 44 | } 45 | 46 | if let Some(pipeline) = matched_pipelines.first() { 47 | println!("\nExample pipeline struct from list:"); 48 | println!("{:#?}", pipeline); 49 | 50 | // The pipeline struct returned from list is different from that returned by get. 51 | // Query and display the struct returned by get for comparison. 52 | let pipeline = pipelines_client 53 | .pipelines_client() 54 | .get(&organization, &project, pipeline.id) 55 | .await?; 56 | println!("\nExample pipeline struct from get:"); 57 | println!("{:#?}", pipeline); 58 | 59 | // Use the client to list all runs of the selected pipeline 60 | let runs = pipelines_client 61 | .runs_client() 62 | .list(&organization, &project, pipeline.id) 63 | .await? 64 | .value; 65 | 66 | println!("\nPipeline runs: {}", runs.len()); 67 | // Display [result, state] for each pipeline run 68 | for run in runs.iter() { 69 | let result = match &run.result { 70 | Some(result) => format!("{:?}", result), 71 | None => "-".to_string(), 72 | }; 73 | println!( 74 | "{:8} {:16} {:16} {:14}", 75 | run.run_reference.id, 76 | run.run_reference.name, 77 | result, 78 | format!("{:?}", run.state) 79 | ); 80 | } 81 | } 82 | 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // policy.rs 5 | // Policy example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::policy; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | // Create a policy client 22 | let policy_client = policy::ClientBuilder::new(credential).build(); 23 | 24 | // Get all policy configurations in the specified organization/project 25 | let policy_types = policy_client 26 | .types_client() 27 | .list(&organization, &project) 28 | .await? 29 | .value; 30 | 31 | for policy_type in policy_types.iter() { 32 | let type_ref = &policy_type.policy_type_ref; 33 | println!( 34 | "{} {:32} {}", 35 | type_ref.id, type_ref.display_name, policy_type.description 36 | ); 37 | } 38 | println!("{} policy types found", policy_types.len()); 39 | 40 | let work_item_linking_policy_id = policy_types 41 | .iter() 42 | .find_map(|pt| { 43 | if pt.policy_type_ref.display_name == "Work item linking" { 44 | Some(pt.policy_type_ref.id.clone()) 45 | } else { 46 | None 47 | } 48 | }) 49 | .unwrap(); 50 | println!( 51 | "Work item linking policy id: {}", 52 | work_item_linking_policy_id 53 | ); 54 | 55 | let configs = policy_client 56 | .configurations_client() 57 | .list(&organization, &project) 58 | .policy_type(work_item_linking_policy_id) 59 | .await? 60 | .value; 61 | println!("{} work item policy configurations found", configs.len()); 62 | 63 | if let Some(config) = configs.first() { 64 | println!("Example config:\n{:#?}", config); 65 | } 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/release.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | // release.rs 4 | // Release example 5 | use anyhow::Result; 6 | use azure_devops_rust_api::release; 7 | use std::env; 8 | 9 | mod utils; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | // Get authentication credential 14 | let credential = utils::get_credential()?; 15 | 16 | // Get ADO server configuration via environment variables 17 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 18 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 19 | 20 | // Create release client 21 | let release_client = release::ClientBuilder::new(credential).build(); 22 | let folder_path = r"\".to_string(); 23 | 24 | // Get list of approvals 25 | println!("Approvals:"); 26 | let approvals = release_client 27 | .approvals_client() 28 | .list(&organization, &project) 29 | .await? 30 | .value; 31 | println!("{:#?}", approvals); 32 | 33 | // Get list of folders 34 | println!("\nFolders:"); 35 | let folders = release_client 36 | .folders_client() 37 | .list(&organization, &project, &folder_path) 38 | .await? 39 | .value; 40 | println!("{:#?}", folders); 41 | 42 | // Get list of deployments 43 | println!("\nDeployments:"); 44 | let deployments = release_client 45 | .deployments_client() 46 | .list(&organization, &project) 47 | .await? 48 | .value; 49 | println!("{:#?}", deployments); 50 | 51 | // Get a list of releases 52 | println!("\nReleases:"); 53 | let releases = release_client 54 | .releases_client() 55 | .list(&organization, &project) 56 | .await? 57 | .value; 58 | println!("{:#?}", releases); 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/release_get_release.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | // release_get_release.rs 4 | // Get a specific release example 5 | use anyhow::Result; 6 | use azure_devops_rust_api::release; 7 | use std::env; 8 | 9 | mod utils; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | // Get authentication credential 14 | let credential = utils::get_credential()?; 15 | 16 | // Get ADO server configuration via environment variables 17 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 18 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 19 | 20 | // Create release client 21 | let release_client = release::ClientBuilder::new(credential).build(); 22 | 23 | // Get the release ID from user 24 | let release_id: i32 = env::args() 25 | .nth(1) 26 | .expect("Usage: release_get_specific_release <release_id>") 27 | .parse() 28 | .unwrap(); 29 | 30 | // Query a specific release 31 | println!("\nRelease:"); 32 | let release = release_client 33 | .releases_client() 34 | .get_release(&organization, &project, release_id) 35 | .await?; 36 | println!("{:#?}", release); 37 | 38 | // Get manual interventions on a release 39 | println!("\nManual interventions:"); 40 | let manual_interventions = release_client 41 | .manual_interventions_client() 42 | .list(&organization, &project, release_id) 43 | .await? 44 | .value; 45 | println!("{:#?}", manual_interventions); 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/release_logs.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | // release_logs.rs 4 | // Release logs example. 5 | // The log data is saved as a zip file - use `unzip` to extract 6 | use anyhow::{anyhow, Result}; 7 | use azure_core::http::StatusCode; 8 | use azure_devops_rust_api::release; 9 | use std::env; 10 | use std::fs::File; 11 | use std::io::prelude::*; 12 | 13 | mod utils; 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<()> { 17 | // Get authentication credential 18 | let credential = utils::get_credential()?; 19 | 20 | // Get ADO server configuration via environment variables 21 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 22 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 23 | let release_id: i32 = env::args() 24 | .nth(1) 25 | .expect("Usage: release-logs <release-id> <output-file>") 26 | .parse::<i32>() 27 | .expect("release-id parameter must be an integer"); 28 | let output_file: String = env::args() 29 | .nth(2) 30 | .expect("Usage: release-logs <release-id> <output-file>"); 31 | 32 | // Create release client 33 | let release_client = release::ClientBuilder::new(credential).build(); 34 | 35 | // Get release logs 36 | println!("\nDownloading release logs for release {}", release_id); 37 | let (status, _headers, body) = release_client 38 | .releases_client() 39 | .get_logs(organization, project, release_id) 40 | .send() 41 | .await? 42 | .into_raw_response() 43 | .deconstruct(); 44 | 45 | if status != StatusCode::Ok { 46 | println!("Request failed. status:{}", status); 47 | return Err(anyhow!("Request failed")); 48 | } 49 | 50 | // Write the data as a zipfile 51 | println!("Writing data to zipfile: {}", output_file); 52 | let data = body.collect().await?; 53 | let mut file = File::create(&output_file)?; 54 | file.write_all(&data)?; 55 | println!("Logs saved"); 56 | 57 | println!("Use 'unzip {}' to extract the logs", output_file); 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/search_code.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // search_code.rs 5 | // Search code example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::search; 8 | use azure_devops_rust_api::search::models::{ 9 | CodeSearchRequest, EntitySearchRequest, EntitySearchRequestBase, 10 | }; 11 | use serde_json::json; 12 | use std::env; 13 | 14 | mod utils; 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<()> { 18 | // Get authentication credential 19 | let credential = utils::get_credential()?; 20 | 21 | // Get ADO configuration via environment variables 22 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 23 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 24 | 25 | let repository_name = env::args() 26 | .nth(1) 27 | .expect("Usage: search_code <repository-name> <branch> <search-text>"); 28 | 29 | let branch_name = env::args() 30 | .nth(2) 31 | .expect("Usage: search_code <repository-name> <branch> <search-text>"); 32 | 33 | let search_text = env::args() 34 | .nth(3) 35 | .expect("Usage: search_code <repository-name> <branch> <search-text>"); 36 | 37 | // Create a search client 38 | println!("Create search client"); 39 | let search_client = search::ClientBuilder::new(credential).build(); 40 | 41 | // Create a search request to search within a specific repository and branch. 42 | // You could make the same query without a filter which would search the entire project. 43 | // 44 | // Unfortunately the filters field format is not currently documented. 45 | // 46 | // There is an example request in the REST API documentation which shows a selection of fields 47 | // that can be used in the filter field: 48 | // https://learn.microsoft.com/en-us/rest/api/azure/devops/search/code-search-results/fetch-code-search-results?view=azure-devops-rest-7.1&tabs=HTTP 49 | // 50 | // In testing I found that if you specify a filter, it must include the `Project` field. 51 | let entity_search_request_base = EntitySearchRequestBase { 52 | filters: Some(json!({ 53 | "Project": [ 54 | project 55 | ], 56 | "Repository": [ 57 | repository_name 58 | ], 59 | "Branch": [ 60 | branch_name 61 | ] 62 | })), 63 | search_text: Some(search_text), 64 | }; 65 | 66 | let entity_search_request = EntitySearchRequest { 67 | entity_search_request_base, 68 | top: Some(10), // Must specify `top`, otherwise search fails 69 | ..Default::default() 70 | }; 71 | 72 | let code_search_request = CodeSearchRequest { 73 | entity_search_request, 74 | include_snippet: Some(true), 75 | }; 76 | 77 | // Do the search 78 | println!("Search..."); 79 | let search_results = search_client 80 | .code_search_results_client() 81 | .fetch_code_search_results(organization, code_search_request, project) 82 | .await? 83 | .results; 84 | 85 | println!("Found {} results", search_results.len()); 86 | if let Some(result) = search_results.first() { 87 | println!("Example search result:\n{:#?}", result); 88 | } 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/search_package.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // search_package.rs 5 | // Search package example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::search; 8 | use azure_devops_rust_api::search::models::{ 9 | EntitySearchRequest, EntitySearchRequestBase, PackageSearchRequest, 10 | }; 11 | use std::env; 12 | 13 | mod utils; 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<()> { 17 | // Get authentication credential 18 | let credential = utils::get_credential()?; 19 | 20 | // Get ADO configuration via environment variables 21 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 22 | let search_package_name = 23 | env::var("PKG_NAME").expect("Must define a package name <PKG_NAME> to be searched"); 24 | 25 | // Create a search client 26 | println!("Create search client"); 27 | let search_client = search::ClientBuilder::new(credential).build(); 28 | 29 | let entity_search_request_base = EntitySearchRequestBase { 30 | filters: None, 31 | search_text: Some(search_package_name.to_string()), 32 | }; 33 | 34 | let entity_search_request = EntitySearchRequest { 35 | entity_search_request_base, 36 | top: Some(10), // Must specify `top`, otherwise search fails 37 | ..Default::default() 38 | }; 39 | 40 | let package_search_request = PackageSearchRequest { 41 | entity_search_request, 42 | }; 43 | 44 | // Do the search 45 | println!("Search..."); 46 | let search_results = search_client 47 | .package_search_results_client() 48 | .fetch_package_search_results(organization, package_search_request) 49 | .await? 50 | .results; 51 | 52 | println!("Found {} results", search_results.len()); 53 | if let Some(result) = search_results.first() { 54 | println!("Example search result:\n{:#?}", result); 55 | } 56 | 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/search_repositories.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // search_code.rs 5 | // Search code example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::search; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | let repository_name = env::args() 22 | .nth(1) 23 | .expect("Usage: Repo name to be searched <repository-name>"); 24 | 25 | // Create a search client 26 | println!("Create search client"); 27 | let search_client = search::ClientBuilder::new(credential).build(); 28 | 29 | // Do the search 30 | println!("Search..."); 31 | let search_results = search_client 32 | .repositories_client() 33 | .get(organization, project, repository_name) 34 | .await?; 35 | 36 | println!("{:#?}", search_results); 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/search_work_item.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // search_work_item.rs 5 | // Search work item example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::search; 8 | use azure_devops_rust_api::search::models::{ 9 | EntitySearchRequest, EntitySearchRequestBase, WorkItemSearchRequest, 10 | }; 11 | use std::env; 12 | 13 | mod utils; 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<()> { 17 | // Get authentication credential 18 | let credential = utils::get_credential()?; 19 | 20 | // Get ADO configuration via environment variables 21 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 22 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 23 | let search_work_item_text = env::var("WORK_ITEM_SEARCH_TEXT") 24 | .expect("Must define a text <PKG_NAME> to be searched for work item"); 25 | 26 | // Create a search client 27 | println!("Create search client"); 28 | let search_client = search::ClientBuilder::new(credential).build(); 29 | 30 | let entity_search_request_base = EntitySearchRequestBase { 31 | filters: None, 32 | search_text: Some(search_work_item_text.to_string()), 33 | }; 34 | 35 | let entity_search_request = EntitySearchRequest { 36 | entity_search_request_base, 37 | top: Some(10), // Must specify `top`, otherwise search fails 38 | ..Default::default() 39 | }; 40 | 41 | // define body for the request 42 | let work_item_search_request = WorkItemSearchRequest { 43 | entity_search_request, 44 | }; 45 | 46 | // Do the search 47 | println!("Search..."); 48 | let search_results = search_client 49 | .work_item_search_results_client() 50 | .fetch_work_item_search_results(organization, work_item_search_request, project) 51 | .await? 52 | .results; 53 | 54 | println!("Found {} results", search_results.len()); 55 | 56 | if let Some(result) = search_results.first() { 57 | println!("Example search work item result:\n{:#?}", result); 58 | } 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/service_endpoint.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // service_endpoint.rs 5 | // Service Endpoint (aka "Service Connection") example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::service_endpoint; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | // Create a service_endpoint client 22 | let service_endpoint_client = service_endpoint::ClientBuilder::new(credential).build(); 23 | 24 | // Use the client to list all service endpoints (aka "service connections") 25 | let service_endpoints = service_endpoint_client 26 | .endpoints_client() 27 | .get_service_endpoints(&organization, &project) 28 | .await? 29 | .value; 30 | println!("Total service_endpoints: {}", service_endpoints.len()); 31 | 32 | // Display the returned service endpoints 33 | for endpoint in service_endpoints.iter() { 34 | println!( 35 | "{:38} {:40} {}", 36 | endpoint.id, endpoint.name, endpoint.description 37 | ); 38 | } 39 | 40 | // Display an example service endpoint struct 41 | if let Some(endpoint) = service_endpoints.first() { 42 | println!("\nExample service_endpoint struct:"); 43 | println!("{:#?}", endpoint); 44 | } 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/status.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // status.rs 5 | // Service status example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::status; 8 | use azure_devops_rust_api::status::models::geography_with_health::Health; 9 | use azure_devops_rust_api::Credential; 10 | 11 | mod utils; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | // The status API is unauthenticated, so don't provide usual credentials 16 | let credential = Credential::unauthenticated(); 17 | 18 | // Create a status client 19 | println!("Create status client"); 20 | let status_client = status::ClientBuilder::new(credential).build(); 21 | 22 | // Get service status 23 | println!("Get service status"); 24 | let status = status_client.health_client().get().await?; 25 | 26 | println!( 27 | "{:?}: {}", 28 | status.status.status.health, status.status.status.message, 29 | ); 30 | 31 | for service in status.services.iter() { 32 | println!("{}:", service.id); 33 | let (healthy, unhealthy): (Vec<_>, Vec<_>) = service 34 | .geographies 35 | .iter() 36 | .partition(|health| health.health == Health::Healthy); 37 | let healthy: Vec<&str> = healthy 38 | .iter() 39 | .map(|health| health.geography.id.as_ref()) 40 | .collect(); 41 | let unhealthy: Vec<&str> = unhealthy 42 | .iter() 43 | .map(|health| health.geography.id.as_ref()) 44 | .collect(); 45 | println!(" healthy: {}", healthy.join(" ")); 46 | println!(" unhealthy: {}", unhealthy.join(" ")); 47 | } 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/telemetry_git_repo_get.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // telemetry_git_repo_get.rs 5 | // Demonstrates how to use a custom policy in the client pipeline. 6 | // This example uses the telemetry request logger policy to trace request and response details. 7 | // This can be helpful for debugging and understanding the behavior of the client. 8 | // 9 | // See: 10 | // - https://docs.rs/azure_core/latest/azure_core/struct.Pipeline.html 11 | // - https://docs.rs/azure_core/latest/azure_core/trait.Policy.html 12 | 13 | use anyhow::Result; 14 | use azure_devops_rust_api::{git, telemetry}; 15 | use std::env; 16 | 17 | mod utils; 18 | 19 | #[tokio::main] 20 | async fn main() -> Result<()> { 21 | // Install global collector configured based on RUST_LOG env var. 22 | tracing_subscriber::fmt::init(); 23 | 24 | // Get authentication credential 25 | let credential = utils::get_credential()?; 26 | 27 | // Get ADO configuration via environment variables 28 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 29 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 30 | let repo_name = env::args() 31 | .nth(1) 32 | .expect("Usage: telemetry_git_repo_get <repository-name>"); 33 | 34 | // Create a git client with the telemetry request logger policy 35 | let git_client = git::ClientBuilder::new(credential) 36 | .per_call_policies(vec![telemetry::request_logger_policy()]) 37 | .build(); 38 | 39 | // Get specified repo 40 | let _repo = git_client 41 | .repositories_client() 42 | .get_repository(&organization, &repo_name, &project) 43 | .await?; 44 | println!("Finished"); 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/test_plan.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // test_plan.rs 5 | // Getting test plan example 6 | use anyhow::Result; 7 | use azure_devops_rust_api::test_plan; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | let test_plan_id_value = env::var("TEST_PLAN_ID").expect("Must define PLAN_ID for the test"); 21 | let test_plan_id: i32 = test_plan_id_value 22 | .parse() 23 | .expect("Failed to parse TEST_PLAN_ID"); 24 | // Create test_plan client 25 | let test_plan_client = test_plan::ClientBuilder::new(credential).build(); 26 | 27 | // Get all test plans for project 28 | println!("The test plan for project are:"); 29 | let test_plans = test_plan_client 30 | .test_plans_client() 31 | .list(&organization, &project) 32 | .await? 33 | .value; 34 | println!("{:#?}", test_plans); 35 | 36 | // Get all suites for a test plan for a project 37 | println!("The suites for the a plan for project are:"); 38 | let test_suites = test_plan_client 39 | .test_suites_client() 40 | .get_test_suites_for_plan(&organization, &project, test_plan_id) 41 | .await? 42 | .value; 43 | println!("{:#?}", test_suites); 44 | 45 | // Get all test plan variables for project 46 | println!("The test plans variables for project are:"); 47 | let test_plan_variables = test_plan_client 48 | .variables_client() 49 | .list(&organization, &project) 50 | .await? 51 | .value; 52 | println!("{:#?}", test_plan_variables); 53 | 54 | // Get all test plan configuration for project 55 | println!("The test plan configuration for project are:"); 56 | let test_plan_configuration = test_plan_client 57 | .configurations_client() 58 | .list(&organization, &project) 59 | .await? 60 | .value; 61 | println!("{:#?}", test_plan_configuration); 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/test_runs_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // test_runs_list.rs 5 | // Getting test runs example 6 | use anyhow::Result; 7 | use azure_devops_rust_api::test; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO server configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | // Max number of runs for the project to be fetched 22 | let top_test_runs: i32 = 200; 23 | 24 | // Create test_run client 25 | let test_run_client = test::ClientBuilder::new(credential).build(); 26 | 27 | // Get all runs for project 28 | println!("The test runs for project are:"); 29 | let test_runs = test_run_client 30 | .runs_client() 31 | .list(&organization, &project) 32 | .top(top_test_runs) 33 | .await? 34 | .value; 35 | println!("{:#?}", test_runs); 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | use anyhow::Result; 5 | use async_trait::async_trait; 6 | use azure_core::http::{ 7 | policies::{Policy, PolicyResult}, 8 | Context, Request, 9 | }; 10 | use azure_devops_rust_api::Credential; 11 | use azure_identity::DefaultAzureCredential; 12 | use std::sync::Arc; 13 | 14 | fn authenticate_with_default_credential() -> Result<Credential> { 15 | println!("Authenticate using auto-refreshing DefaultAzureCredential"); 16 | let default_azure_credential = DefaultAzureCredential::new()?; 17 | 18 | Ok(Credential::from_token_credential(default_azure_credential)) 19 | } 20 | 21 | #[allow(dead_code)] 22 | pub fn get_credential() -> Result<Credential> { 23 | // Get authentication credential either from a PAT ("ADO_TOKEN") or via the az cli 24 | match std::env::var("ADO_TOKEN") { 25 | Ok(token) if !token.is_empty() => { 26 | println!("Authenticate using PAT provided via $ADO_TOKEN"); 27 | Ok(Credential::from_pat(token)) 28 | } 29 | _ => authenticate_with_default_credential(), 30 | } 31 | } 32 | 33 | // Define a simple policy to always set the `accept` header to `application/zip` 34 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 35 | pub struct AcceptZipPolicy {} 36 | 37 | impl AcceptZipPolicy { 38 | #[allow(dead_code)] 39 | pub fn new_policy() -> Arc<dyn Policy> { 40 | Arc::new(AcceptZipPolicy {}) 41 | } 42 | } 43 | 44 | /// Always set the `accept` header to `application/zip` 45 | #[async_trait] 46 | impl Policy for AcceptZipPolicy { 47 | async fn send( 48 | &self, 49 | ctx: &Context, 50 | request: &mut Request, 51 | next: &[Arc<dyn Policy>], 52 | ) -> PolicyResult { 53 | request.insert_header("accept", "application/zip"); 54 | next[0].send(ctx, request, &next[1..]).await 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/wit_work_item_create.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // wit_work_item_create.rs 5 | // Work Item creation example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::wit; 8 | use azure_devops_rust_api::wit::models::{json_patch_operation::Op, JsonPatchOperation}; 9 | use serde_json::json; 10 | use std::env; 11 | 12 | mod utils; 13 | 14 | #[tokio::main] 15 | async fn main() -> Result<()> { 16 | // Get authentication credential 17 | let credential = utils::get_credential()?; 18 | 19 | // Get ADO configuration via environment variables 20 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 21 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 22 | 23 | // Create a wit client 24 | let wit_client = wit::ClientBuilder::new(credential).build(); 25 | 26 | // Assign the type of work item to create 27 | let work_item_type = "User Story"; 28 | 29 | // Define the title of the work item to be created 30 | let title = JsonPatchOperation { 31 | from: None, 32 | op: Some(Op::Add), 33 | path: Some("/fields/System.Title".to_owned()), 34 | value: Some(json!("Example User Story title")), 35 | }; 36 | 37 | // Each operation lives in a vector, additional elements can be added to fill in other fields 38 | // of a work item, see the comments at the end of this file for some examples 39 | let body = vec![title]; 40 | 41 | // Create a work item 42 | let work_item = wit_client 43 | .work_items_client() 44 | .create(organization, body, project, work_item_type) 45 | .await?; 46 | 47 | println!("{:#?}", work_item); 48 | 49 | Ok(()) 50 | } 51 | 52 | // When creating a work item you can also assign an iteration 53 | 54 | // let iteration = JsonPatchOperation { 55 | // from: None, 56 | // op: Some(Op::Add), 57 | // path: Some("/fields/System.IterationPath".to_owned()), 58 | // value: Some(json!("my-iteration".to_owned())) 59 | // }; 60 | // 61 | // e.g create a work item with a title and in an iteration 62 | // 63 | // let body = vec![title, iteration]; 64 | 65 | // When creating a work item you can also assign a parent 66 | 67 | // let parent = JsonPatchOperation { 68 | // from: None, 69 | // op: Some(Op::Add), 70 | // path: Some("/relations/-".to_owned()), 71 | // value: Some(json!({ 72 | // "rel": "System.LinkTypes.Hierarchy-Reverse", 73 | // "url": format!("https://dev.azure.com/{}/{}/_apis/wit/workItems/{}", organisation, project, parent_id) 74 | // })) 75 | // } 76 | // 77 | // e.g create a work item with a title, in an iteration and assign a parent item 78 | // 79 | // let body = vec![title, iteration, parent]; 80 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/wit_work_item_queries.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // wit_work_item_queries.rs 5 | // Work Item query list example. 6 | use anyhow::Result; 7 | use azure_devops_rust_api::wit; 8 | use std::env; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<()> { 14 | // Get authentication credential 15 | let credential = utils::get_credential()?; 16 | 17 | // Get ADO configuration via environment variables 18 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 19 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 20 | 21 | // Create a wit client 22 | let wit_client = wit::ClientBuilder::new(credential).build(); 23 | 24 | // Get all work item queries 25 | let work_item_queries = wit_client 26 | .queries_client() 27 | .list(&organization, &project) 28 | .await?; 29 | 30 | println!("All work item queries:\n{:#?}", work_item_queries); 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /azure_devops_rust_api/examples/work.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // work.rs 5 | // Work module examples: 6 | // - Get team settings 7 | // - Get iterations 8 | // - Get current iteration 9 | // - Get current iteration work items 10 | 11 | use anyhow::{Context, Result}; 12 | use azure_devops_rust_api::work; 13 | use std::env; 14 | 15 | mod utils; 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<()> { 19 | // Get authentication credential 20 | let credential = utils::get_credential()?; 21 | 22 | // Get ADO configuration via environment variables 23 | let organization = env::var("ADO_ORGANIZATION").expect("Must define ADO_ORGANIZATION"); 24 | let project = env::var("ADO_PROJECT").expect("Must define ADO_PROJECT"); 25 | let team = env::var("ADO_TEAM").expect("Must define ADO_TEAM"); 26 | 27 | // Create a work client 28 | let work_client = work::ClientBuilder::new(credential).build(); 29 | 30 | // Get team settings 31 | let team_settings = work_client 32 | .teamsettings_client() 33 | .get(&organization, &project, &team) 34 | .await?; 35 | println!("Team settings:\n{:#?}", team_settings); 36 | 37 | // Get all iterations 38 | let iterations = work_client 39 | .iterations_client() 40 | .list(&organization, &project, &team) 41 | .await?; 42 | println!("\nIterations:\n{:#?}", iterations); 43 | 44 | // Get current iteration 45 | let current_iteration = work_client 46 | .iterations_client() 47 | .list(&organization, &project, &team) 48 | .timeframe("current") 49 | .await?; 50 | println!("\nCurrent iteration:\n{:#?}", current_iteration); 51 | 52 | // Get current iteration id 53 | let current_iteration_id = current_iteration 54 | .value 55 | .first() 56 | .context("No current iteration")? 57 | .id 58 | .as_ref() 59 | .context("Current iteration has no id")?; 60 | println!("\ncurrent_iteration_id: {current_iteration_id}"); 61 | 62 | // Get current iteration work items 63 | let current_iteration_workitems = work_client 64 | .iterations_client() 65 | .get_iteration_work_items(&organization, &project, current_iteration_id, &team) 66 | .await?; 67 | println!( 68 | "\nCurrent iteration workitems:\n{:#?}", 69 | current_iteration_workitems 70 | ); 71 | 72 | println!( 73 | "\nCurrent iteration work_item_relations length: {}", 74 | current_iteration_workitems.work_item_relations.len() 75 | ); 76 | 77 | Ok(()) 78 | } 79 | -------------------------------------------------------------------------------- /azure_devops_rust_api/src/auth.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | //! Azure DevOps authentication support. 5 | //! 6 | //! For more background information on Azure DevOps authentication see: [Azure DevOps authentication](https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/authentication-guidance) 7 | 8 | use azure_core::credentials::TokenCredential; 9 | use azure_core::error::{Result, ResultExt}; 10 | use base64::{prelude::BASE64_STANDARD, Engine}; 11 | use std::sync::Arc; 12 | 13 | /// A credential for authenticating with Azure DevOps. 14 | /// 15 | /// Supports: 16 | /// - [Azure DevOps Personal Access Token (PAT)](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate). 17 | /// - OAuth token credential obtained via the [`azure_identity`](https://crates.io/crates/azure_identity) crate. 18 | #[derive(Clone)] 19 | pub enum Credential { 20 | Unauthenticated, 21 | Pat(String), 22 | TokenCredential(Arc<dyn TokenCredential>), 23 | } 24 | 25 | impl Credential { 26 | /// Creates a new `Credential` for unauthenticated operations. 27 | pub fn unauthenticated() -> Self { 28 | Credential::Unauthenticated 29 | } 30 | 31 | /// Creates a new `Credential` using the supplied PAT token. 32 | pub fn from_pat(pat: impl Into<String>) -> Self { 33 | let pat = pat.into(); 34 | Credential::Pat(pat) 35 | } 36 | 37 | /// Creates a new `Credential` using the supplied object that implements [`TokenCredential`](https://docs.rs/azure_core/latest/azure_core/auth/trait.TokenCredential.html). 38 | /// 39 | /// Note that the supplied object must be wrapped in an `Arc<...>`. 40 | pub fn from_token_credential<T>(token_credential: Arc<T>) -> Self 41 | where 42 | T: TokenCredential + 'static, 43 | { 44 | let token_credential = token_credential as Arc<dyn TokenCredential>; 45 | Credential::TokenCredential(token_credential) 46 | } 47 | 48 | /// Returns the HTTP authorization header value containing the credential. 49 | #[allow(dead_code)] 50 | pub(crate) async fn http_authorization_header( 51 | &self, 52 | scopes: &[&str], 53 | ) -> Result<Option<String>> { 54 | match self { 55 | Credential::Unauthenticated => Ok(None), 56 | // PAT tokens are passed using Basic authentication. 57 | Credential::Pat(pat) => Ok(Some(format!( 58 | "Basic {}", 59 | BASE64_STANDARD.encode(format!(":{}", &pat)) 60 | ))), 61 | // OAuth tokens are passed using Bearer authentication. 62 | Credential::TokenCredential(token_credential) => { 63 | let token_response = token_credential 64 | .get_token(scopes) 65 | .await 66 | .context(azure_core::error::ErrorKind::Other, "get bearer token")?; 67 | Ok(Some(format!("Bearer {}", token_response.token.secret()))) 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /azure_devops_rust_api/src/headers.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | //! Azure DevOps rate limit headers. 5 | //! 6 | //! For more information see [Azure DevOps Rate and usage limits](https://learn.microsoft.com/en-us/azure/devops/integrate/concepts/rate-limits?view=azure-devops). 7 | 8 | use azure_core::http::headers::HeaderName; 9 | 10 | /// A custom header indicating the service and type of threshold that was reached. Threshold types and service names might vary over time and without warning. 11 | /// We recommend displaying this string to a human, but not relying on it for computation. 12 | pub const X_RATELIMIT_RESOURCE: HeaderName = HeaderName::from_static("x-ratelimit-resource"); 13 | 14 | /// How long the request was delayed. Units: seconds with up to three decimal places (milliseconds). 15 | pub const X_RATELIMIT_DELAY: HeaderName = HeaderName::from_static("x-ratelimit-delay"); 16 | 17 | /// Total number of TSTUs allowed before delays are imposed. 18 | pub const X_RATELIMIT_LIMIT: HeaderName = HeaderName::from_static("x-ratelimit-limit"); 19 | 20 | /// Number of TSTUs remaining before being delayed. If requests are already being delayed or blocked, it's 0. 21 | pub const X_RATELIMIT_REMAINING: HeaderName = HeaderName::from_static("x-ratelimit-remaining"); 22 | 23 | /// Time at which, if all resource consumption stopped immediately, tracked usage would return to 0 TSTUs. Expressed in Unix epoch time. 24 | pub const X_RATELIMIT_RESET: HeaderName = HeaderName::from_static("x-ratelimit-reset"); 25 | -------------------------------------------------------------------------------- /azure_devops_rust_api/src/operations/models.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | #![allow(non_camel_case_types)] 4 | #![allow(unused_imports)] 5 | use serde::de::{value, Deserializer, IntoDeserializer}; 6 | use serde::{Deserialize, Serialize, Serializer}; 7 | use std::str::FromStr; 8 | #[doc = "Contains information about the progress or result of an async operation."] 9 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] 10 | pub struct Operation { 11 | #[serde(flatten)] 12 | pub operation_reference: OperationReference, 13 | #[doc = "Links"] 14 | #[serde(rename = "_links", default, skip_serializing_if = "Option::is_none")] 15 | pub links: Option<serde_json::Value>, 16 | #[doc = "Detailed messaged about the status of an operation."] 17 | #[serde( 18 | rename = "detailedMessage", 19 | default, 20 | skip_serializing_if = "Option::is_none" 21 | )] 22 | pub detailed_message: Option<String>, 23 | #[doc = "Result message for an operation."] 24 | #[serde( 25 | rename = "resultMessage", 26 | default, 27 | skip_serializing_if = "Option::is_none" 28 | )] 29 | pub result_message: Option<String>, 30 | #[serde(rename = "resultUrl", default, skip_serializing_if = "Option::is_none")] 31 | pub result_url: Option<OperationResultReference>, 32 | } 33 | impl Operation { 34 | pub fn new() -> Self { 35 | Self::default() 36 | } 37 | } 38 | #[doc = "Reference for an async operation."] 39 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] 40 | pub struct OperationReference { 41 | #[doc = "Unique identifier for the operation."] 42 | #[serde(default, skip_serializing_if = "Option::is_none")] 43 | pub id: Option<String>, 44 | #[doc = "Unique identifier for the plugin."] 45 | #[serde(rename = "pluginId", default, skip_serializing_if = "Option::is_none")] 46 | pub plugin_id: Option<String>, 47 | #[doc = "The current status of the operation."] 48 | #[serde(default, skip_serializing_if = "Option::is_none")] 49 | pub status: Option<operation_reference::Status>, 50 | #[doc = "URL to get the full operation object."] 51 | #[serde(default, skip_serializing_if = "Option::is_none")] 52 | pub url: Option<String>, 53 | } 54 | impl OperationReference { 55 | pub fn new() -> Self { 56 | Self::default() 57 | } 58 | } 59 | pub mod operation_reference { 60 | use super::*; 61 | #[doc = "The current status of the operation."] 62 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 63 | pub enum Status { 64 | #[serde(rename = "notSet")] 65 | NotSet, 66 | #[serde(rename = "queued")] 67 | Queued, 68 | #[serde(rename = "inProgress")] 69 | InProgress, 70 | #[serde(rename = "cancelled")] 71 | Cancelled, 72 | #[serde(rename = "succeeded")] 73 | Succeeded, 74 | #[serde(rename = "failed")] 75 | Failed, 76 | } 77 | } 78 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] 79 | pub struct OperationResultReference { 80 | #[doc = "URL to the operation result."] 81 | #[serde(rename = "resultUrl", default, skip_serializing_if = "Option::is_none")] 82 | pub result_url: Option<String>, 83 | } 84 | impl OperationResultReference { 85 | pub fn new() -> Self { 86 | Self::default() 87 | } 88 | } 89 | #[doc = "The class to represent a collection of REST reference links."] 90 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] 91 | pub struct ReferenceLinks { 92 | #[doc = "The readonly view of the links. Because Reference links are readonly, we only want to expose them as read only."] 93 | #[serde(default, skip_serializing_if = "Option::is_none")] 94 | pub links: Option<serde_json::Value>, 95 | } 96 | impl ReferenceLinks { 97 | pub fn new() -> Self { 98 | Self::default() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /azure_devops_rust_api/src/serde.rs: -------------------------------------------------------------------------------- 1 | use serde::de::{Deserialize, Deserializer}; 2 | use std::result::Result; 3 | 4 | #[allow(dead_code)] 5 | pub(crate) fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error> 6 | where 7 | T: Default + Deserialize<'de>, 8 | D: Deserializer<'de>, 9 | { 10 | let opt = Option::deserialize(deserializer)?; 11 | Ok(opt.unwrap_or_default()) 12 | } 13 | 14 | /// Deserialize JSON null as default 15 | /// <https://github.com/serde-rs/serde/issues/1098> 16 | pub fn deserialize_null_as_default<'de, D, T>(deserializer: D) -> Result<T, D::Error> 17 | where 18 | T: Default + Deserialize<'de>, 19 | D: Deserializer<'de>, 20 | { 21 | let opt = Option::deserialize(deserializer)?; 22 | Ok(opt.unwrap_or_default()) 23 | } 24 | -------------------------------------------------------------------------------- /azure_devops_rust_api/src/telemetry.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | //! Azure DevOps telemetry support. 5 | use async_trait::async_trait; 6 | use azure_core::http::{ 7 | headers::Headers, 8 | policies::{Policy, PolicyResult}, 9 | Context, Request, Response, 10 | }; 11 | use std::sync::Arc; 12 | use tracing::{error, info}; 13 | 14 | /// Basic request logger policy. 15 | /// 16 | /// This policy logs the request and response at `info` level using the `tracing` crate. 17 | /// If the request fails, it logs the error at `error` level. 18 | /// 19 | /// To use the policy, add it to the client builder: 20 | /// ```rust 21 | /// let git_client = git::ClientBuilder::new(credential) 22 | /// .per_call_policies(vec![telemetry::request_logger_policy()]) 23 | /// .build(); 24 | /// ``` 25 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 26 | pub struct RequestLogger {} 27 | 28 | impl RequestLogger { 29 | fn new() -> Self { 30 | Default::default() 31 | } 32 | } 33 | 34 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 35 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 36 | impl Policy for RequestLogger { 37 | async fn send( 38 | &self, 39 | ctx: &Context, 40 | request: &mut Request, 41 | next: &[Arc<dyn Policy>], 42 | ) -> PolicyResult { 43 | // Redact the Authorization header so that we don't log sensitive information 44 | let mut redacted_headers = Headers::new(); 45 | for (header_name, header_value) in request.headers().iter() { 46 | if header_name.as_str().to_lowercase() == "authorization" { 47 | redacted_headers.insert(header_name.clone(), "<redacted>"); 48 | } else { 49 | redacted_headers.insert(header_name.clone(), header_value.clone()); 50 | } 51 | } 52 | 53 | info!( 54 | method = %request.method(), 55 | url = %request.url(), 56 | headers = ?redacted_headers, 57 | body = ?request.body(), 58 | "Request" 59 | ); 60 | let now = std::time::Instant::now(); 61 | // Call the next policy in the chain, and await the response 62 | let rsp = next[0].send(ctx, request, &next[1..]).await; 63 | 64 | // Log the response 65 | let elapsed_time = now.elapsed().as_secs_f32(); 66 | match rsp { 67 | Ok(rsp) => { 68 | // To get the body content, we need to consume the response stream. 69 | // We then have to subsequently reconstruct the response stream 70 | // to pass back to the caller. 71 | let (status_code, headers, response_body) = rsp.deconstruct(); 72 | let response_body = response_body.collect().await; 73 | info!( 74 | status_code = %status_code, 75 | headers = ?headers, 76 | response_body = ?response_body, 77 | elapsed_time = %elapsed_time, 78 | "Response" 79 | ); 80 | let response_stream = 81 | Box::pin(futures::stream::once(futures::future::ready(response_body))); 82 | Ok(Response::new(status_code, headers, response_stream)) 83 | } 84 | Err(err) => { 85 | error!( 86 | err = ?err, 87 | "Request failed" 88 | ); 89 | Err(err) 90 | } 91 | } 92 | } 93 | } 94 | 95 | /// Create a new instance of the `RequestLogger` policy. 96 | pub fn request_logger_policy() -> Arc<dyn Policy> { 97 | Arc::new(RequestLogger::new()) 98 | } 99 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # Exit on error 5 | $ErrorActionPreference = "Stop" 6 | 7 | # Fetch the vsts-rest-api-specs submodule 8 | Write-Host "Fetch vsts-rest-api-specs" 9 | git submodule init 10 | git submodule update 11 | 12 | # Patch the API 13 | Write-Host "Create vsts-rest-api-specs.patched" 14 | Remove-Item -Recurse -Force vsts-rest-api-specs.patched -ErrorAction Ignore 15 | Set-Location vsts-api-patcher 16 | cargo run --release 17 | Set-Location .. 18 | 19 | # Autogen the Rust crate 20 | Write-Host "Autogen Rust crate" 21 | Remove-Item -Recurse -Force azure_devops_rust_api/target -ErrorAction Ignore 22 | Set-Location autorust 23 | cargo run --bin ado-autorust --release 24 | Set-Location .. 25 | 26 | # Build Rust crate 27 | Set-Location azure_devops_rust_api 28 | Write-Host "Build azure_devops_rust_api" 29 | cargo build --all-features 30 | Set-Location .. 31 | 32 | Write-Host "Done" 33 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | # Exit on error 6 | set -ex 7 | 8 | # Fetch the vsts-rest-api-specs submodule 9 | echo "Fetch vsts-rest-api-specs" 10 | git submodule init 11 | git submodule update 12 | 13 | # Patch the API 14 | echo "Crate vsts-rest-api-specs.patched" 15 | rm -rf vsts-rest-api-specs.patched 16 | cd vsts-api-patcher 17 | cargo run --release 18 | cd .. 19 | 20 | # Autogen the Rust crate 21 | echo "Autogen Rust crate" 22 | rm -rf azure_devops_rust_api/target 23 | cd autorust 24 | cargo run --bin ado-autorust --release 25 | cd .. 26 | 27 | # Build Rust crate 28 | cd azure_devops_rust_api 29 | echo "Build azure_devops_rust_api" 30 | cargo build --all-features 31 | cd .. 32 | 33 | echo "Done" 34 | -------------------------------------------------------------------------------- /publish.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # Exit on error 5 | $ErrorActionPreference = "Stop" 6 | 7 | Write-Host "Publish crate to crates.io" 8 | Set-Location azure_devops_rust_api 9 | cargo publish --verbose --all-features 10 | Set-Location .. 11 | 12 | Write-Host "Done" -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | # Exit on error 6 | set -ex 7 | 8 | echo "Publish crate to crates.io" 9 | cd azure_devops_rust_api 10 | cargo publish --verbose --all-features 11 | cd .. 12 | 13 | echo "Done" 14 | -------------------------------------------------------------------------------- /vsts-api-patcher/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.98" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 19 | 20 | [[package]] 21 | name = "json" 22 | version = "0.12.4" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 25 | 26 | [[package]] 27 | name = "memchr" 28 | version = "2.6.3" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 31 | 32 | [[package]] 33 | name = "regex" 34 | version = "1.11.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 37 | dependencies = [ 38 | "aho-corasick", 39 | "memchr", 40 | "regex-automata", 41 | "regex-syntax", 42 | ] 43 | 44 | [[package]] 45 | name = "regex-automata" 46 | version = "0.4.8" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 49 | dependencies = [ 50 | "aho-corasick", 51 | "memchr", 52 | "regex-syntax", 53 | ] 54 | 55 | [[package]] 56 | name = "regex-syntax" 57 | version = "0.8.5" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 60 | 61 | [[package]] 62 | name = "same-file" 63 | version = "1.0.6" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 66 | dependencies = [ 67 | "winapi-util", 68 | ] 69 | 70 | [[package]] 71 | name = "vsts-api-patcher" 72 | version = "0.1.0" 73 | dependencies = [ 74 | "anyhow", 75 | "json", 76 | "regex", 77 | "walkdir", 78 | ] 79 | 80 | [[package]] 81 | name = "walkdir" 82 | version = "2.5.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 85 | dependencies = [ 86 | "same-file", 87 | "winapi-util", 88 | ] 89 | 90 | [[package]] 91 | name = "winapi" 92 | version = "0.3.9" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 95 | dependencies = [ 96 | "winapi-i686-pc-windows-gnu", 97 | "winapi-x86_64-pc-windows-gnu", 98 | ] 99 | 100 | [[package]] 101 | name = "winapi-i686-pc-windows-gnu" 102 | version = "0.4.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 105 | 106 | [[package]] 107 | name = "winapi-util" 108 | version = "0.1.5" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 111 | dependencies = [ 112 | "winapi", 113 | ] 114 | 115 | [[package]] 116 | name = "winapi-x86_64-pc-windows-gnu" 117 | version = "0.4.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 120 | -------------------------------------------------------------------------------- /vsts-api-patcher/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | [package] 5 | name = "vsts-api-patcher" 6 | version = "0.1.0" 7 | edition = "2021" 8 | authors = ["John Batty <johnbatty@microsoft.com>"] 9 | description = "vsts-rest-api-specs patcher" 10 | documentation = "https://docs.rs/azure_devops_rust_api" 11 | homepage = "https://github.com/microsoft/azure-devops-rust-api" 12 | repository = "https://github.com/microsoft/azure-devops-rust-api" 13 | keywords = ["azure", "devops", "vsts-rest-api-specs", "ado", "patcher"] 14 | license = "MIT" 15 | publish = false 16 | 17 | [dependencies] 18 | anyhow = "1" 19 | json = "0.12" 20 | walkdir = "2" 21 | regex = "1" 22 | --------------------------------------------------------------------------------