├── .github ├── buildomat │ └── config.toml ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.adoc ├── Cargo.lock ├── Cargo.toml ├── README.md ├── cargo-progenitor ├── Cargo.toml ├── release.toml ├── src │ └── main.rs └── tests │ ├── data │ └── test_help.stdout │ └── test_cmd.rs ├── docs ├── builder-generation.md ├── implementing-client.md ├── positional-generation.md └── progenitor-client.md ├── example-build ├── Cargo.toml ├── build.rs ├── release.toml └── src │ └── main.rs ├── example-macro ├── Cargo.toml ├── release.toml └── src │ └── main.rs ├── example-wasm ├── .cargo │ └── config ├── Cargo.toml ├── build.rs ├── release.toml ├── src │ └── main.rs └── tests │ └── client.rs ├── progenitor-client ├── Cargo.toml ├── release.toml ├── src │ ├── lib.rs │ └── progenitor_client.rs └── tests │ └── client_test.rs ├── progenitor-impl ├── Cargo.toml ├── release.toml ├── src │ ├── cli.rs │ ├── httpmock.rs │ ├── lib.rs │ ├── method.rs │ ├── template.rs │ ├── to_schema.rs │ └── util.rs └── tests │ ├── output │ ├── Cargo.toml │ └── src │ │ ├── buildomat_builder.rs │ │ ├── buildomat_builder_tagged.rs │ │ ├── buildomat_cli.rs │ │ ├── buildomat_httpmock.rs │ │ ├── buildomat_positional.rs │ │ ├── cli_gen_builder.rs │ │ ├── cli_gen_builder_tagged.rs │ │ ├── cli_gen_cli.rs │ │ ├── cli_gen_httpmock.rs │ │ ├── cli_gen_positional.rs │ │ ├── keeper_builder.rs │ │ ├── keeper_builder_tagged.rs │ │ ├── keeper_cli.rs │ │ ├── keeper_httpmock.rs │ │ ├── keeper_positional.rs │ │ ├── lib.rs │ │ ├── nexus_builder.rs │ │ ├── nexus_builder_tagged.rs │ │ ├── nexus_cli.rs │ │ ├── nexus_httpmock.rs │ │ ├── nexus_positional.rs │ │ ├── param_collision_builder.rs │ │ ├── param_collision_builder_tagged.rs │ │ ├── param_collision_cli.rs │ │ ├── param_collision_httpmock.rs │ │ ├── param_collision_positional.rs │ │ ├── param_overrides_builder.rs │ │ ├── param_overrides_builder_tagged.rs │ │ ├── param_overrides_cli.rs │ │ ├── param_overrides_httpmock.rs │ │ ├── param_overrides_positional.rs │ │ ├── propolis_server_builder.rs │ │ ├── propolis_server_builder_tagged.rs │ │ ├── propolis_server_cli.rs │ │ ├── propolis_server_httpmock.rs │ │ ├── propolis_server_positional.rs │ │ ├── test_default_params_builder.rs │ │ ├── test_default_params_positional.rs │ │ ├── test_freeform_response.rs │ │ ├── test_renamed_parameters.rs │ │ ├── test_stream_pagination_builder.rs │ │ └── test_stream_pagination_positional.rs │ ├── test_output.rs │ └── test_specific.rs ├── progenitor-macro ├── Cargo.toml ├── release.toml └── src │ ├── lib.rs │ └── token_utils.rs ├── progenitor ├── Cargo.toml ├── LICENSE ├── src │ └── lib.rs └── tests │ ├── build_buildomat.rs │ ├── build_keeper.rs │ ├── build_nexus.rs │ ├── build_propolis.rs │ ├── load_yaml.rs │ └── test_client.rs ├── release.toml └── sample_openapi ├── README.md ├── api.github.com.json ├── buildomat.json ├── cli-gen.json ├── keeper.json ├── nexus.json ├── param-collision.json ├── param-overrides.json ├── param-overrides.yaml └── propolis-server.json /.github/buildomat/config.toml: -------------------------------------------------------------------------------- 1 | # 2 | # This file, with this flag, must be present in the default branch in order for 3 | # the buildomat integration to create check suites. 4 | # 5 | enable = true 6 | 7 | # 8 | # Require approval for pull requests made by users outside our organisation. 9 | # 10 | org_only = true 11 | 12 | # 13 | # We accept pull requests from several automated services that are outside the 14 | # organisation. Allow jobs from those sources to proceed without manual 15 | # approval: 16 | # 17 | allow_users = [ 18 | "dependabot[bot]", 19 | "oxide-reflector-bot[bot]", 20 | "oxide-renovate[bot]", 21 | "renovate[bot]", 22 | ] 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Dependabot configuration file 3 | # 4 | 5 | version: 2 6 | updates: 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | ignore: 12 | - dependency-name: "schemars" 13 | versions: [">=0.9.0"] 14 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration for GitHub-based CI 3 | # 4 | name: Build 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | check-style: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Install nightly rustfmt 18 | uses: dtolnay/rust-toolchain@nightly 19 | with: 20 | components: rustfmt 21 | - name: Report cargo version 22 | run: cargo --version 23 | - name: Report rustfmt version 24 | run: cargo fmt -- --version 25 | - name: Report nightly cargo version 26 | run: cargo +nightly --version 27 | - name: Report nightly rustfmt version 28 | run: cargo +nightly fmt -- --version 29 | - name: Check style 30 | run: cargo fmt -- --check 31 | 32 | build-and-test: 33 | runs-on: ${{ matrix.os }} 34 | strategy: 35 | matrix: 36 | os: [ ubuntu-latest, windows-latest, macos-latest ] 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Install nightly rustfmt 40 | uses: dtolnay/rust-toolchain@nightly 41 | with: 42 | components: rustfmt 43 | - name: Install stable 44 | uses: dtolnay/rust-toolchain@stable 45 | with: 46 | components: rustfmt 47 | - name: Build 48 | run: cargo build --locked --tests --verbose 49 | - name: Run tests 50 | run: cargo test --locked --verbose 51 | 52 | build-no-default-features: 53 | runs-on: ${{ matrix.os }} 54 | strategy: 55 | matrix: 56 | os: [ ubuntu-latest, windows-latest, macos-latest ] 57 | steps: 58 | - uses: actions/checkout@v2 59 | - name: Build 60 | run: cargo build --locked --no-default-features 61 | 62 | test-wasm: 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v4 66 | - name: Install nightly 67 | uses: dtolnay/rust-toolchain@nightly 68 | with: 69 | components: rustfmt 70 | target: wasm32-unknown-unknown 71 | - uses: nanasess/setup-chromedriver@v2 72 | - name: Install wasm-bindgen-cli 73 | run: cargo install --version 0.2.100 wasm-bindgen-cli 74 | - name: Run tests 75 | run: | 76 | cd example-wasm 77 | CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --locked --verbose 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | progenitor-impl/tests/output/Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.adoc: -------------------------------------------------------------------------------- 1 | :showtitle: 2 | :toc: left 3 | :icons: font 4 | :toclevels: 1 5 | 6 | = Progenitor Changelog 7 | 8 | // WARNING: This file is modified programmatically by `cargo release` as 9 | // configured in release.toml. DO NOT change the format of the headers or the 10 | // list of raw commits. 11 | 12 | // cargo-release: next header goes here (do not change this line) 13 | 14 | == Unreleased changes (release date TBD) 15 | 16 | https://github.com/oxidecomputer/progenitor/compare/v0.11.0\...HEAD[Full list of commits] 17 | 18 | == 0.11.0 (released 2025-05-27) 19 | 20 | https://github.com/oxidecomputer/progenitor/compare/v0.10.0\...v0.11.0[Full list of commits] 21 | 22 | * Implement hooks via a trait and autoref specialization (#1121) 23 | * Update httpmock generation for header parameters (#1140) 24 | 25 | == 0.10.0 (released 2025-04-30) 26 | 27 | https://github.com/oxidecomputer/progenitor/compare/v0.9.1\...v0.10.0[Full list of commits] 28 | 29 | * General CLI generation and doc improvements (#1051) 30 | * Fix header parameters for non-string scalars (#1067) 31 | * Allow OAS 3.0.4 and other versions starting with '3.0.' (#1107) 32 | * Generate api-version headers for each request (#1120) 33 | * Implement From for client error (#1116) 34 | 35 | == 0.9.1 (released 2025-01-06) 36 | 37 | https://github.com/oxidecomputer/progenitor/compare/v0.9.0\...v0.9.1[Full list of commits] 38 | 39 | * Fix regression serializing query parameter simple enums (#1033) 40 | 41 | == 0.9.0 (released 2024-12-27) 42 | 43 | https://github.com/oxidecomputer/progenitor/compare/v0.8.0\...v0.9.0[Full list of commits] 44 | 45 | * Handle array-like and map-like query parameters (#1017) 46 | * Support custom map types (#1011) 47 | * Add a progenitor feature for use of macro (and client) (#1007) 48 | * Add async post_hook function support (#942) 49 | * Fix name-collision and code-generation issues (#1019) (#993) (#933) (#941) 50 | 51 | == 0.8.0 (released 2024-09-26) 52 | 53 | https://github.com/oxidecomputer/progenitor/compare/v0.7.0\...v0.8.0[Full list of commits] 54 | 55 | * Update to `hyper` 1.0, `reqwest` 0.12 -- these are likely breaking changes 56 | * Improvements to CLI generation 57 | * Changes to type generation and traits in `typify` 0.2.0 58 | 59 | == 0.7.0 (released 2024-05-15) 60 | 61 | * Minor fixes 62 | * Support for `x-rust-types`` via typify (#804) 63 | 64 | https://github.com/oxidecomputer/progenitor/compare/v0.6.0\...v0.7.0[Full list of commits] 65 | 66 | == 0.6.0 (released 2024-02-28) 67 | 68 | * Include payload in error when there's a deserialization failure (#655) 69 | * Conversion error type 70 | * Rustdoc for mods and extension traits (#677) 71 | * Ignore clippy in generated code (#702) 72 | 73 | https://github.com/oxidecomputer/progenitor/compare/v0.5.0\...v0.6.0[Full list of commits] 74 | 75 | == 0.5.0 (released 2023-12-16) 76 | 77 | * Improve breadth of schema support (#592) 78 | * Various changes to avoid name collisions (#599) 79 | * Support for text/plain and text/x-markdown body content types (#593) 80 | * Upgrade to `openapiv3` v2.0.0 81 | * Various type generation changes from `typify` 0.0.15 82 | 83 | https://github.com/oxidecomputer/progenitor/compare/v0.4.0\...v0.5.0[Full list of commits] 84 | 85 | == 0.4.1 (released 2023-12-15) 86 | 87 | * Fixed an issue with `openapiv3` and `indexmap` dependencies 88 | 89 | https://github.com/oxidecomputer/progenitor/compare/v0.4.0\...v0.4.1[Full list of commits] 90 | 91 | == 0.4.0 (released 2023-09-25) 92 | 93 | * Support for updated dropshot pagination extension (#465) 94 | * Many CLI and http mock generation improvements (beta) 95 | * Add cargo-progenitor (#476) 96 | 97 | https://github.com/oxidecomputer/progenitor/compare/v0.3.0\...v0.4.0[Full list of commits] 98 | 99 | == 0.3.0 (released 2023-05-03) 100 | 101 | * Add support for header parameters (#210) 102 | * Add support for YAML input (#227) 103 | * Add generation for `clap`-based CLIs 104 | * Add generation for strongly-typed mocks with `httpmock` 105 | * Remove dependency on rustfmt installations in macro and builder uses 106 | * Many improvements to type schema handling 107 | * Use of builder types for body parameters 108 | * Path-level parameter handling 109 | * Many options for augmenting type generation 110 | 111 | https://github.com/oxidecomputer/progenitor/compare/v0.2.0\...v0.3.0[Full list of commits] 112 | 113 | == 0.2.0 (released 2022-09-11) 114 | 115 | * Add support for a builder-style generation in addition to the positional style (#86) 116 | * Add support for body parameters with application/x-www-form-urlencoded media type (#109) 117 | * Derive `Debug` for `Client` and builders for the various operations (#145) 118 | * Builders for `struct` types (#171) 119 | * Add a prelude that include the `Client` and any extension traits (#176) 120 | * Add support for upgrading connections, which requires a version bump to reqwest. (#183) 121 | 122 | https://github.com/oxidecomputer/progenitor/compare/v0.1.1\...v0.2.0[Full list of commits] 123 | 124 | == 0.1.1 (released 2022-05-13) 125 | 126 | First published version 127 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "cargo-progenitor", 4 | "example-build", 5 | "example-macro", 6 | "example-wasm", 7 | "progenitor", 8 | "progenitor-client", 9 | "progenitor-impl", 10 | "progenitor-macro", 11 | ] 12 | 13 | resolver = "2" 14 | 15 | [workspace.dependencies] 16 | progenitor = { version = "0.11.0", path = "progenitor", default-features = false } 17 | progenitor-client = { version = "0.11.0", path = "progenitor-client" } 18 | progenitor-impl = { version = "0.11.0", path = "progenitor-impl" } 19 | progenitor-macro = { version = "0.11.0", path = "progenitor-macro" } 20 | 21 | anyhow = "1.0.98" 22 | assert_cmd = "2.0.17" 23 | base64 = "0.22.1" 24 | bytes = "1.10.1" 25 | chrono = { version = "0.4.41", default-features = false, features = ["serde"] } 26 | clap = { version = "4.5.39" } 27 | clap-cargo = "0.15.2" 28 | dropshot = { version = "0.13.0", default-features = false } 29 | env_logger = "0.10.2" 30 | expectorate = { version = "1.2.0", features = ["predicates"] } 31 | futures = "0.3.31" 32 | futures-core = "0.3.31" 33 | heck = "0.5.0" 34 | http = "1.3.1" 35 | hyper = "1.6.0" 36 | indexmap = "2.9.0" 37 | openapiv3 = "2.2.0" 38 | percent-encoding = "2.3.0" 39 | proc-macro2 = "1.0.95" 40 | quote = "1.0.40" 41 | rand = "0.9.1" 42 | regex = "1.11.1" 43 | regress = "0.10.3" 44 | reqwest = { version = "0.12.4", default-features = false, features = ["json", "stream"] } 45 | rustfmt-wrapper = "0.2.1" 46 | schemars = { version = "0.8.22", features = ["chrono", "uuid1"] } 47 | semver = "1.0.26" 48 | serde = { version = "1.0.219", features = ["derive"] } 49 | serde_json = "1.0.140" 50 | serde_urlencoded = "0.7.1" 51 | serde_yaml = "0.9" 52 | syn = { version = "2.0.101", features = ["parsing"] } 53 | thiserror = "2.0.12" 54 | tokio = { version = "1.45.1", features = ["rt", "net"] } 55 | # change when publishing 56 | typify = { version = "0.4.2" } 57 | #typify = { git = "https://github.com/oxidecomputer/typify" } 58 | url = "2.5.4" 59 | unicode-ident = "1.0.18" 60 | uuid = { version = "1.17.0", features = ["serde", "v4"] } 61 | 62 | #[patch."https://github.com/oxidecomputer/typify"] 63 | #typify = { path = "../typify/typify" } 64 | 65 | #[patch.crates-io] 66 | #serde_tokenstream = { path = "../serde_tokenstream" } 67 | #typify = { path = "../typify/typify" } 68 | #rustfmt-wrapper = { path = "../rustfmt-wrapper" } 69 | -------------------------------------------------------------------------------- /cargo-progenitor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-progenitor" 3 | version = "0.11.0" 4 | edition = "2021" 5 | license = "MPL-2.0" 6 | description = "A cargo command to generate a Rust client SDK from OpenAPI" 7 | repository = "https://github.com/oxidecomputer/progenitor.git" 8 | readme = "../README.md" 9 | keywords = ["openapi", "openapiv3", "sdk", "generator"] 10 | categories = ["api-bindings", "development-tools::cargo-plugins"] 11 | 12 | default-run = "cargo-progenitor" 13 | 14 | [dependencies] 15 | progenitor = { workspace = true, default-features = false } 16 | progenitor-client = { workspace = true } 17 | progenitor-impl = { workspace = true } 18 | 19 | anyhow = { workspace = true } 20 | assert_cmd = { workspace = true } 21 | clap = { workspace = true } 22 | clap-cargo = { workspace = true } 23 | env_logger = { workspace = true } 24 | expectorate = { workspace = true } 25 | openapiv3 = { workspace = true } 26 | rustfmt-wrapper = { workspace = true } 27 | serde_json = { workspace = true } 28 | serde_yaml = { workspace = true } 29 | -------------------------------------------------------------------------------- /cargo-progenitor/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [] 2 | -------------------------------------------------------------------------------- /cargo-progenitor/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Oxide Computer Company 2 | 3 | use std::{ 4 | fs::{File, OpenOptions}, 5 | io::Write, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | use anyhow::{bail, Result}; 10 | use clap::{Parser, ValueEnum}; 11 | use openapiv3::OpenAPI; 12 | use progenitor::{GenerationSettings, Generator, InterfaceStyle, TagStyle}; 13 | use progenitor_impl::space_out_items; 14 | 15 | fn is_non_release() -> bool { 16 | cfg!(debug_assertions) 17 | } 18 | 19 | #[derive(Parser)] 20 | #[command(name = "cargo")] 21 | #[command(bin_name = "cargo")] 22 | enum CargoCli { 23 | Progenitor(Args), 24 | } 25 | 26 | /// Generate a stand-alone crate from an OpenAPI document 27 | #[derive(Parser)] 28 | struct Args { 29 | /// OpenAPI definition document (JSON or YAML) 30 | #[clap(short = 'i', long)] 31 | input: String, 32 | /// Output directory for Rust crate 33 | #[clap(short = 'o', long)] 34 | output: String, 35 | /// Target Rust crate name 36 | #[clap(short = 'n', long)] 37 | name: String, 38 | /// Target Rust crate version 39 | #[clap(short = 'v', long)] 40 | version: String, 41 | /// Target Rust crate registry 42 | #[clap(long)] 43 | registry_name: Option, 44 | /// Target crate license 45 | #[clap(long, default_value = "SPECIFY A LICENSE BEFORE PUBLISHING")] 46 | license_name: String, 47 | 48 | /// SDK interface style 49 | #[clap(value_enum, long, default_value_t = InterfaceArg::Positional)] 50 | interface: InterfaceArg, 51 | /// SDK tag style 52 | #[clap(value_enum, long, default_value_t = TagArg::Merged)] 53 | tags: TagArg, 54 | /// Include client code rather than depending on progenitor-client 55 | #[clap(default_value = match is_non_release() { true => "true", false => "false" }, long, action = clap::ArgAction::Set)] 56 | include_client: bool, 57 | } 58 | 59 | #[derive(Copy, Clone, ValueEnum)] 60 | enum InterfaceArg { 61 | Positional, 62 | Builder, 63 | } 64 | 65 | impl From for InterfaceStyle { 66 | fn from(arg: InterfaceArg) -> Self { 67 | match arg { 68 | InterfaceArg::Positional => InterfaceStyle::Positional, 69 | InterfaceArg::Builder => InterfaceStyle::Builder, 70 | } 71 | } 72 | } 73 | 74 | #[derive(Copy, Clone, ValueEnum)] 75 | enum TagArg { 76 | Merged, 77 | Separate, 78 | } 79 | 80 | impl From for TagStyle { 81 | fn from(arg: TagArg) -> Self { 82 | match arg { 83 | TagArg::Merged => TagStyle::Merged, 84 | TagArg::Separate => TagStyle::Separate, 85 | } 86 | } 87 | } 88 | 89 | fn reformat_code(input: String) -> String { 90 | let config = rustfmt_wrapper::config::Config { 91 | normalize_doc_attributes: Some(true), 92 | wrap_comments: Some(true), 93 | ..Default::default() 94 | }; 95 | space_out_items(rustfmt_wrapper::rustfmt_config(config, input).unwrap()).unwrap() 96 | } 97 | 98 | fn save

(p: P, data: &str) -> Result<()> 99 | where 100 | P: AsRef, 101 | { 102 | let p = p.as_ref(); 103 | let mut f = OpenOptions::new() 104 | .create(true) 105 | .truncate(true) 106 | .write(true) 107 | .open(p)?; 108 | f.write_all(data.as_bytes())?; 109 | f.flush()?; 110 | Ok(()) 111 | } 112 | 113 | fn main() -> Result<()> { 114 | env_logger::init(); 115 | 116 | let CargoCli::Progenitor(args) = CargoCli::parse(); 117 | let api = load_api(&args.input)?; 118 | 119 | let mut builder = Generator::new( 120 | GenerationSettings::default() 121 | .with_interface(args.interface.into()) 122 | .with_tag(args.tags.into()), 123 | ); 124 | 125 | match builder.generate_tokens(&api) { 126 | Ok(api_code) => { 127 | let type_space = builder.get_type_space(); 128 | 129 | println!("-----------------------------------------------------"); 130 | println!(" TYPE SPACE"); 131 | println!("-----------------------------------------------------"); 132 | for (idx, type_entry) in type_space.iter_types().enumerate() { 133 | let n = type_entry.describe(); 134 | println!("{:>4} {}", idx, n); 135 | } 136 | println!("-----------------------------------------------------"); 137 | println!(); 138 | 139 | let name = &args.name; 140 | let version = &args.version; 141 | 142 | // Create the top-level crate directory: 143 | let root = PathBuf::from(&args.output); 144 | std::fs::create_dir_all(&root)?; 145 | 146 | // Write the Cargo.toml file: 147 | let mut toml = root.clone(); 148 | toml.push("Cargo.toml"); 149 | 150 | let mut tomlout = format!( 151 | "[package]\n\ 152 | name = \"{}\"\n\ 153 | version = \"{}\"\n\ 154 | edition = \"2021\"\n\ 155 | license = \"{}\"\n", 156 | name, version, &args.license_name, 157 | ); 158 | if let Some(registry_name) = args.registry_name { 159 | tomlout.extend(format!("publish = [\"{}\"]\n", registry_name).chars()); 160 | } 161 | tomlout.extend( 162 | format!( 163 | "\n\ 164 | [dependencies]\n\ 165 | {}\n\ 166 | \n", 167 | dependencies(builder, args.include_client).join("\n"), 168 | ) 169 | .chars(), 170 | ); 171 | 172 | save(&toml, tomlout.as_str())?; 173 | 174 | // Create the src/ directory: 175 | let mut src = root; 176 | src.push("src"); 177 | std::fs::create_dir_all(&src)?; 178 | 179 | // Create the Rust source file containing the generated client: 180 | let lib_code = if args.include_client { 181 | format!("mod progenitor_client;\n\n{}", api_code) 182 | } else { 183 | api_code.to_string() 184 | }; 185 | let lib_code = reformat_code(lib_code); 186 | 187 | let mut librs = src.clone(); 188 | librs.push("lib.rs"); 189 | save(librs, lib_code.as_str())?; 190 | 191 | // Create the Rust source file containing the support code: 192 | if args.include_client { 193 | let progenitor_client_code = progenitor_client::code(); 194 | let mut clientrs = src; 195 | clientrs.push("progenitor_client.rs"); 196 | save(clientrs, progenitor_client_code)?; 197 | } 198 | } 199 | 200 | Err(e) => { 201 | println!("gen fail: {:?}", e); 202 | bail!("generation experienced errors"); 203 | } 204 | } 205 | 206 | Ok(()) 207 | } 208 | 209 | // Indirect dependencies may or may not be preserved in built's output so we 210 | // manually encode the versions. We need to take care to update this 211 | // particularly when generated code depends on particular dependency versions. 212 | struct Dependencies { 213 | base64: &'static str, 214 | bytes: &'static str, 215 | chrono: &'static str, 216 | futures: &'static str, 217 | percent_encoding: &'static str, 218 | rand: &'static str, 219 | regress: &'static str, 220 | reqwest: &'static str, 221 | serde: &'static str, 222 | serde_json: &'static str, 223 | serde_urlencoded: &'static str, 224 | uuid: &'static str, 225 | } 226 | 227 | static DEPENDENCIES: Dependencies = Dependencies { 228 | base64: "0.22", 229 | bytes: "1.9", 230 | chrono: "0.4", 231 | futures: "0.3", 232 | percent_encoding: "2.3", 233 | rand: "0.8", 234 | regress: "0.10", 235 | reqwest: "0.12", 236 | serde: "1.0", 237 | serde_json: "1.0", 238 | serde_urlencoded: "0.7", 239 | uuid: "1.0", 240 | }; 241 | 242 | pub fn dependencies(builder: Generator, include_client: bool) -> Vec { 243 | let mut deps = vec![ 244 | format!("bytes = \"{}\"", DEPENDENCIES.bytes), 245 | format!("futures-core = \"{}\"", DEPENDENCIES.futures), 246 | format!("reqwest = {{ version = \"{}\", default-features=false, features = [\"json\", \"stream\"] }}", DEPENDENCIES.reqwest), 247 | format!("serde = {{ version = \"{}\", features = [\"derive\"] }}", DEPENDENCIES.serde), 248 | format!("serde_urlencoded = \"{}\"", DEPENDENCIES.serde_urlencoded), 249 | ]; 250 | 251 | let type_space = builder.get_type_space(); 252 | let mut needs_serde_json = false; 253 | 254 | if include_client { 255 | // code included from progenitor-client needs extra dependencies 256 | deps.push(format!( 257 | "percent-encoding = \"{}\"", 258 | DEPENDENCIES.percent_encoding 259 | )); 260 | needs_serde_json = true; 261 | } else { 262 | let crate_version = 263 | if let (false, Some(value)) = (is_non_release(), option_env!("CARGO_PKG_VERSION")) { 264 | value 265 | } else { 266 | "*" 267 | }; 268 | let client_version_dep = format!("progenitor-client = \"{}\"", crate_version); 269 | deps.push(client_version_dep); 270 | } 271 | 272 | if type_space.uses_regress() { 273 | deps.push(format!("regress = \"{}\"", DEPENDENCIES.regress)); 274 | } 275 | if type_space.uses_uuid() { 276 | deps.push(format!( 277 | "uuid = {{ version = \"{}\", features = [\"serde\", \"v4\"] }}", 278 | DEPENDENCIES.uuid 279 | )); 280 | } 281 | if type_space.uses_chrono() { 282 | deps.push(format!( 283 | "chrono = {{ version = \"{}\", default-features=false, features = [\"serde\"] }}", 284 | DEPENDENCIES.chrono 285 | )); 286 | } 287 | if builder.uses_futures() { 288 | deps.push(format!("futures = \"{}\"", DEPENDENCIES.futures)); 289 | } 290 | if builder.uses_websockets() { 291 | deps.push(format!("base64 = \"{}\"", DEPENDENCIES.base64)); 292 | deps.push(format!("rand = \"{}\"", DEPENDENCIES.rand)); 293 | } 294 | if type_space.uses_serde_json() || needs_serde_json { 295 | deps.push(format!("serde_json = \"{}\"", DEPENDENCIES.serde_json)); 296 | } 297 | deps.sort_unstable(); 298 | deps 299 | } 300 | 301 | fn load_api

(p: P) -> Result 302 | where 303 | P: AsRef + std::clone::Clone + std::fmt::Debug, 304 | { 305 | let mut f = File::open(p.clone())?; 306 | let api = match serde_json::from_reader(f) { 307 | Ok(json_value) => json_value, 308 | _ => { 309 | f = File::open(p)?; 310 | serde_yaml::from_reader(f)? 311 | } 312 | }; 313 | Ok(api) 314 | } 315 | -------------------------------------------------------------------------------- /cargo-progenitor/tests/data/test_help.stdout: -------------------------------------------------------------------------------- 1 | Generate a stand-alone crate from an OpenAPI document 2 | 3 | Usage: cargo progenitor [OPTIONS] --input --output --name --version 4 | 5 | Options: 6 | -i, --input 7 | OpenAPI definition document (JSON or YAML) 8 | -o, --output 9 | Output directory for Rust crate 10 | -n, --name 11 | Target Rust crate name 12 | -v, --version 13 | Target Rust crate version 14 | --registry-name 15 | Target Rust crate registry 16 | --license-name 17 | Target crate license [default: "SPECIFY A LICENSE BEFORE PUBLISHING"] 18 | --interface 19 | SDK interface style [default: positional] [possible values: positional, builder] 20 | --tags 21 | SDK tag style [default: merged] [possible values: merged, separate] 22 | --include-client 23 | Include client code rather than depending on progenitor-client [default: true] [possible values: true, false] 24 | -h, --help 25 | Print help 26 | -------------------------------------------------------------------------------- /cargo-progenitor/tests/test_cmd.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Oxide Computer Company 2 | 3 | use assert_cmd::Command; 4 | 5 | #[test] 6 | fn test_help() { 7 | Command::cargo_bin("cargo-progenitor") 8 | .unwrap() 9 | .arg("progenitor") 10 | .arg("--help") 11 | .assert() 12 | .success() 13 | .stdout(expectorate::eq_file_or_panic("tests/data/test_help.stdout")); 14 | } 15 | -------------------------------------------------------------------------------- /docs/builder-generation.md: -------------------------------------------------------------------------------- 1 | # Builder Generation 2 | 3 | When the "builder" style is specified, Progenitor generates methods and builder 4 | struct according to operations within an OpenAPI document. They take the 5 | following general form: 6 | 7 | ```rust 8 | impl Client { 9 | pub fn operation_name(&self) -> builder::InstanceCreate { 10 | builder::OperationName::new(self) 11 | } 12 | } 13 | 14 | mod builder { 15 | #[derive(Debug, Clone)] 16 | pub struct OperationName<'a> { 17 | client: &'a super::Client, 18 | 19 | path_parameter_1: Result, 20 | path_parameter_2: Result, 21 | query_parameter_1: Result, 22 | query_parameter_2: Result, String>, 23 | body: Result, 24 | } 25 | 26 | impl<'a> OperationName<'a> { 27 | pub fn new(client: &'a super::Client) -> Self { 28 | Self { 29 | client, 30 | path_parameter_1: Err("path_parameter_1 was not initialized".to_string()), 31 | path_parameter_2: Err("path_parameter_2 was not initialized".to_string()), 32 | query_parameter_1: Err("query_parameter_1 was not initialized".to_string()), 33 | query_parameter_2: Ok(None), 34 | body: Err("body was not initialized".to_string()), 35 | } 36 | } 37 | 38 | pub fn path_parameter_1(mut self, value: V) -> Self 39 | where 40 | V: std::convert::TryInto, 41 | { 42 | self.organization_name = value 43 | .try_into() 44 | .map_err(|_| "conversion to `String` for path_parameter_1 failed".to_string()); 45 | self 46 | } 47 | 48 | pub fn path_parameter_2(mut self, value: V) -> Self 49 | where 50 | V: std::convert::TryInto, 51 | { 52 | self.organization_name = value 53 | .try_into() 54 | .map_err(|_| "conversion to `u32` for path_parameter_2 failed".to_string()); 55 | self 56 | } 57 | 58 | pub fn query_parameter_1(mut self, value: V) -> Self 59 | where 60 | V: std::convert::TryInto, 61 | { 62 | self.organization_name = value 63 | .try_into() 64 | .map_err(|_| "conversion to `String` for query_parameter_1 failed".to_string()); 65 | self 66 | } 67 | 68 | pub fn query_parameter_2(mut self, value: V) -> Self 69 | where 70 | V: std::convert::TryInto, 71 | { 72 | self.organization_name = value 73 | .try_into() 74 | .map_err(|_| "conversion to `u32` for query_parameter_2 failed".to_string()); 75 | self 76 | } 77 | 78 | pub fn body(mut self, value: V) -> Self 79 | where 80 | V: TryInto, 81 | { 82 | self.body = value 83 | .try_into() 84 | .map_err(|_| "conversion to `ThisOperationBody` for body failed".to_string()); 85 | self 86 | } 87 | 88 | pub async fn send(self) -> Result< 89 | ResponseValue, 90 | Error, 91 | > { 92 | // ... 93 | } 94 | } 95 | ``` 96 | 97 | For more info on the `ResponseValue` and `Error` types, see 98 | [progenitor_client](./progenitor-client.md). 99 | 100 | Note that `send` methods are `async` so must be `await`ed to get the response value. 101 | 102 | ### Dropshot Paginated Operations 103 | 104 | Dropshot defines a mechanism for pagination. If that mechanism is used for a 105 | particular operation, Progenitor will generate an additional `stream` method on 106 | the builder struct that produces a `Stream`. Consumers can iterate over all 107 | items in the paginated collection without manually fetching individual pages. 108 | 109 | Here's the signature for a typical generated method: 110 | 111 | ```rust 112 | impl<'a> OperationName<'a> { 113 | pub fn stream( 114 | self, 115 | ) -> impl futures::Stream< 116 | Item = Result> 117 | > + Unpin + 'a { 118 | // ... 119 | } 120 | } 121 | ``` 122 | 123 | A typical consumer of this method might look like this: 124 | 125 | ```rust 126 | let mut stream = client.operation_name().stream(); 127 | loop { 128 | match stream.try_next().await { 129 | Ok(Some(item)) => println!("item {:?}", item), 130 | Ok(None) => { 131 | println!("done."); 132 | break; 133 | } 134 | Err(_) => { 135 | println!("error!"); 136 | break; 137 | } 138 | } 139 | } 140 | ``` 141 | -------------------------------------------------------------------------------- /docs/implementing-client.md: -------------------------------------------------------------------------------- 1 | # Implementing the client 2 | 3 | Once you have generated your client, you'll notice there are two methods to 4 | create a new `Client`; `Client::new()` and `Client::new_with_client()`. 5 | 6 | The first method will create a basic client without any headers or 7 | customizations that aren't already in your OpenAPI specification file: 8 | 9 | ```rust 10 | let client = Client::new("https://foo/bar"); 11 | ``` 12 | 13 | Should you need to set custom headers or other `reqwest::ClientBuilder` 14 | methods, you can use `Client::new_with_client()` as shown below. 15 | 16 | ```rust 17 | let mut val = reqwest::header::HeaderValue::from_static("super-secret"); 18 | val.set_sensitive(true); 19 | let mut headers = reqwest::header::HeaderMap::new(); 20 | headers.insert(reqwest::header::AUTHORIZATION, val); 21 | // Insert more headers if necessary 22 | 23 | let client_builder = reqwest::ClientBuilder::new() 24 | // Set custom timeout 25 | .connect_timeout(Duration::new(60, 0)) 26 | // Set custom headers 27 | .default_headers(headers) 28 | .build() 29 | .unwrap(); 30 | 31 | let client Client::new_with_client("https://foo/bar", client_builder); 32 | ``` 33 | 34 | For more information on available methods, see the 35 | [reqwest](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html) 36 | documentation. -------------------------------------------------------------------------------- /docs/positional-generation.md: -------------------------------------------------------------------------------- 1 | # Positional Generation 2 | 3 | When the "positional" style is specified, Progenitor generates methods 4 | according to operations within an OpenAPI document. Each takes the following 5 | general form: 6 | 7 | ```rust 8 | impl Client { 9 | pub async fn operation_name<'a>( 10 | &'a self, 11 | // Path parameters (if any) come first and are always mandatory 12 | path_parameter_1: String, 13 | path_parameter_2: u32, 14 | // Query parameters (if any) come next and may be optional 15 | query_parameter_1: String, 16 | query_parameter_2: Option, 17 | // A body parameter (if specified) comes last 18 | body: &types::ThisOperationBody, 19 | ) -> Result< 20 | ResponseValue, 21 | Error, 22 | > { 23 | // ... 24 | } 25 | ``` 26 | 27 | For more info on the `ResponseValue` and `Error` types, see 28 | [progenitor_client](./progenitor-client.md). 29 | 30 | Note that methods are `async` so must be `await`ed to get the response value. 31 | 32 | ### Dropshot Paginated Operations 33 | 34 | Dropshot defines a mechanism for pagination. If that mechanism is used for a 35 | particular operation, Progenitor will generate an additional method that 36 | produces a `Stream`. Consumers can iterate over all items in the paginated 37 | collection without manually fetching individual pages. 38 | 39 | Here's the signature for a typical generated method: 40 | 41 | ```rust 42 | pub fn operation_name_stream<'a>( 43 | &'a self, 44 | // Specific parameters... 45 | limit: Option, 46 | ) -> impl futures::Stream< 47 | Item = Result> 48 | > + Unpin + '_ { 49 | // ... 50 | } 51 | ``` 52 | 53 | A typical consumer of this method might look like this: 54 | 55 | ```rust 56 | let mut stream = client.operation_name_stream(None); 57 | loop { 58 | match stream.try_next().await { 59 | Ok(Some(item)) => println!("item {:?}", item), 60 | Ok(None) => { 61 | println!("done."); 62 | break; 63 | } 64 | Err(_) => { 65 | println!("error!"); 66 | break; 67 | } 68 | } 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/progenitor-client.md: -------------------------------------------------------------------------------- 1 | # Progenitor Client 2 | 3 | The `progenitor-client` crate contains types that are exported by generated 4 | clients as well as functions that are used internally by generated clients. 5 | Depending on how `progenitor` is being used, the crate will be included in 6 | different ways (see ["Using Progenitor"](../README.md#using_progenitor)). 7 | 8 | - For macro consumers, it comes from the `progenitor` dependency. 9 | 10 | - For builder consumers, it must be specified under `[dependencies]` (while `progenitor` is under `[build-dependencies]`). 11 | 12 | - For statically generated consumers, the code is emitted into 13 | `src/progenitor_client.rs`. 14 | 15 | The two types that `progenitor-client` exports are `Error` and 16 | `ResponseValue`. A typical generated method will use these types in its 17 | signature: 18 | 19 | ```rust 20 | impl Client { 21 | pub async fn operation_name<'a>( 22 | &'a self, 23 | // parameters ... 24 | ) -> Result< 25 | ResponseValue, 26 | Error> 27 | { 28 | // ... 29 | } 30 | ``` 31 | 32 | ## `ResponseValue` 33 | 34 | OpenAPI documents defines the types that an operation returns. Generated 35 | methods wrap these types in `ResponseValue` for two reasons: there is 36 | additional information that may be included in a response such as the specific 37 | status code and headers, and that information cannot simply be included in the 38 | response type because that type may be used in other context (e.g. as a body 39 | parameter). 40 | 41 | These are the relevant implementations for `ResponseValue`: 42 | 43 | ```rust 44 | /// Success value returned by generated client methods. 45 | pub struct ResponseValue { .. } 46 | 47 | impl ResponseValue { 48 | pub fn status(&self) -> &reqwest::StatusCode { .. } 49 | pub fn headers(&self) -> &reqwest::header::HeaderMap { .. } 50 | pub fn into_inner(self) -> T { .. } 51 | } 52 | impl std::ops::Deref for ResponseValue { 53 | type Target = T; 54 | .. 55 | } 56 | impl std::ops::DerefMut for ResponseValue { .. } 57 | 58 | impl std::fmt::Debug for ResponseValue { .. } 59 | ``` 60 | 61 | It can be used as the type `T` in most instances and extracted as a `T` using 62 | `into_inner()`. 63 | 64 | ## `Error` 65 | 66 | There are seven sub-categories of error covered by the error type variants: 67 | 68 | - A request that did not conform to API requirements. 69 | This can occur when required builder or body parameters were not specified, 70 | and the error message will denote the specific failure. 71 | 72 | - A communication error 73 | 74 | - An expected error response when upgrading connection. 75 | 76 | - An expected error response, defined by the OpenAPI document 77 | with a 4xx or 5xx status code 78 | 79 | - An expected status code that encountered an error reading the body 80 | or the payload deserialization failed 81 | (this could be viewed as a sub-type of communication error), 82 | but it is separately identified as there's more information; 83 | note that this covers both success and error status codes 84 | 85 | - An unexpected status code in the response 86 | 87 | - A custom error, particular to the generated client 88 | 89 | These errors are covered by the variants of the `Error` type: 90 | 91 | ```rust 92 | pub enum Error { 93 | InvalidRequest(String), 94 | CommunicationError(reqwest::Error), 95 | InvalidUpgrade(reqwest::Error), 96 | ErrorResponse(ResponseValue), 97 | ResponseBodyError(reqwest::Error), 98 | InvalidResponsePayload(bytes::Bytes, reqwest::Error), 99 | UnexpectedResponse(reqwest::Response), 100 | Custom(String), 101 | } 102 | ``` 103 | -------------------------------------------------------------------------------- /example-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-build" 3 | version = "0.0.1" 4 | authors = ["Adam H. Leventhal "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | chrono = { version = "0.4", features = ["serde"] } 9 | progenitor-client = { path = "../progenitor-client" } 10 | reqwest = { version = "0.12.4", features = ["json", "stream"] } 11 | base64 = "0.22" 12 | rand = "0.9" 13 | serde = { version = "1.0", features = ["derive"] } 14 | uuid = { version = "1.17", features = ["serde", "v4"] } 15 | 16 | [build-dependencies] 17 | prettyplease = "0.2.33" 18 | progenitor = { path = "../progenitor", default-features = false } 19 | serde_json = "1.0" 20 | syn = "2.0" 21 | -------------------------------------------------------------------------------- /example-build/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use std::{ 4 | env, 5 | fs::{self, File}, 6 | path::Path, 7 | }; 8 | 9 | fn main() { 10 | let src = "../sample_openapi/keeper.json"; 11 | println!("cargo:rerun-if-changed={}", src); 12 | let file = File::open(src).unwrap(); 13 | let spec = serde_json::from_reader(file).unwrap(); 14 | let mut generator = progenitor::Generator::default(); 15 | 16 | let tokens = generator.generate_tokens(&spec).unwrap(); 17 | let ast = syn::parse2(tokens).unwrap(); 18 | let content = prettyplease::unparse(&ast); 19 | 20 | let mut out_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf(); 21 | out_file.push("codegen.rs"); 22 | 23 | fs::write(out_file, content).unwrap(); 24 | } 25 | -------------------------------------------------------------------------------- /example-build/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /example-build/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Oxide Computer Company 2 | 3 | // Include the generated code. 4 | include!(concat!(env!("OUT_DIR"), "/codegen.rs")); 5 | 6 | fn main() { 7 | let client = Client::new("https://foo/bar"); 8 | let _ = client.enrol( 9 | "auth-token", 10 | &types::EnrolBody { 11 | host: "".to_string(), 12 | key: "".to_string(), 13 | }, 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /example-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-macro" 3 | version = "0.0.1" 4 | authors = ["Adam H. Leventhal "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | chrono = { version = "0.4", features = ["serde"] } 9 | progenitor = { path = "../progenitor" } 10 | reqwest = { version = "0.12.4", features = ["json", "stream"] } 11 | schemars = { version = "0.8.22", features = ["uuid1"] } 12 | serde = { version = "1.0", features = ["derive"] } 13 | uuid = { version = "1.17", features = ["serde", "v4"] } 14 | -------------------------------------------------------------------------------- /example-macro/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /example-macro/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use progenitor::generate_api; 4 | 5 | generate_api!( 6 | spec = "../sample_openapi/keeper.json", 7 | pre_hook = (|request| { 8 | println!("doing this {:?}", request); 9 | }), 10 | pre_hook_async = crate::add_auth_headers, 11 | post_hook = crate::all_done, 12 | derives = [schemars::JsonSchema], 13 | ); 14 | 15 | async fn add_auth_headers( 16 | req: &mut reqwest::Request, 17 | ) -> Result<(), reqwest::header::InvalidHeaderValue> { 18 | // You can perform asynchronous, fallible work in a request hook, then 19 | // modify the request right before it is transmitted to the server; e.g., 20 | // for generating an authenticaiton signature based on the complete set of 21 | // request header values: 22 | req.headers_mut().insert( 23 | reqwest::header::AUTHORIZATION, 24 | reqwest::header::HeaderValue::from_str("legitimate")?, 25 | ); 26 | 27 | Ok(()) 28 | } 29 | 30 | fn all_done(_result: &reqwest::Result) {} 31 | 32 | mod buildomat { 33 | use progenitor::generate_api; 34 | 35 | generate_api!("../sample_openapi/buildomat.json"); 36 | } 37 | 38 | fn main() {} 39 | -------------------------------------------------------------------------------- /example-wasm/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | runner = 'wasm-bindgen-test-runner' 3 | -------------------------------------------------------------------------------- /example-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | chrono = { version = "0.4", features = ["serde"] } 8 | progenitor-client = { path = "../progenitor-client" } 9 | reqwest = { version = "0.12.4", features = ["json", "stream"] } 10 | base64 = "0.22" 11 | serde = { version = "1.0", features = ["derive"] } 12 | uuid = { version = "1.17", features = ["serde", "v4", "js"] } 13 | 14 | [dev-dependencies] 15 | wasm-bindgen-test = "0.3.50" 16 | 17 | [build-dependencies] 18 | prettyplease = "0.2.33" 19 | progenitor = { path = "../progenitor" } 20 | serde_json = "1.0" 21 | syn = "2.0" 22 | -------------------------------------------------------------------------------- /example-wasm/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use std::{ 4 | env, 5 | fs::{self, File}, 6 | path::Path, 7 | }; 8 | 9 | fn main() { 10 | let src = "../sample_openapi/keeper.json"; 11 | println!("cargo:rerun-if-changed={}", src); 12 | let file = File::open(src).unwrap(); 13 | let spec = serde_json::from_reader(file).unwrap(); 14 | let mut generator = progenitor::Generator::default(); 15 | 16 | let tokens = generator.generate_tokens(&spec).unwrap(); 17 | let ast = syn::parse2(tokens).unwrap(); 18 | let content = prettyplease::unparse(&ast); 19 | 20 | let mut out_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf(); 21 | out_file.push("codegen.rs"); 22 | 23 | fs::write(out_file, content).unwrap(); 24 | } 25 | -------------------------------------------------------------------------------- /example-wasm/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /example-wasm/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Oxide Computer Company 2 | 3 | // Include the generated code. 4 | include!(concat!(env!("OUT_DIR"), "/codegen.rs")); 5 | 6 | fn main() { 7 | let client = Client::new("https://foo/bar"); 8 | let _ = client.enrol( 9 | "auth-token", 10 | &types::EnrolBody { 11 | host: "".to_string(), 12 | key: "".to_string(), 13 | }, 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /example-wasm/tests/client.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Oxide Computer Company 2 | 3 | wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); 4 | 5 | include!(concat!(env!("OUT_DIR"), "/codegen.rs")); 6 | 7 | #[cfg(target_arch = "wasm32")] 8 | #[wasm_bindgen_test::wasm_bindgen_test] 9 | fn test_client_new() { 10 | let client = Client::new("http://foo/bar"); 11 | assert!(client.baseurl == "http://foo/bar"); 12 | } 13 | -------------------------------------------------------------------------------- /progenitor-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "progenitor-client" 3 | version = "0.11.0" 4 | edition = "2021" 5 | license = "MPL-2.0" 6 | repository = "https://github.com/oxidecomputer/progenitor.git" 7 | description = "An OpenAPI client generator - client support" 8 | 9 | [dependencies] 10 | bytes = { workspace = true } 11 | futures-core = { workspace = true } 12 | percent-encoding = { workspace = true } 13 | reqwest = { workspace = true } 14 | serde = { workspace = true } 15 | serde_json = { workspace = true } 16 | serde_urlencoded = { workspace = true } 17 | 18 | [dev-dependencies] 19 | url = { workspace = true } 20 | uuid = { workspace = true } 21 | -------------------------------------------------------------------------------- /progenitor-client/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [] 2 | -------------------------------------------------------------------------------- /progenitor-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | //! Support for generated clients. 4 | 5 | #![deny(missing_docs)] 6 | 7 | mod progenitor_client; 8 | 9 | pub use crate::progenitor_client::*; 10 | 11 | // For stand-alone crates, rather than adding a dependency on 12 | // progenitor-client, we simply dump the code right in. This means we don't 13 | // need to determine the provenance of progenitor (crates.io, github, etc.) 14 | // when generating the stand-alone crate. 15 | #[doc(hidden)] 16 | pub fn code() -> &'static str { 17 | include_str!("progenitor_client.rs") 18 | } 19 | -------------------------------------------------------------------------------- /progenitor-client/tests/client_test.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Oxide Computer Company 2 | 3 | use std::{ 4 | collections::{BTreeMap, BTreeSet, HashSet}, 5 | error::Error, 6 | }; 7 | 8 | use progenitor_client::{encode_path, QueryParam}; 9 | use serde::Serialize; 10 | 11 | #[test] 12 | fn test_path_segment_encoding() { 13 | assert_eq!(encode_path("192.168.0.0/24"), "192.168.0.0%2F24"); 14 | } 15 | 16 | fn encode_query_param(param_name: &str, value: &T) -> Result> { 17 | let mut url = url::Url::parse("https://localhost")?; 18 | let mut pairs = url.query_pairs_mut(); 19 | let serializer = serde_urlencoded::Serializer::new(&mut pairs); 20 | 21 | QueryParam::new(param_name, value).serialize(serializer)?; 22 | drop(pairs); 23 | 24 | Ok(url.query().unwrap().to_owned()) 25 | } 26 | 27 | #[test] 28 | fn test_query_scalars() { 29 | let value = "xyz".to_string(); 30 | let result = encode_query_param("param_name", &value).unwrap(); 31 | assert_eq!(result, "param_name=xyz"); 32 | 33 | let value = 42; 34 | let result = encode_query_param("param_name", &value).unwrap(); 35 | assert_eq!(result, "param_name=42"); 36 | 37 | let value = -0.05; 38 | let result = encode_query_param("param_name", &value).unwrap(); 39 | assert_eq!(result, "param_name=-0.05"); 40 | } 41 | 42 | #[test] 43 | fn test_query_arrays() { 44 | let value = vec!["a", "b", "c"]; 45 | let result = encode_query_param("paramName", &value).unwrap(); 46 | 47 | assert_eq!(result, "paramName=a¶mName=b¶mName=c"); 48 | 49 | let value = ["a", "b", "c"].into_iter().collect::>(); 50 | let result = encode_query_param("paramName", &value).unwrap(); 51 | 52 | assert_eq!(result, "paramName=a¶mName=b¶mName=c"); 53 | 54 | let value = ["a", "b", "c"].into_iter().collect::>(); 55 | let result = encode_query_param("paramName", &value).unwrap(); 56 | 57 | // Handle hash ordering. 58 | assert_eq!( 59 | result, 60 | value 61 | .iter() 62 | .map(|v| format!("paramName={v}")) 63 | .collect::>() 64 | .join("&") 65 | ); 66 | } 67 | 68 | #[test] 69 | fn test_query_object() { 70 | #[derive(Serialize)] 71 | struct Value { 72 | a: String, 73 | b: String, 74 | } 75 | let value = Value { 76 | a: "a value".to_string(), 77 | b: "b value".to_string(), 78 | }; 79 | let result = encode_query_param("ignored", &value).unwrap(); 80 | 81 | assert_eq!(result, "a=a+value&b=b+value"); 82 | } 83 | 84 | #[test] 85 | fn test_query_map() { 86 | let value = [("a", "a value"), ("b", "b value")] 87 | .into_iter() 88 | .collect::>(); 89 | let result = encode_query_param("ignored", &value).unwrap(); 90 | assert_eq!(result, "a=a+value&b=b+value"); 91 | } 92 | 93 | #[test] 94 | fn test_query_enum_external() { 95 | #[derive(Serialize)] 96 | #[serde(rename_all = "snake_case")] 97 | enum Value { 98 | Simple, 99 | Newtype(u64), 100 | Tuple(u64, u64), 101 | Object { a: u64, b: u64 }, 102 | } 103 | let value = Value::Simple; 104 | let result = encode_query_param("paramValue", &value).unwrap(); 105 | assert_eq!(result, "paramValue=simple"); 106 | 107 | let value = Value::Newtype(42); 108 | let result = encode_query_param("ignored", &value).unwrap(); 109 | assert_eq!(result, "newtype=42"); 110 | 111 | let value = Value::Tuple(1, 2); 112 | encode_query_param("ignored", &value).expect_err("variant not supported"); 113 | 114 | let value = Value::Object { a: 3, b: 4 }; 115 | encode_query_param("ignored", &value).expect_err("variant not supported"); 116 | } 117 | 118 | #[test] 119 | fn test_query_enum_internal() { 120 | #[derive(Serialize)] 121 | #[serde(tag = "tag_name")] 122 | #[serde(rename_all = "snake_case")] 123 | enum Value { 124 | Simple, 125 | Object { a: u64, b: u64 }, 126 | } 127 | let value = Value::Simple; 128 | let result = encode_query_param("ignored", &value).unwrap(); 129 | assert_eq!(result, "tag_name=simple"); 130 | 131 | let value = Value::Object { a: 3, b: 4 }; 132 | let result = encode_query_param("ignored", &value).unwrap(); 133 | assert_eq!(result, "tag_name=object&a=3&b=4"); 134 | } 135 | 136 | #[test] 137 | fn test_query_enum_adjacent() { 138 | #[derive(Serialize)] 139 | #[serde(tag = "tag_name", content = "content_name")] 140 | #[serde(rename_all = "snake_case")] 141 | enum Value { 142 | Simple, 143 | Newtype(u64), 144 | Tuple(u64, u64), 145 | Object { a: u64, b: u64 }, 146 | } 147 | let value = Value::Simple; 148 | let result = encode_query_param("ignored", &value).unwrap(); 149 | assert_eq!(result, "tag_name=simple"); 150 | 151 | let value = Value::Newtype(42); 152 | let result = encode_query_param("ignored", &value).unwrap(); 153 | assert_eq!(result, "tag_name=newtype&content_name=42"); 154 | 155 | let value = Value::Tuple(1, 2); 156 | encode_query_param("ignored", &value).expect_err("invalid variant"); 157 | 158 | let value = Value::Object { a: 3, b: 4 }; 159 | encode_query_param("ignored", &value).expect_err("invalid variant"); 160 | } 161 | 162 | #[test] 163 | fn test_query_enum_untagged() { 164 | #[derive(Serialize)] 165 | #[serde(untagged)] 166 | #[serde(rename_all = "snake_case")] 167 | enum Value { 168 | Simple, 169 | Newtype(u64), 170 | Tuple(u64, u64), 171 | Object { a: u64, b: u64 }, 172 | Array(Vec), 173 | } 174 | let value = Value::Simple; 175 | let result = encode_query_param("ignored", &value).unwrap(); 176 | assert_eq!(result, ""); 177 | 178 | let value = Value::Newtype(42); 179 | let result = encode_query_param("paramName", &value).unwrap(); 180 | assert_eq!(result, "paramName=42"); 181 | 182 | let value = Value::Tuple(1, 2); 183 | encode_query_param("ignored", &value).expect_err("invalid variant"); 184 | 185 | let value = Value::Object { a: 3, b: 4 }; 186 | let result = encode_query_param("ignored", &value).unwrap(); 187 | assert_eq!(result, "a=3&b=4"); 188 | 189 | let value = Value::Array(vec![1, 2, 3, 4]); 190 | let result = encode_query_param("paramName", &value).unwrap(); 191 | assert_eq!(result, "paramName=1¶mName=2¶mName=3¶mName=4"); 192 | 193 | #[derive(Serialize)] 194 | #[serde(transparent)] 195 | struct Name(String); 196 | #[derive(Serialize)] 197 | #[serde(untagged)] 198 | enum NameOrId { 199 | Name(Name), 200 | Id(uuid::Uuid), 201 | } 202 | let value = Some(NameOrId::Name(Name("xyz".to_string()))); 203 | let result = encode_query_param("paramName", &value).unwrap(); 204 | assert_eq!(result, "paramName=xyz"); 205 | 206 | let id = uuid::Uuid::new_v4(); 207 | let value = Some(NameOrId::Id(id)); 208 | let result = encode_query_param("paramName", &value).unwrap(); 209 | assert_eq!(result, format!("paramName={id}")); 210 | } 211 | 212 | #[test] 213 | fn test_query_option() { 214 | let value = Option::::None; 215 | let result = encode_query_param("ignored", &value).unwrap(); 216 | assert_eq!(result, ""); 217 | 218 | let value = Some(42); 219 | let result = encode_query_param("paramName", &value).unwrap(); 220 | assert_eq!(result, "paramName=42"); 221 | } 222 | -------------------------------------------------------------------------------- /progenitor-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "progenitor-impl" 3 | version = "0.11.0" 4 | edition = "2021" 5 | license = "MPL-2.0" 6 | description = "An OpenAPI client generator - core implementation" 7 | repository = "https://github.com/oxidecomputer/progenitor.git" 8 | readme = "../README.md" 9 | 10 | [dependencies] 11 | heck = { workspace = true } 12 | http = { workspace = true } 13 | indexmap = { workspace = true } 14 | openapiv3 = { workspace = true } 15 | proc-macro2 = { workspace = true } 16 | quote = { workspace = true } 17 | regex = { workspace = true } 18 | schemars = { workspace = true } 19 | serde = { workspace = true } 20 | serde_json = { workspace = true } 21 | syn = { workspace = true } 22 | thiserror = { workspace = true } 23 | typify = { workspace = true } 24 | unicode-ident = { workspace = true } 25 | 26 | [dev-dependencies] 27 | dropshot = { workspace = true } 28 | expectorate = { workspace = true } 29 | futures = { workspace = true } 30 | http = { workspace = true } 31 | hyper = { workspace = true } 32 | progenitor-client = { workspace = true } 33 | reqwest = { workspace = true } 34 | rustfmt-wrapper = { workspace = true } 35 | semver = { workspace = true } 36 | serde_yaml = { workspace = true } 37 | tokio = { workspace = true } 38 | -------------------------------------------------------------------------------- /progenitor-impl/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [] 2 | -------------------------------------------------------------------------------- /progenitor-impl/src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use std::collections::BTreeMap; 4 | 5 | use indexmap::IndexMap; 6 | use openapiv3::{Components, Parameter, ReferenceOr, RequestBody, Response, Schema}; 7 | use unicode_ident::{is_xid_continue, is_xid_start}; 8 | 9 | use crate::Result; 10 | 11 | pub(crate) trait ReferenceOrExt { 12 | fn item<'a>(&'a self, components: &'a Option) -> Result<&'a T>; 13 | } 14 | pub(crate) trait ComponentLookup: Sized { 15 | fn get_components(components: &Components) -> &IndexMap>; 16 | } 17 | 18 | impl ReferenceOrExt for openapiv3::ReferenceOr { 19 | fn item<'a>(&'a self, components: &'a Option) -> Result<&'a T> { 20 | match self { 21 | ReferenceOr::Item(item) => Ok(item), 22 | ReferenceOr::Reference { reference } => { 23 | let idx = reference.rfind('/').unwrap(); 24 | let key = &reference[idx + 1..]; 25 | let parameters = T::get_components(components.as_ref().unwrap()); 26 | parameters 27 | .get(key) 28 | .unwrap_or_else(|| panic!("key {} is missing", key)) 29 | .item(components) 30 | } 31 | } 32 | } 33 | } 34 | 35 | pub(crate) fn items<'a, T>( 36 | refs: &'a [ReferenceOr], 37 | components: &'a Option, 38 | ) -> impl Iterator> 39 | where 40 | T: ComponentLookup, 41 | { 42 | refs.iter().map(|r| r.item(components)) 43 | } 44 | 45 | pub(crate) fn parameter_map<'a>( 46 | refs: &'a [ReferenceOr], 47 | components: &'a Option, 48 | ) -> Result> { 49 | items(refs, components) 50 | .map(|res| res.map(|param| (¶m.parameter_data_ref().name, param))) 51 | .collect() 52 | } 53 | 54 | impl ComponentLookup for Parameter { 55 | fn get_components(components: &Components) -> &IndexMap> { 56 | &components.parameters 57 | } 58 | } 59 | 60 | impl ComponentLookup for RequestBody { 61 | fn get_components(components: &Components) -> &IndexMap> { 62 | &components.request_bodies 63 | } 64 | } 65 | 66 | impl ComponentLookup for Response { 67 | fn get_components(components: &Components) -> &IndexMap> { 68 | &components.responses 69 | } 70 | } 71 | 72 | impl ComponentLookup for Schema { 73 | fn get_components(components: &Components) -> &IndexMap> { 74 | &components.schemas 75 | } 76 | } 77 | 78 | pub(crate) enum Case { 79 | Pascal, 80 | Snake, 81 | } 82 | 83 | pub(crate) fn sanitize(input: &str, case: Case) -> String { 84 | use heck::{ToPascalCase, ToSnakeCase}; 85 | let to_case = match case { 86 | Case::Pascal => str::to_pascal_case, 87 | Case::Snake => str::to_snake_case, 88 | }; 89 | // If every case was special then none of them would be. 90 | let out = match input { 91 | "+1" => "plus1".to_string(), 92 | "-1" => "minus1".to_string(), 93 | _ => to_case( 94 | &input 95 | .replace('\'', "") 96 | .replace(|c: char| !is_xid_continue(c), "-"), 97 | ), 98 | }; 99 | 100 | let out = match out.chars().next() { 101 | None => to_case("x"), 102 | Some(c) if is_xid_start(c) => out, 103 | Some(_) => format!("_{}", out), 104 | }; 105 | 106 | // Make sure the string is a valid Rust identifier. 107 | if syn::parse_str::(&out).is_ok() { 108 | out 109 | } else { 110 | format!("{}_", out) 111 | } 112 | } 113 | 114 | /// Given a desired name and a slice of proc_macro2::Ident, generate a new 115 | /// Ident that is unique from the slice. 116 | pub(crate) fn unique_ident_from( 117 | name: &str, 118 | identities: &[proc_macro2::Ident], 119 | ) -> proc_macro2::Ident { 120 | let mut name = name.to_string(); 121 | 122 | loop { 123 | let ident = quote::format_ident!("{}", name); 124 | 125 | if !identities.contains(&ident) { 126 | return ident; 127 | } 128 | 129 | name.insert_str(0, "_"); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "test-output" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | anyhow = "1.0" 10 | base64 = "0.21" 11 | chrono = { version = "0.4", features = ["serde"] } 12 | clap = { version = "4", features = ["string"] } 13 | futures = "0.3" 14 | httpmock = "0.7" 15 | progenitor-client = { path = "../../../progenitor-client" } 16 | rand = { version = "0.8", features = ["serde1"] } 17 | regex = "1.10" 18 | regress = "0.7" 19 | reqwest = "0.12" 20 | schemars = { version = "0.8", features = ["chrono", "uuid1"] } 21 | serde = { features = ["derive"], version = "1" } 22 | serde_json = "1" 23 | uuid = { features = ["serde", "v4"], version = "1" } 24 | 25 | [lints.rust] 26 | unused_imports = "allow" 27 | unused_variables = "allow" 28 | 29 | [lints.clippy] 30 | needless_lifetimes = "allow" 31 | redundant_field_names = "allow" 32 | vec_init_then_push = "allow" 33 | clone_on_copy = "allow" 34 | unnecessary_to_owned = "allow" 35 | to_string_in_format_args = "allow" 36 | len_zero = "allow" 37 | ptr_arg = "allow" 38 | too_many_arguments = "allow" 39 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/cli_gen_builder.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | 38 | ///`UnoBody` 39 | /// 40 | ///

JSON schema 41 | /// 42 | /// ```json 43 | ///{ 44 | /// "type": "object", 45 | /// "required": [ 46 | /// "required" 47 | /// ], 48 | /// "properties": { 49 | /// "gateway": { 50 | /// "type": "string" 51 | /// } 52 | /// } 53 | ///} 54 | /// ``` 55 | ///
56 | #[derive( 57 | :: serde :: Deserialize, :: serde :: Serialize, Clone, Debug, schemars :: JsonSchema, 58 | )] 59 | pub struct UnoBody { 60 | #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] 61 | pub gateway: ::std::option::Option<::std::string::String>, 62 | pub required: ::serde_json::Value, 63 | } 64 | 65 | impl ::std::convert::From<&UnoBody> for UnoBody { 66 | fn from(value: &UnoBody) -> Self { 67 | value.clone() 68 | } 69 | } 70 | 71 | impl UnoBody { 72 | pub fn builder() -> builder::UnoBody { 73 | Default::default() 74 | } 75 | } 76 | 77 | /// Types for composing complex structures. 78 | pub mod builder { 79 | #[derive(Clone, Debug)] 80 | pub struct UnoBody { 81 | gateway: ::std::result::Result< 82 | ::std::option::Option<::std::string::String>, 83 | ::std::string::String, 84 | >, 85 | required: ::std::result::Result<::serde_json::Value, ::std::string::String>, 86 | } 87 | 88 | impl ::std::default::Default for UnoBody { 89 | fn default() -> Self { 90 | Self { 91 | gateway: Ok(Default::default()), 92 | required: Err("no value supplied for required".to_string()), 93 | } 94 | } 95 | } 96 | 97 | impl UnoBody { 98 | pub fn gateway(mut self, value: T) -> Self 99 | where 100 | T: ::std::convert::TryInto<::std::option::Option<::std::string::String>>, 101 | T::Error: ::std::fmt::Display, 102 | { 103 | self.gateway = value 104 | .try_into() 105 | .map_err(|e| format!("error converting supplied value for gateway: {}", e)); 106 | self 107 | } 108 | pub fn required(mut self, value: T) -> Self 109 | where 110 | T: ::std::convert::TryInto<::serde_json::Value>, 111 | T::Error: ::std::fmt::Display, 112 | { 113 | self.required = value 114 | .try_into() 115 | .map_err(|e| format!("error converting supplied value for required: {}", e)); 116 | self 117 | } 118 | } 119 | 120 | impl ::std::convert::TryFrom for super::UnoBody { 121 | type Error = super::error::ConversionError; 122 | fn try_from( 123 | value: UnoBody, 124 | ) -> ::std::result::Result { 125 | Ok(Self { 126 | gateway: value.gateway?, 127 | required: value.required?, 128 | }) 129 | } 130 | } 131 | 132 | impl ::std::convert::From for UnoBody { 133 | fn from(value: super::UnoBody) -> Self { 134 | Self { 135 | gateway: Ok(value.gateway), 136 | required: Ok(value.required), 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | #[derive(Clone, Debug)] 144 | ///Client for CLI gen test 145 | /// 146 | ///Test case to exercise CLI generation 147 | /// 148 | ///Version: 9000 149 | pub struct Client { 150 | pub(crate) baseurl: String, 151 | pub(crate) client: reqwest::Client, 152 | } 153 | 154 | impl Client { 155 | /// Create a new client. 156 | /// 157 | /// `baseurl` is the base URL provided to the internal 158 | /// `reqwest::Client`, and should include a scheme and hostname, 159 | /// as well as port and a path stem if applicable. 160 | pub fn new(baseurl: &str) -> Self { 161 | #[cfg(not(target_arch = "wasm32"))] 162 | let client = { 163 | let dur = std::time::Duration::from_secs(15); 164 | reqwest::ClientBuilder::new() 165 | .connect_timeout(dur) 166 | .timeout(dur) 167 | }; 168 | #[cfg(target_arch = "wasm32")] 169 | let client = reqwest::ClientBuilder::new(); 170 | Self::new_with_client(baseurl, client.build().unwrap()) 171 | } 172 | 173 | /// Construct a new client with an existing `reqwest::Client`, 174 | /// allowing more control over its configuration. 175 | /// 176 | /// `baseurl` is the base URL provided to the internal 177 | /// `reqwest::Client`, and should include a scheme and hostname, 178 | /// as well as port and a path stem if applicable. 179 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 180 | Self { 181 | baseurl: baseurl.to_string(), 182 | client, 183 | } 184 | } 185 | } 186 | 187 | impl ClientInfo<()> for Client { 188 | fn api_version() -> &'static str { 189 | "9000" 190 | } 191 | 192 | fn baseurl(&self) -> &str { 193 | self.baseurl.as_str() 194 | } 195 | 196 | fn client(&self) -> &reqwest::Client { 197 | &self.client 198 | } 199 | 200 | fn inner(&self) -> &() { 201 | &() 202 | } 203 | } 204 | 205 | impl ClientHooks<()> for &Client {} 206 | impl Client { 207 | ///Sends a `GET` request to `/uno` 208 | /// 209 | ///```ignore 210 | /// let response = client.uno() 211 | /// .gateway(gateway) 212 | /// .body(body) 213 | /// .send() 214 | /// .await; 215 | /// ``` 216 | pub fn uno(&self) -> builder::Uno { 217 | builder::Uno::new(self) 218 | } 219 | } 220 | 221 | /// Types for composing operation parameters. 222 | #[allow(clippy::all)] 223 | pub mod builder { 224 | use super::types; 225 | #[allow(unused_imports)] 226 | use super::{ 227 | encode_path, ByteStream, ClientHooks, ClientInfo, Error, OperationInfo, RequestBuilderExt, 228 | ResponseValue, 229 | }; 230 | ///Builder for [`Client::uno`] 231 | /// 232 | ///[`Client::uno`]: super::Client::uno 233 | #[derive(Debug, Clone)] 234 | pub struct Uno<'a> { 235 | client: &'a super::Client, 236 | gateway: Result<::std::string::String, String>, 237 | body: Result, 238 | } 239 | 240 | impl<'a> Uno<'a> { 241 | pub fn new(client: &'a super::Client) -> Self { 242 | Self { 243 | client: client, 244 | gateway: Err("gateway was not initialized".to_string()), 245 | body: Ok(::std::default::Default::default()), 246 | } 247 | } 248 | 249 | pub fn gateway(mut self, value: V) -> Self 250 | where 251 | V: std::convert::TryInto<::std::string::String>, 252 | { 253 | self.gateway = value.try_into().map_err(|_| { 254 | "conversion to `:: std :: string :: String` for gateway failed".to_string() 255 | }); 256 | self 257 | } 258 | 259 | pub fn body(mut self, value: V) -> Self 260 | where 261 | V: std::convert::TryInto, 262 | >::Error: std::fmt::Display, 263 | { 264 | self.body = value 265 | .try_into() 266 | .map(From::from) 267 | .map_err(|s| format!("conversion to `UnoBody` for body failed: {}", s)); 268 | self 269 | } 270 | 271 | pub fn body_map(mut self, f: F) -> Self 272 | where 273 | F: std::ops::FnOnce(types::builder::UnoBody) -> types::builder::UnoBody, 274 | { 275 | self.body = self.body.map(f); 276 | self 277 | } 278 | 279 | ///Sends a `GET` request to `/uno` 280 | pub async fn send(self) -> Result, Error<()>> { 281 | let Self { 282 | client, 283 | gateway, 284 | body, 285 | } = self; 286 | let gateway = gateway.map_err(Error::InvalidRequest)?; 287 | let body = body 288 | .and_then(|v| types::UnoBody::try_from(v).map_err(|e| e.to_string())) 289 | .map_err(Error::InvalidRequest)?; 290 | let url = format!("{}/uno", client.baseurl,); 291 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 292 | header_map.append( 293 | ::reqwest::header::HeaderName::from_static("api-version"), 294 | ::reqwest::header::HeaderValue::from_static(super::Client::api_version()), 295 | ); 296 | #[allow(unused_mut)] 297 | let mut request = client 298 | .client 299 | .get(url) 300 | .json(&body) 301 | .query(&progenitor_client::QueryParam::new("gateway", &gateway)) 302 | .headers(header_map) 303 | .build()?; 304 | let info = OperationInfo { 305 | operation_id: "uno", 306 | }; 307 | client.pre(&mut request, &info).await?; 308 | let result = client.exec(request, &info).await; 309 | client.post(&result, &info).await?; 310 | let response = result?; 311 | match response.status().as_u16() { 312 | 200..=299 => Ok(ResponseValue::stream(response)), 313 | _ => Err(Error::UnexpectedResponse(response)), 314 | } 315 | } 316 | } 317 | } 318 | 319 | /// Items consumers will typically use such as the Client. 320 | pub mod prelude { 321 | pub use self::super::Client; 322 | } 323 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/cli_gen_builder_tagged.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | 38 | ///`UnoBody` 39 | /// 40 | ///
JSON schema 41 | /// 42 | /// ```json 43 | ///{ 44 | /// "type": "object", 45 | /// "required": [ 46 | /// "required" 47 | /// ], 48 | /// "properties": { 49 | /// "gateway": { 50 | /// "type": "string" 51 | /// } 52 | /// } 53 | ///} 54 | /// ``` 55 | ///
56 | #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)] 57 | pub struct UnoBody { 58 | #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] 59 | pub gateway: ::std::option::Option<::std::string::String>, 60 | pub required: ::serde_json::Value, 61 | } 62 | 63 | impl ::std::convert::From<&UnoBody> for UnoBody { 64 | fn from(value: &UnoBody) -> Self { 65 | value.clone() 66 | } 67 | } 68 | 69 | impl UnoBody { 70 | pub fn builder() -> builder::UnoBody { 71 | Default::default() 72 | } 73 | } 74 | 75 | /// Types for composing complex structures. 76 | pub mod builder { 77 | #[derive(Clone, Debug)] 78 | pub struct UnoBody { 79 | gateway: ::std::result::Result< 80 | ::std::option::Option<::std::string::String>, 81 | ::std::string::String, 82 | >, 83 | required: ::std::result::Result<::serde_json::Value, ::std::string::String>, 84 | } 85 | 86 | impl ::std::default::Default for UnoBody { 87 | fn default() -> Self { 88 | Self { 89 | gateway: Ok(Default::default()), 90 | required: Err("no value supplied for required".to_string()), 91 | } 92 | } 93 | } 94 | 95 | impl UnoBody { 96 | pub fn gateway(mut self, value: T) -> Self 97 | where 98 | T: ::std::convert::TryInto<::std::option::Option<::std::string::String>>, 99 | T::Error: ::std::fmt::Display, 100 | { 101 | self.gateway = value 102 | .try_into() 103 | .map_err(|e| format!("error converting supplied value for gateway: {}", e)); 104 | self 105 | } 106 | pub fn required(mut self, value: T) -> Self 107 | where 108 | T: ::std::convert::TryInto<::serde_json::Value>, 109 | T::Error: ::std::fmt::Display, 110 | { 111 | self.required = value 112 | .try_into() 113 | .map_err(|e| format!("error converting supplied value for required: {}", e)); 114 | self 115 | } 116 | } 117 | 118 | impl ::std::convert::TryFrom for super::UnoBody { 119 | type Error = super::error::ConversionError; 120 | fn try_from( 121 | value: UnoBody, 122 | ) -> ::std::result::Result { 123 | Ok(Self { 124 | gateway: value.gateway?, 125 | required: value.required?, 126 | }) 127 | } 128 | } 129 | 130 | impl ::std::convert::From for UnoBody { 131 | fn from(value: super::UnoBody) -> Self { 132 | Self { 133 | gateway: Ok(value.gateway), 134 | required: Ok(value.required), 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | #[derive(Clone, Debug)] 142 | ///Client for CLI gen test 143 | /// 144 | ///Test case to exercise CLI generation 145 | /// 146 | ///Version: 9000 147 | pub struct Client { 148 | pub(crate) baseurl: String, 149 | pub(crate) client: reqwest::Client, 150 | } 151 | 152 | impl Client { 153 | /// Create a new client. 154 | /// 155 | /// `baseurl` is the base URL provided to the internal 156 | /// `reqwest::Client`, and should include a scheme and hostname, 157 | /// as well as port and a path stem if applicable. 158 | pub fn new(baseurl: &str) -> Self { 159 | #[cfg(not(target_arch = "wasm32"))] 160 | let client = { 161 | let dur = std::time::Duration::from_secs(15); 162 | reqwest::ClientBuilder::new() 163 | .connect_timeout(dur) 164 | .timeout(dur) 165 | }; 166 | #[cfg(target_arch = "wasm32")] 167 | let client = reqwest::ClientBuilder::new(); 168 | Self::new_with_client(baseurl, client.build().unwrap()) 169 | } 170 | 171 | /// Construct a new client with an existing `reqwest::Client`, 172 | /// allowing more control over its configuration. 173 | /// 174 | /// `baseurl` is the base URL provided to the internal 175 | /// `reqwest::Client`, and should include a scheme and hostname, 176 | /// as well as port and a path stem if applicable. 177 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 178 | Self { 179 | baseurl: baseurl.to_string(), 180 | client, 181 | } 182 | } 183 | } 184 | 185 | impl ClientInfo<()> for Client { 186 | fn api_version() -> &'static str { 187 | "9000" 188 | } 189 | 190 | fn baseurl(&self) -> &str { 191 | self.baseurl.as_str() 192 | } 193 | 194 | fn client(&self) -> &reqwest::Client { 195 | &self.client 196 | } 197 | 198 | fn inner(&self) -> &() { 199 | &() 200 | } 201 | } 202 | 203 | impl ClientHooks<()> for &Client {} 204 | impl Client { 205 | ///Sends a `GET` request to `/uno` 206 | /// 207 | ///```ignore 208 | /// let response = client.uno() 209 | /// .gateway(gateway) 210 | /// .body(body) 211 | /// .send() 212 | /// .await; 213 | /// ``` 214 | pub fn uno(&self) -> builder::Uno { 215 | builder::Uno::new(self) 216 | } 217 | } 218 | 219 | /// Types for composing operation parameters. 220 | #[allow(clippy::all)] 221 | pub mod builder { 222 | use super::types; 223 | #[allow(unused_imports)] 224 | use super::{ 225 | encode_path, ByteStream, ClientHooks, ClientInfo, Error, OperationInfo, RequestBuilderExt, 226 | ResponseValue, 227 | }; 228 | ///Builder for [`Client::uno`] 229 | /// 230 | ///[`Client::uno`]: super::Client::uno 231 | #[derive(Debug, Clone)] 232 | pub struct Uno<'a> { 233 | client: &'a super::Client, 234 | gateway: Result<::std::string::String, String>, 235 | body: Result, 236 | } 237 | 238 | impl<'a> Uno<'a> { 239 | pub fn new(client: &'a super::Client) -> Self { 240 | Self { 241 | client: client, 242 | gateway: Err("gateway was not initialized".to_string()), 243 | body: Ok(::std::default::Default::default()), 244 | } 245 | } 246 | 247 | pub fn gateway(mut self, value: V) -> Self 248 | where 249 | V: std::convert::TryInto<::std::string::String>, 250 | { 251 | self.gateway = value.try_into().map_err(|_| { 252 | "conversion to `:: std :: string :: String` for gateway failed".to_string() 253 | }); 254 | self 255 | } 256 | 257 | pub fn body(mut self, value: V) -> Self 258 | where 259 | V: std::convert::TryInto, 260 | >::Error: std::fmt::Display, 261 | { 262 | self.body = value 263 | .try_into() 264 | .map(From::from) 265 | .map_err(|s| format!("conversion to `UnoBody` for body failed: {}", s)); 266 | self 267 | } 268 | 269 | pub fn body_map(mut self, f: F) -> Self 270 | where 271 | F: std::ops::FnOnce(types::builder::UnoBody) -> types::builder::UnoBody, 272 | { 273 | self.body = self.body.map(f); 274 | self 275 | } 276 | 277 | ///Sends a `GET` request to `/uno` 278 | pub async fn send(self) -> Result, Error<()>> { 279 | let Self { 280 | client, 281 | gateway, 282 | body, 283 | } = self; 284 | let gateway = gateway.map_err(Error::InvalidRequest)?; 285 | let body = body 286 | .and_then(|v| types::UnoBody::try_from(v).map_err(|e| e.to_string())) 287 | .map_err(Error::InvalidRequest)?; 288 | let url = format!("{}/uno", client.baseurl,); 289 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 290 | header_map.append( 291 | ::reqwest::header::HeaderName::from_static("api-version"), 292 | ::reqwest::header::HeaderValue::from_static(super::Client::api_version()), 293 | ); 294 | #[allow(unused_mut)] 295 | let mut request = client 296 | .client 297 | .get(url) 298 | .json(&body) 299 | .query(&progenitor_client::QueryParam::new("gateway", &gateway)) 300 | .headers(header_map) 301 | .build()?; 302 | let info = OperationInfo { 303 | operation_id: "uno", 304 | }; 305 | client.pre(&mut request, &info).await?; 306 | let result = client.exec(request, &info).await; 307 | client.post(&result, &info).await?; 308 | let response = result?; 309 | match response.status().as_u16() { 310 | 200..=299 => Ok(ResponseValue::stream(response)), 311 | _ => Err(Error::UnexpectedResponse(response)), 312 | } 313 | } 314 | } 315 | } 316 | 317 | /// Items consumers will typically use such as the Client and 318 | /// extension traits. 319 | pub mod prelude { 320 | #[allow(unused_imports)] 321 | pub use super::Client; 322 | } 323 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/cli_gen_cli.rs: -------------------------------------------------------------------------------- 1 | use crate::cli_gen_builder::*; 2 | pub struct Cli { 3 | client: Client, 4 | config: T, 5 | } 6 | 7 | impl Cli { 8 | pub fn new(client: Client, config: T) -> Self { 9 | Self { client, config } 10 | } 11 | 12 | pub fn get_command(cmd: CliCommand) -> ::clap::Command { 13 | match cmd { 14 | CliCommand::Uno => Self::cli_uno(), 15 | } 16 | } 17 | 18 | pub fn cli_uno() -> ::clap::Command { 19 | ::clap::Command::new("") 20 | .arg( 21 | ::clap::Arg::new("gateway") 22 | .long("gateway") 23 | .value_parser(::clap::value_parser!(::std::string::String)) 24 | .required(true), 25 | ) 26 | .arg( 27 | ::clap::Arg::new("json-body") 28 | .long("json-body") 29 | .value_name("JSON-FILE") 30 | .required(true) 31 | .value_parser(::clap::value_parser!(std::path::PathBuf)) 32 | .help("Path to a file that contains the full json body."), 33 | ) 34 | .arg( 35 | ::clap::Arg::new("json-body-template") 36 | .long("json-body-template") 37 | .action(::clap::ArgAction::SetTrue) 38 | .help("XXX"), 39 | ) 40 | } 41 | 42 | pub async fn execute( 43 | &self, 44 | cmd: CliCommand, 45 | matches: &::clap::ArgMatches, 46 | ) -> anyhow::Result<()> { 47 | match cmd { 48 | CliCommand::Uno => self.execute_uno(matches).await, 49 | } 50 | } 51 | 52 | pub async fn execute_uno(&self, matches: &::clap::ArgMatches) -> anyhow::Result<()> { 53 | let mut request = self.client.uno(); 54 | if let Some(value) = matches.get_one::<::std::string::String>("gateway") { 55 | request = request.gateway(value.clone()); 56 | } 57 | 58 | if let Some(value) = matches.get_one::("json-body") { 59 | let body_txt = std::fs::read_to_string(value).unwrap(); 60 | let body_value = serde_json::from_str::(&body_txt).unwrap(); 61 | request = request.body(body_value); 62 | } 63 | 64 | self.config.execute_uno(matches, &mut request)?; 65 | let result = request.send().await; 66 | match result { 67 | Ok(r) => { 68 | todo!() 69 | } 70 | Err(r) => { 71 | self.config.error(&r); 72 | Err(anyhow::Error::new(r)) 73 | } 74 | } 75 | } 76 | } 77 | 78 | pub trait CliConfig { 79 | fn success_item(&self, value: &ResponseValue) 80 | where 81 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 82 | fn success_no_item(&self, value: &ResponseValue<()>); 83 | fn error(&self, value: &Error) 84 | where 85 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 86 | fn list_start(&self) 87 | where 88 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 89 | fn list_item(&self, value: &T) 90 | where 91 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 92 | fn list_end_success(&self) 93 | where 94 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 95 | fn list_end_error(&self, value: &Error) 96 | where 97 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 98 | fn execute_uno( 99 | &self, 100 | matches: &::clap::ArgMatches, 101 | request: &mut builder::Uno, 102 | ) -> anyhow::Result<()> { 103 | Ok(()) 104 | } 105 | } 106 | 107 | #[derive(Copy, Clone, Debug)] 108 | pub enum CliCommand { 109 | Uno, 110 | } 111 | 112 | impl CliCommand { 113 | pub fn iter() -> impl Iterator { 114 | vec![CliCommand::Uno].into_iter() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/cli_gen_httpmock.rs: -------------------------------------------------------------------------------- 1 | pub mod operations { 2 | #![doc = r" [`When`](::httpmock::When) and [`Then`](::httpmock::Then)"] 3 | #![doc = r" wrappers for each operation. Each can be converted to"] 4 | #![doc = r" its inner type with a call to `into_inner()`. This can"] 5 | #![doc = r" be used to explicitly deviate from permitted values."] 6 | use crate::cli_gen_builder::*; 7 | pub struct UnoWhen(::httpmock::When); 8 | impl UnoWhen { 9 | pub fn new(inner: ::httpmock::When) -> Self { 10 | Self( 11 | inner 12 | .method(::httpmock::Method::GET) 13 | .path_matches(regex::Regex::new("^/uno$").unwrap()), 14 | ) 15 | } 16 | 17 | pub fn into_inner(self) -> ::httpmock::When { 18 | self.0 19 | } 20 | 21 | pub fn gateway(self, value: &str) -> Self { 22 | Self(self.0.query_param("gateway", value.to_string())) 23 | } 24 | 25 | pub fn body(self, value: &types::UnoBody) -> Self { 26 | Self(self.0.json_body_obj(value)) 27 | } 28 | } 29 | 30 | pub struct UnoThen(::httpmock::Then); 31 | impl UnoThen { 32 | pub fn new(inner: ::httpmock::Then) -> Self { 33 | Self(inner) 34 | } 35 | 36 | pub fn into_inner(self) -> ::httpmock::Then { 37 | self.0 38 | } 39 | 40 | pub fn success(self, status: u16, value: ::serde_json::Value) -> Self { 41 | assert_eq!(status / 100u16, 2u16); 42 | Self( 43 | self.0 44 | .status(status) 45 | .header("content-type", "application/json") 46 | .json_body(value), 47 | ) 48 | } 49 | } 50 | } 51 | 52 | #[doc = r" An extension trait for [`MockServer`](::httpmock::MockServer) that"] 53 | #[doc = r" adds a method for each operation. These are the equivalent of"] 54 | #[doc = r" type-checked [`mock()`](::httpmock::MockServer::mock) calls."] 55 | pub trait MockServerExt { 56 | fn uno(&self, config_fn: F) -> ::httpmock::Mock 57 | where 58 | F: FnOnce(operations::UnoWhen, operations::UnoThen); 59 | } 60 | 61 | impl MockServerExt for ::httpmock::MockServer { 62 | fn uno(&self, config_fn: F) -> ::httpmock::Mock 63 | where 64 | F: FnOnce(operations::UnoWhen, operations::UnoThen), 65 | { 66 | self.mock(|when, then| { 67 | config_fn( 68 | operations::UnoWhen::new(when), 69 | operations::UnoThen::new(then), 70 | ) 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/cli_gen_positional.rs: -------------------------------------------------------------------------------- 1 | #![allow(elided_named_lifetimes)] 2 | #[allow(unused_imports)] 3 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 4 | #[allow(unused_imports)] 5 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 6 | /// Types used as operation parameters and responses. 7 | #[allow(clippy::all)] 8 | pub mod types { 9 | /// Error types. 10 | pub mod error { 11 | /// Error from a `TryFrom` or `FromStr` implementation. 12 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 13 | impl ::std::error::Error for ConversionError {} 14 | impl ::std::fmt::Display for ConversionError { 15 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 16 | ::std::fmt::Display::fmt(&self.0, f) 17 | } 18 | } 19 | 20 | impl ::std::fmt::Debug for ConversionError { 21 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 22 | ::std::fmt::Debug::fmt(&self.0, f) 23 | } 24 | } 25 | 26 | impl From<&'static str> for ConversionError { 27 | fn from(value: &'static str) -> Self { 28 | Self(value.into()) 29 | } 30 | } 31 | 32 | impl From for ConversionError { 33 | fn from(value: String) -> Self { 34 | Self(value.into()) 35 | } 36 | } 37 | } 38 | 39 | ///`UnoBody` 40 | /// 41 | ///
JSON schema 42 | /// 43 | /// ```json 44 | ///{ 45 | /// "type": "object", 46 | /// "required": [ 47 | /// "required" 48 | /// ], 49 | /// "properties": { 50 | /// "gateway": { 51 | /// "type": "string" 52 | /// } 53 | /// } 54 | ///} 55 | /// ``` 56 | ///
57 | #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)] 58 | pub struct UnoBody { 59 | #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] 60 | pub gateway: ::std::option::Option<::std::string::String>, 61 | pub required: ::serde_json::Value, 62 | } 63 | 64 | impl ::std::convert::From<&UnoBody> for UnoBody { 65 | fn from(value: &UnoBody) -> Self { 66 | value.clone() 67 | } 68 | } 69 | } 70 | 71 | #[derive(Clone, Debug)] 72 | ///Client for CLI gen test 73 | /// 74 | ///Test case to exercise CLI generation 75 | /// 76 | ///Version: 9000 77 | pub struct Client { 78 | pub(crate) baseurl: String, 79 | pub(crate) client: reqwest::Client, 80 | } 81 | 82 | impl Client { 83 | /// Create a new client. 84 | /// 85 | /// `baseurl` is the base URL provided to the internal 86 | /// `reqwest::Client`, and should include a scheme and hostname, 87 | /// as well as port and a path stem if applicable. 88 | pub fn new(baseurl: &str) -> Self { 89 | #[cfg(not(target_arch = "wasm32"))] 90 | let client = { 91 | let dur = std::time::Duration::from_secs(15); 92 | reqwest::ClientBuilder::new() 93 | .connect_timeout(dur) 94 | .timeout(dur) 95 | }; 96 | #[cfg(target_arch = "wasm32")] 97 | let client = reqwest::ClientBuilder::new(); 98 | Self::new_with_client(baseurl, client.build().unwrap()) 99 | } 100 | 101 | /// Construct a new client with an existing `reqwest::Client`, 102 | /// allowing more control over its configuration. 103 | /// 104 | /// `baseurl` is the base URL provided to the internal 105 | /// `reqwest::Client`, and should include a scheme and hostname, 106 | /// as well as port and a path stem if applicable. 107 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 108 | Self { 109 | baseurl: baseurl.to_string(), 110 | client, 111 | } 112 | } 113 | } 114 | 115 | impl ClientInfo<()> for Client { 116 | fn api_version() -> &'static str { 117 | "9000" 118 | } 119 | 120 | fn baseurl(&self) -> &str { 121 | self.baseurl.as_str() 122 | } 123 | 124 | fn client(&self) -> &reqwest::Client { 125 | &self.client 126 | } 127 | 128 | fn inner(&self) -> &() { 129 | &() 130 | } 131 | } 132 | 133 | impl ClientHooks<()> for &Client {} 134 | #[allow(clippy::all)] 135 | #[allow(elided_named_lifetimes)] 136 | impl Client { 137 | ///Sends a `GET` request to `/uno` 138 | pub async fn uno<'a>( 139 | &'a self, 140 | gateway: &'a str, 141 | body: &'a types::UnoBody, 142 | ) -> Result, Error<()>> { 143 | let url = format!("{}/uno", self.baseurl,); 144 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 145 | header_map.append( 146 | ::reqwest::header::HeaderName::from_static("api-version"), 147 | ::reqwest::header::HeaderValue::from_static(Self::api_version()), 148 | ); 149 | #[allow(unused_mut)] 150 | let mut request = self 151 | .client 152 | .get(url) 153 | .json(&body) 154 | .query(&progenitor_client::QueryParam::new("gateway", &gateway)) 155 | .headers(header_map) 156 | .build()?; 157 | let info = OperationInfo { 158 | operation_id: "uno", 159 | }; 160 | self.pre(&mut request, &info).await?; 161 | let result = self.exec(request, &info).await; 162 | self.post(&result, &info).await?; 163 | let response = result?; 164 | match response.status().as_u16() { 165 | 200..=299 => Ok(ResponseValue::stream(response)), 166 | _ => Err(Error::UnexpectedResponse(response)), 167 | } 168 | } 169 | } 170 | 171 | /// Items consumers will typically use such as the Client. 172 | pub mod prelude { 173 | #[allow(unused_imports)] 174 | pub use super::Client; 175 | } 176 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod buildomat_builder; 2 | pub mod buildomat_builder_tagged; 3 | pub mod buildomat_cli; 4 | //pub mod buildomat_httpmock; 5 | pub mod buildomat_positional; 6 | pub mod keeper_builder; 7 | pub mod keeper_builder_tagged; 8 | pub mod keeper_cli; 9 | pub mod keeper_httpmock; 10 | pub mod keeper_positional; 11 | pub mod nexus_builder; 12 | pub mod nexus_builder_tagged; 13 | pub mod nexus_cli; 14 | pub mod nexus_httpmock; 15 | pub mod nexus_positional; 16 | pub mod param_collision_builder; 17 | pub mod param_collision_builder_tagged; 18 | pub mod param_collision_cli; 19 | pub mod param_collision_httpmock; 20 | pub mod param_collision_positional; 21 | pub mod param_overrides_builder; 22 | pub mod param_overrides_builder_tagged; 23 | pub mod param_overrides_cli; 24 | pub mod param_overrides_httpmock; 25 | pub mod param_overrides_positional; 26 | pub mod propolis_server_builder; 27 | pub mod propolis_server_builder_tagged; 28 | pub mod propolis_server_cli; 29 | //pub mod propolis_server_httpmock; 30 | pub mod propolis_server_positional; 31 | pub mod test_default_params_builder; 32 | pub mod test_default_params_positional; 33 | pub mod test_freeform_response; 34 | pub mod test_renamed_parameters; 35 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_collision_builder.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | ///Client for Parameter name collision test 41 | /// 42 | ///Minimal API for testing collision between parameter names and generated code 43 | /// 44 | ///Version: v1 45 | pub struct Client { 46 | pub(crate) baseurl: String, 47 | pub(crate) client: reqwest::Client, 48 | } 49 | 50 | impl Client { 51 | /// Create a new client. 52 | /// 53 | /// `baseurl` is the base URL provided to the internal 54 | /// `reqwest::Client`, and should include a scheme and hostname, 55 | /// as well as port and a path stem if applicable. 56 | pub fn new(baseurl: &str) -> Self { 57 | #[cfg(not(target_arch = "wasm32"))] 58 | let client = { 59 | let dur = std::time::Duration::from_secs(15); 60 | reqwest::ClientBuilder::new() 61 | .connect_timeout(dur) 62 | .timeout(dur) 63 | }; 64 | #[cfg(target_arch = "wasm32")] 65 | let client = reqwest::ClientBuilder::new(); 66 | Self::new_with_client(baseurl, client.build().unwrap()) 67 | } 68 | 69 | /// Construct a new client with an existing `reqwest::Client`, 70 | /// allowing more control over its configuration. 71 | /// 72 | /// `baseurl` is the base URL provided to the internal 73 | /// `reqwest::Client`, and should include a scheme and hostname, 74 | /// as well as port and a path stem if applicable. 75 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 76 | Self { 77 | baseurl: baseurl.to_string(), 78 | client, 79 | } 80 | } 81 | } 82 | 83 | impl ClientInfo<()> for Client { 84 | fn api_version() -> &'static str { 85 | "v1" 86 | } 87 | 88 | fn baseurl(&self) -> &str { 89 | self.baseurl.as_str() 90 | } 91 | 92 | fn client(&self) -> &reqwest::Client { 93 | &self.client 94 | } 95 | 96 | fn inner(&self) -> &() { 97 | &() 98 | } 99 | } 100 | 101 | impl ClientHooks<()> for &Client {} 102 | impl Client { 103 | ///Gets a key 104 | /// 105 | ///Sends a `GET` request to `/key/{query}` 106 | /// 107 | ///Arguments: 108 | /// - `query`: Parameter name that was previously colliding 109 | /// - `client`: Parameter name that was previously colliding 110 | /// - `request`: Parameter name that was previously colliding 111 | /// - `response`: Parameter name that was previously colliding 112 | /// - `result`: Parameter name that was previously colliding 113 | /// - `url`: Parameter name that was previously colliding 114 | ///```ignore 115 | /// let response = client.key_get() 116 | /// .query(query) 117 | /// .client(client) 118 | /// .request(request) 119 | /// .response(response) 120 | /// .result(result) 121 | /// .url(url) 122 | /// .send() 123 | /// .await; 124 | /// ``` 125 | pub fn key_get(&self) -> builder::KeyGet { 126 | builder::KeyGet::new(self) 127 | } 128 | } 129 | 130 | /// Types for composing operation parameters. 131 | #[allow(clippy::all)] 132 | pub mod builder { 133 | use super::types; 134 | #[allow(unused_imports)] 135 | use super::{ 136 | encode_path, ByteStream, ClientHooks, ClientInfo, Error, OperationInfo, RequestBuilderExt, 137 | ResponseValue, 138 | }; 139 | ///Builder for [`Client::key_get`] 140 | /// 141 | ///[`Client::key_get`]: super::Client::key_get 142 | #[derive(Debug, Clone)] 143 | pub struct KeyGet<'a> { 144 | _client: &'a super::Client, 145 | query: Result, 146 | client: Result, 147 | request: Result, 148 | response: Result, 149 | result: Result, 150 | url: Result, 151 | } 152 | 153 | impl<'a> KeyGet<'a> { 154 | pub fn new(client: &'a super::Client) -> Self { 155 | Self { 156 | _client: client, 157 | query: Err("query was not initialized".to_string()), 158 | client: Err("client was not initialized".to_string()), 159 | request: Err("request was not initialized".to_string()), 160 | response: Err("response was not initialized".to_string()), 161 | result: Err("result was not initialized".to_string()), 162 | url: Err("url was not initialized".to_string()), 163 | } 164 | } 165 | 166 | pub fn query(mut self, value: V) -> Self 167 | where 168 | V: std::convert::TryInto, 169 | { 170 | self.query = value 171 | .try_into() 172 | .map_err(|_| "conversion to `bool` for query failed".to_string()); 173 | self 174 | } 175 | 176 | pub fn client(mut self, value: V) -> Self 177 | where 178 | V: std::convert::TryInto, 179 | { 180 | self.client = value 181 | .try_into() 182 | .map_err(|_| "conversion to `bool` for client failed".to_string()); 183 | self 184 | } 185 | 186 | pub fn request(mut self, value: V) -> Self 187 | where 188 | V: std::convert::TryInto, 189 | { 190 | self.request = value 191 | .try_into() 192 | .map_err(|_| "conversion to `bool` for request failed".to_string()); 193 | self 194 | } 195 | 196 | pub fn response(mut self, value: V) -> Self 197 | where 198 | V: std::convert::TryInto, 199 | { 200 | self.response = value 201 | .try_into() 202 | .map_err(|_| "conversion to `bool` for response failed".to_string()); 203 | self 204 | } 205 | 206 | pub fn result(mut self, value: V) -> Self 207 | where 208 | V: std::convert::TryInto, 209 | { 210 | self.result = value 211 | .try_into() 212 | .map_err(|_| "conversion to `bool` for result failed".to_string()); 213 | self 214 | } 215 | 216 | pub fn url(mut self, value: V) -> Self 217 | where 218 | V: std::convert::TryInto, 219 | { 220 | self.url = value 221 | .try_into() 222 | .map_err(|_| "conversion to `bool` for url failed".to_string()); 223 | self 224 | } 225 | 226 | ///Sends a `GET` request to `/key/{query}` 227 | pub async fn send(self) -> Result, Error<()>> { 228 | let Self { 229 | _client, 230 | query, 231 | client, 232 | request, 233 | response, 234 | result, 235 | url, 236 | } = self; 237 | let query = query.map_err(Error::InvalidRequest)?; 238 | let client = client.map_err(Error::InvalidRequest)?; 239 | let request = request.map_err(Error::InvalidRequest)?; 240 | let response = response.map_err(Error::InvalidRequest)?; 241 | let result = result.map_err(Error::InvalidRequest)?; 242 | let url = url.map_err(Error::InvalidRequest)?; 243 | let _url = format!( 244 | "{}/key/{}", 245 | _client.baseurl, 246 | encode_path(&query.to_string()), 247 | ); 248 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 249 | header_map.append( 250 | ::reqwest::header::HeaderName::from_static("api-version"), 251 | ::reqwest::header::HeaderValue::from_static(super::Client::api_version()), 252 | ); 253 | #[allow(unused_mut)] 254 | let mut _request = _client 255 | .client 256 | .get(_url) 257 | .query(&progenitor_client::QueryParam::new("client", &client)) 258 | .query(&progenitor_client::QueryParam::new("request", &request)) 259 | .query(&progenitor_client::QueryParam::new("response", &response)) 260 | .query(&progenitor_client::QueryParam::new("result", &result)) 261 | .query(&progenitor_client::QueryParam::new("url", &url)) 262 | .headers(header_map) 263 | .build()?; 264 | let info = OperationInfo { 265 | operation_id: "key_get", 266 | }; 267 | _client.pre(&mut _request, &info).await?; 268 | let _result = _client.exec(_request, &info).await; 269 | _client.post(&_result, &info).await?; 270 | let _response = _result?; 271 | match _response.status().as_u16() { 272 | 200u16 => Ok(ResponseValue::empty(_response)), 273 | _ => Err(Error::UnexpectedResponse(_response)), 274 | } 275 | } 276 | } 277 | } 278 | 279 | /// Items consumers will typically use such as the Client. 280 | pub mod prelude { 281 | pub use self::super::Client; 282 | } 283 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_collision_builder_tagged.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | ///Client for Parameter name collision test 41 | /// 42 | ///Minimal API for testing collision between parameter names and generated code 43 | /// 44 | ///Version: v1 45 | pub struct Client { 46 | pub(crate) baseurl: String, 47 | pub(crate) client: reqwest::Client, 48 | } 49 | 50 | impl Client { 51 | /// Create a new client. 52 | /// 53 | /// `baseurl` is the base URL provided to the internal 54 | /// `reqwest::Client`, and should include a scheme and hostname, 55 | /// as well as port and a path stem if applicable. 56 | pub fn new(baseurl: &str) -> Self { 57 | #[cfg(not(target_arch = "wasm32"))] 58 | let client = { 59 | let dur = std::time::Duration::from_secs(15); 60 | reqwest::ClientBuilder::new() 61 | .connect_timeout(dur) 62 | .timeout(dur) 63 | }; 64 | #[cfg(target_arch = "wasm32")] 65 | let client = reqwest::ClientBuilder::new(); 66 | Self::new_with_client(baseurl, client.build().unwrap()) 67 | } 68 | 69 | /// Construct a new client with an existing `reqwest::Client`, 70 | /// allowing more control over its configuration. 71 | /// 72 | /// `baseurl` is the base URL provided to the internal 73 | /// `reqwest::Client`, and should include a scheme and hostname, 74 | /// as well as port and a path stem if applicable. 75 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 76 | Self { 77 | baseurl: baseurl.to_string(), 78 | client, 79 | } 80 | } 81 | } 82 | 83 | impl ClientInfo<()> for Client { 84 | fn api_version() -> &'static str { 85 | "v1" 86 | } 87 | 88 | fn baseurl(&self) -> &str { 89 | self.baseurl.as_str() 90 | } 91 | 92 | fn client(&self) -> &reqwest::Client { 93 | &self.client 94 | } 95 | 96 | fn inner(&self) -> &() { 97 | &() 98 | } 99 | } 100 | 101 | impl ClientHooks<()> for &Client {} 102 | impl Client { 103 | ///Gets a key 104 | /// 105 | ///Sends a `GET` request to `/key/{query}` 106 | /// 107 | ///Arguments: 108 | /// - `query`: Parameter name that was previously colliding 109 | /// - `client`: Parameter name that was previously colliding 110 | /// - `request`: Parameter name that was previously colliding 111 | /// - `response`: Parameter name that was previously colliding 112 | /// - `result`: Parameter name that was previously colliding 113 | /// - `url`: Parameter name that was previously colliding 114 | ///```ignore 115 | /// let response = client.key_get() 116 | /// .query(query) 117 | /// .client(client) 118 | /// .request(request) 119 | /// .response(response) 120 | /// .result(result) 121 | /// .url(url) 122 | /// .send() 123 | /// .await; 124 | /// ``` 125 | pub fn key_get(&self) -> builder::KeyGet { 126 | builder::KeyGet::new(self) 127 | } 128 | } 129 | 130 | /// Types for composing operation parameters. 131 | #[allow(clippy::all)] 132 | pub mod builder { 133 | use super::types; 134 | #[allow(unused_imports)] 135 | use super::{ 136 | encode_path, ByteStream, ClientHooks, ClientInfo, Error, OperationInfo, RequestBuilderExt, 137 | ResponseValue, 138 | }; 139 | ///Builder for [`Client::key_get`] 140 | /// 141 | ///[`Client::key_get`]: super::Client::key_get 142 | #[derive(Debug, Clone)] 143 | pub struct KeyGet<'a> { 144 | _client: &'a super::Client, 145 | query: Result, 146 | client: Result, 147 | request: Result, 148 | response: Result, 149 | result: Result, 150 | url: Result, 151 | } 152 | 153 | impl<'a> KeyGet<'a> { 154 | pub fn new(client: &'a super::Client) -> Self { 155 | Self { 156 | _client: client, 157 | query: Err("query was not initialized".to_string()), 158 | client: Err("client was not initialized".to_string()), 159 | request: Err("request was not initialized".to_string()), 160 | response: Err("response was not initialized".to_string()), 161 | result: Err("result was not initialized".to_string()), 162 | url: Err("url was not initialized".to_string()), 163 | } 164 | } 165 | 166 | pub fn query(mut self, value: V) -> Self 167 | where 168 | V: std::convert::TryInto, 169 | { 170 | self.query = value 171 | .try_into() 172 | .map_err(|_| "conversion to `bool` for query failed".to_string()); 173 | self 174 | } 175 | 176 | pub fn client(mut self, value: V) -> Self 177 | where 178 | V: std::convert::TryInto, 179 | { 180 | self.client = value 181 | .try_into() 182 | .map_err(|_| "conversion to `bool` for client failed".to_string()); 183 | self 184 | } 185 | 186 | pub fn request(mut self, value: V) -> Self 187 | where 188 | V: std::convert::TryInto, 189 | { 190 | self.request = value 191 | .try_into() 192 | .map_err(|_| "conversion to `bool` for request failed".to_string()); 193 | self 194 | } 195 | 196 | pub fn response(mut self, value: V) -> Self 197 | where 198 | V: std::convert::TryInto, 199 | { 200 | self.response = value 201 | .try_into() 202 | .map_err(|_| "conversion to `bool` for response failed".to_string()); 203 | self 204 | } 205 | 206 | pub fn result(mut self, value: V) -> Self 207 | where 208 | V: std::convert::TryInto, 209 | { 210 | self.result = value 211 | .try_into() 212 | .map_err(|_| "conversion to `bool` for result failed".to_string()); 213 | self 214 | } 215 | 216 | pub fn url(mut self, value: V) -> Self 217 | where 218 | V: std::convert::TryInto, 219 | { 220 | self.url = value 221 | .try_into() 222 | .map_err(|_| "conversion to `bool` for url failed".to_string()); 223 | self 224 | } 225 | 226 | ///Sends a `GET` request to `/key/{query}` 227 | pub async fn send(self) -> Result, Error<()>> { 228 | let Self { 229 | _client, 230 | query, 231 | client, 232 | request, 233 | response, 234 | result, 235 | url, 236 | } = self; 237 | let query = query.map_err(Error::InvalidRequest)?; 238 | let client = client.map_err(Error::InvalidRequest)?; 239 | let request = request.map_err(Error::InvalidRequest)?; 240 | let response = response.map_err(Error::InvalidRequest)?; 241 | let result = result.map_err(Error::InvalidRequest)?; 242 | let url = url.map_err(Error::InvalidRequest)?; 243 | let _url = format!( 244 | "{}/key/{}", 245 | _client.baseurl, 246 | encode_path(&query.to_string()), 247 | ); 248 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 249 | header_map.append( 250 | ::reqwest::header::HeaderName::from_static("api-version"), 251 | ::reqwest::header::HeaderValue::from_static(super::Client::api_version()), 252 | ); 253 | #[allow(unused_mut)] 254 | let mut _request = _client 255 | .client 256 | .get(_url) 257 | .query(&progenitor_client::QueryParam::new("client", &client)) 258 | .query(&progenitor_client::QueryParam::new("request", &request)) 259 | .query(&progenitor_client::QueryParam::new("response", &response)) 260 | .query(&progenitor_client::QueryParam::new("result", &result)) 261 | .query(&progenitor_client::QueryParam::new("url", &url)) 262 | .headers(header_map) 263 | .build()?; 264 | let info = OperationInfo { 265 | operation_id: "key_get", 266 | }; 267 | _client.pre(&mut _request, &info).await?; 268 | let _result = _client.exec(_request, &info).await; 269 | _client.post(&_result, &info).await?; 270 | let _response = _result?; 271 | match _response.status().as_u16() { 272 | 200u16 => Ok(ResponseValue::empty(_response)), 273 | _ => Err(Error::UnexpectedResponse(_response)), 274 | } 275 | } 276 | } 277 | } 278 | 279 | /// Items consumers will typically use such as the Client and 280 | /// extension traits. 281 | pub mod prelude { 282 | #[allow(unused_imports)] 283 | pub use super::Client; 284 | } 285 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_collision_cli.rs: -------------------------------------------------------------------------------- 1 | use crate::param_collision_builder::*; 2 | pub struct Cli { 3 | client: Client, 4 | config: T, 5 | } 6 | 7 | impl Cli { 8 | pub fn new(client: Client, config: T) -> Self { 9 | Self { client, config } 10 | } 11 | 12 | pub fn get_command(cmd: CliCommand) -> ::clap::Command { 13 | match cmd { 14 | CliCommand::KeyGet => Self::cli_key_get(), 15 | } 16 | } 17 | 18 | pub fn cli_key_get() -> ::clap::Command { 19 | ::clap::Command::new("") 20 | .arg( 21 | ::clap::Arg::new("client") 22 | .long("client") 23 | .value_parser(::clap::value_parser!(bool)) 24 | .required(true) 25 | .help("Parameter name that was previously colliding"), 26 | ) 27 | .arg( 28 | ::clap::Arg::new("query") 29 | .long("query") 30 | .value_parser(::clap::value_parser!(bool)) 31 | .required(true) 32 | .help("Parameter name that was previously colliding"), 33 | ) 34 | .arg( 35 | ::clap::Arg::new("request") 36 | .long("request") 37 | .value_parser(::clap::value_parser!(bool)) 38 | .required(true) 39 | .help("Parameter name that was previously colliding"), 40 | ) 41 | .arg( 42 | ::clap::Arg::new("response") 43 | .long("response") 44 | .value_parser(::clap::value_parser!(bool)) 45 | .required(true) 46 | .help("Parameter name that was previously colliding"), 47 | ) 48 | .arg( 49 | ::clap::Arg::new("result") 50 | .long("result") 51 | .value_parser(::clap::value_parser!(bool)) 52 | .required(true) 53 | .help("Parameter name that was previously colliding"), 54 | ) 55 | .arg( 56 | ::clap::Arg::new("url") 57 | .long("url") 58 | .value_parser(::clap::value_parser!(bool)) 59 | .required(true) 60 | .help("Parameter name that was previously colliding"), 61 | ) 62 | .long_about("Gets a key") 63 | } 64 | 65 | pub async fn execute( 66 | &self, 67 | cmd: CliCommand, 68 | matches: &::clap::ArgMatches, 69 | ) -> anyhow::Result<()> { 70 | match cmd { 71 | CliCommand::KeyGet => self.execute_key_get(matches).await, 72 | } 73 | } 74 | 75 | pub async fn execute_key_get(&self, matches: &::clap::ArgMatches) -> anyhow::Result<()> { 76 | let mut request = self.client.key_get(); 77 | if let Some(value) = matches.get_one::("client") { 78 | request = request.client(value.clone()); 79 | } 80 | 81 | if let Some(value) = matches.get_one::("query") { 82 | request = request.query(value.clone()); 83 | } 84 | 85 | if let Some(value) = matches.get_one::("request") { 86 | request = request.request(value.clone()); 87 | } 88 | 89 | if let Some(value) = matches.get_one::("response") { 90 | request = request.response(value.clone()); 91 | } 92 | 93 | if let Some(value) = matches.get_one::("result") { 94 | request = request.result(value.clone()); 95 | } 96 | 97 | if let Some(value) = matches.get_one::("url") { 98 | request = request.url(value.clone()); 99 | } 100 | 101 | self.config.execute_key_get(matches, &mut request)?; 102 | let result = request.send().await; 103 | match result { 104 | Ok(r) => { 105 | self.config.success_no_item(&r); 106 | Ok(()) 107 | } 108 | Err(r) => { 109 | self.config.error(&r); 110 | Err(anyhow::Error::new(r)) 111 | } 112 | } 113 | } 114 | } 115 | 116 | pub trait CliConfig { 117 | fn success_item(&self, value: &ResponseValue) 118 | where 119 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 120 | fn success_no_item(&self, value: &ResponseValue<()>); 121 | fn error(&self, value: &Error) 122 | where 123 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 124 | fn list_start(&self) 125 | where 126 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 127 | fn list_item(&self, value: &T) 128 | where 129 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 130 | fn list_end_success(&self) 131 | where 132 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 133 | fn list_end_error(&self, value: &Error) 134 | where 135 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 136 | fn execute_key_get( 137 | &self, 138 | matches: &::clap::ArgMatches, 139 | request: &mut builder::KeyGet, 140 | ) -> anyhow::Result<()> { 141 | Ok(()) 142 | } 143 | } 144 | 145 | #[derive(Copy, Clone, Debug)] 146 | pub enum CliCommand { 147 | KeyGet, 148 | } 149 | 150 | impl CliCommand { 151 | pub fn iter() -> impl Iterator { 152 | vec![CliCommand::KeyGet].into_iter() 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_collision_httpmock.rs: -------------------------------------------------------------------------------- 1 | pub mod operations { 2 | #![doc = r" [`When`](::httpmock::When) and [`Then`](::httpmock::Then)"] 3 | #![doc = r" wrappers for each operation. Each can be converted to"] 4 | #![doc = r" its inner type with a call to `into_inner()`. This can"] 5 | #![doc = r" be used to explicitly deviate from permitted values."] 6 | use crate::param_collision_builder::*; 7 | pub struct KeyGetWhen(::httpmock::When); 8 | impl KeyGetWhen { 9 | pub fn new(inner: ::httpmock::When) -> Self { 10 | Self( 11 | inner 12 | .method(::httpmock::Method::GET) 13 | .path_matches(regex::Regex::new("^/key/[^/]*$").unwrap()), 14 | ) 15 | } 16 | 17 | pub fn into_inner(self) -> ::httpmock::When { 18 | self.0 19 | } 20 | 21 | pub fn query(self, value: bool) -> Self { 22 | let re = regex::Regex::new(&format!("^/key/{}$", value.to_string())).unwrap(); 23 | Self(self.0.path_matches(re)) 24 | } 25 | 26 | pub fn client(self, value: bool) -> Self { 27 | Self(self.0.query_param("client", value.to_string())) 28 | } 29 | 30 | pub fn request(self, value: bool) -> Self { 31 | Self(self.0.query_param("request", value.to_string())) 32 | } 33 | 34 | pub fn response(self, value: bool) -> Self { 35 | Self(self.0.query_param("response", value.to_string())) 36 | } 37 | 38 | pub fn result(self, value: bool) -> Self { 39 | Self(self.0.query_param("result", value.to_string())) 40 | } 41 | 42 | pub fn url(self, value: bool) -> Self { 43 | Self(self.0.query_param("url", value.to_string())) 44 | } 45 | } 46 | 47 | pub struct KeyGetThen(::httpmock::Then); 48 | impl KeyGetThen { 49 | pub fn new(inner: ::httpmock::Then) -> Self { 50 | Self(inner) 51 | } 52 | 53 | pub fn into_inner(self) -> ::httpmock::Then { 54 | self.0 55 | } 56 | 57 | pub fn ok(self) -> Self { 58 | Self(self.0.status(200u16)) 59 | } 60 | } 61 | } 62 | 63 | #[doc = r" An extension trait for [`MockServer`](::httpmock::MockServer) that"] 64 | #[doc = r" adds a method for each operation. These are the equivalent of"] 65 | #[doc = r" type-checked [`mock()`](::httpmock::MockServer::mock) calls."] 66 | pub trait MockServerExt { 67 | fn key_get(&self, config_fn: F) -> ::httpmock::Mock 68 | where 69 | F: FnOnce(operations::KeyGetWhen, operations::KeyGetThen); 70 | } 71 | 72 | impl MockServerExt for ::httpmock::MockServer { 73 | fn key_get(&self, config_fn: F) -> ::httpmock::Mock 74 | where 75 | F: FnOnce(operations::KeyGetWhen, operations::KeyGetThen), 76 | { 77 | self.mock(|when, then| { 78 | config_fn( 79 | operations::KeyGetWhen::new(when), 80 | operations::KeyGetThen::new(then), 81 | ) 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_collision_positional.rs: -------------------------------------------------------------------------------- 1 | #![allow(elided_named_lifetimes)] 2 | #[allow(unused_imports)] 3 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 4 | #[allow(unused_imports)] 5 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 6 | /// Types used as operation parameters and responses. 7 | #[allow(clippy::all)] 8 | pub mod types { 9 | /// Error types. 10 | pub mod error { 11 | /// Error from a `TryFrom` or `FromStr` implementation. 12 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 13 | impl ::std::error::Error for ConversionError {} 14 | impl ::std::fmt::Display for ConversionError { 15 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 16 | ::std::fmt::Display::fmt(&self.0, f) 17 | } 18 | } 19 | 20 | impl ::std::fmt::Debug for ConversionError { 21 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 22 | ::std::fmt::Debug::fmt(&self.0, f) 23 | } 24 | } 25 | 26 | impl From<&'static str> for ConversionError { 27 | fn from(value: &'static str) -> Self { 28 | Self(value.into()) 29 | } 30 | } 31 | 32 | impl From for ConversionError { 33 | fn from(value: String) -> Self { 34 | Self(value.into()) 35 | } 36 | } 37 | } 38 | } 39 | 40 | #[derive(Clone, Debug)] 41 | ///Client for Parameter name collision test 42 | /// 43 | ///Minimal API for testing collision between parameter names and generated code 44 | /// 45 | ///Version: v1 46 | pub struct Client { 47 | pub(crate) baseurl: String, 48 | pub(crate) client: reqwest::Client, 49 | } 50 | 51 | impl Client { 52 | /// Create a new client. 53 | /// 54 | /// `baseurl` is the base URL provided to the internal 55 | /// `reqwest::Client`, and should include a scheme and hostname, 56 | /// as well as port and a path stem if applicable. 57 | pub fn new(baseurl: &str) -> Self { 58 | #[cfg(not(target_arch = "wasm32"))] 59 | let client = { 60 | let dur = std::time::Duration::from_secs(15); 61 | reqwest::ClientBuilder::new() 62 | .connect_timeout(dur) 63 | .timeout(dur) 64 | }; 65 | #[cfg(target_arch = "wasm32")] 66 | let client = reqwest::ClientBuilder::new(); 67 | Self::new_with_client(baseurl, client.build().unwrap()) 68 | } 69 | 70 | /// Construct a new client with an existing `reqwest::Client`, 71 | /// allowing more control over its configuration. 72 | /// 73 | /// `baseurl` is the base URL provided to the internal 74 | /// `reqwest::Client`, and should include a scheme and hostname, 75 | /// as well as port and a path stem if applicable. 76 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 77 | Self { 78 | baseurl: baseurl.to_string(), 79 | client, 80 | } 81 | } 82 | } 83 | 84 | impl ClientInfo<()> for Client { 85 | fn api_version() -> &'static str { 86 | "v1" 87 | } 88 | 89 | fn baseurl(&self) -> &str { 90 | self.baseurl.as_str() 91 | } 92 | 93 | fn client(&self) -> &reqwest::Client { 94 | &self.client 95 | } 96 | 97 | fn inner(&self) -> &() { 98 | &() 99 | } 100 | } 101 | 102 | impl ClientHooks<()> for &Client {} 103 | #[allow(clippy::all)] 104 | #[allow(elided_named_lifetimes)] 105 | impl Client { 106 | ///Gets a key 107 | /// 108 | ///Sends a `GET` request to `/key/{query}` 109 | /// 110 | ///Arguments: 111 | /// - `query`: Parameter name that was previously colliding 112 | /// - `client`: Parameter name that was previously colliding 113 | /// - `request`: Parameter name that was previously colliding 114 | /// - `response`: Parameter name that was previously colliding 115 | /// - `result`: Parameter name that was previously colliding 116 | /// - `url`: Parameter name that was previously colliding 117 | pub async fn key_get<'a>( 118 | &'a self, 119 | query: bool, 120 | client: bool, 121 | request: bool, 122 | response: bool, 123 | result: bool, 124 | url: bool, 125 | ) -> Result, Error<()>> { 126 | let _url = format!("{}/key/{}", self.baseurl, encode_path(&query.to_string()),); 127 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 128 | header_map.append( 129 | ::reqwest::header::HeaderName::from_static("api-version"), 130 | ::reqwest::header::HeaderValue::from_static(Self::api_version()), 131 | ); 132 | #[allow(unused_mut)] 133 | let mut _request = self 134 | .client 135 | .get(_url) 136 | .query(&progenitor_client::QueryParam::new("client", &client)) 137 | .query(&progenitor_client::QueryParam::new("request", &request)) 138 | .query(&progenitor_client::QueryParam::new("response", &response)) 139 | .query(&progenitor_client::QueryParam::new("result", &result)) 140 | .query(&progenitor_client::QueryParam::new("url", &url)) 141 | .headers(header_map) 142 | .build()?; 143 | let info = OperationInfo { 144 | operation_id: "key_get", 145 | }; 146 | self.pre(&mut _request, &info).await?; 147 | let _result = self.exec(_request, &info).await; 148 | self.post(&_result, &info).await?; 149 | let _response = _result?; 150 | match _response.status().as_u16() { 151 | 200u16 => Ok(ResponseValue::empty(_response)), 152 | _ => Err(Error::UnexpectedResponse(_response)), 153 | } 154 | } 155 | } 156 | 157 | /// Items consumers will typically use such as the Client. 158 | pub mod prelude { 159 | #[allow(unused_imports)] 160 | pub use super::Client; 161 | } 162 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_overrides_builder.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | ///Client for Parameter override test 41 | /// 42 | ///Minimal API for testing parameter overrides 43 | /// 44 | ///Version: v1 45 | pub struct Client { 46 | pub(crate) baseurl: String, 47 | pub(crate) client: reqwest::Client, 48 | } 49 | 50 | impl Client { 51 | /// Create a new client. 52 | /// 53 | /// `baseurl` is the base URL provided to the internal 54 | /// `reqwest::Client`, and should include a scheme and hostname, 55 | /// as well as port and a path stem if applicable. 56 | pub fn new(baseurl: &str) -> Self { 57 | #[cfg(not(target_arch = "wasm32"))] 58 | let client = { 59 | let dur = std::time::Duration::from_secs(15); 60 | reqwest::ClientBuilder::new() 61 | .connect_timeout(dur) 62 | .timeout(dur) 63 | }; 64 | #[cfg(target_arch = "wasm32")] 65 | let client = reqwest::ClientBuilder::new(); 66 | Self::new_with_client(baseurl, client.build().unwrap()) 67 | } 68 | 69 | /// Construct a new client with an existing `reqwest::Client`, 70 | /// allowing more control over its configuration. 71 | /// 72 | /// `baseurl` is the base URL provided to the internal 73 | /// `reqwest::Client`, and should include a scheme and hostname, 74 | /// as well as port and a path stem if applicable. 75 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 76 | Self { 77 | baseurl: baseurl.to_string(), 78 | client, 79 | } 80 | } 81 | } 82 | 83 | impl ClientInfo<()> for Client { 84 | fn api_version() -> &'static str { 85 | "v1" 86 | } 87 | 88 | fn baseurl(&self) -> &str { 89 | self.baseurl.as_str() 90 | } 91 | 92 | fn client(&self) -> &reqwest::Client { 93 | &self.client 94 | } 95 | 96 | fn inner(&self) -> &() { 97 | &() 98 | } 99 | } 100 | 101 | impl ClientHooks<()> for &Client {} 102 | impl Client { 103 | ///Gets a key 104 | /// 105 | ///Sends a `GET` request to `/key` 106 | /// 107 | ///Arguments: 108 | /// - `key`: The same key parameter that overlaps with the path level 109 | /// parameter 110 | /// - `unique_key`: A key parameter that will not be overridden by the path 111 | /// spec 112 | ///```ignore 113 | /// let response = client.key_get() 114 | /// .key(key) 115 | /// .unique_key(unique_key) 116 | /// .send() 117 | /// .await; 118 | /// ``` 119 | pub fn key_get(&self) -> builder::KeyGet { 120 | builder::KeyGet::new(self) 121 | } 122 | } 123 | 124 | /// Types for composing operation parameters. 125 | #[allow(clippy::all)] 126 | pub mod builder { 127 | use super::types; 128 | #[allow(unused_imports)] 129 | use super::{ 130 | encode_path, ByteStream, ClientHooks, ClientInfo, Error, OperationInfo, RequestBuilderExt, 131 | ResponseValue, 132 | }; 133 | ///Builder for [`Client::key_get`] 134 | /// 135 | ///[`Client::key_get`]: super::Client::key_get 136 | #[derive(Debug, Clone)] 137 | pub struct KeyGet<'a> { 138 | client: &'a super::Client, 139 | key: Result, String>, 140 | unique_key: Result, String>, 141 | } 142 | 143 | impl<'a> KeyGet<'a> { 144 | pub fn new(client: &'a super::Client) -> Self { 145 | Self { 146 | client: client, 147 | key: Ok(None), 148 | unique_key: Ok(None), 149 | } 150 | } 151 | 152 | pub fn key(mut self, value: V) -> Self 153 | where 154 | V: std::convert::TryInto, 155 | { 156 | self.key = value 157 | .try_into() 158 | .map(Some) 159 | .map_err(|_| "conversion to `bool` for key failed".to_string()); 160 | self 161 | } 162 | 163 | pub fn unique_key(mut self, value: V) -> Self 164 | where 165 | V: std::convert::TryInto<::std::string::String>, 166 | { 167 | self.unique_key = value.try_into().map(Some).map_err(|_| { 168 | "conversion to `:: std :: string :: String` for unique_key failed".to_string() 169 | }); 170 | self 171 | } 172 | 173 | ///Sends a `GET` request to `/key` 174 | pub async fn send(self) -> Result, Error<()>> { 175 | let Self { 176 | client, 177 | key, 178 | unique_key, 179 | } = self; 180 | let key = key.map_err(Error::InvalidRequest)?; 181 | let unique_key = unique_key.map_err(Error::InvalidRequest)?; 182 | let url = format!("{}/key", client.baseurl,); 183 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 184 | header_map.append( 185 | ::reqwest::header::HeaderName::from_static("api-version"), 186 | ::reqwest::header::HeaderValue::from_static(super::Client::api_version()), 187 | ); 188 | #[allow(unused_mut)] 189 | let mut request = client 190 | .client 191 | .get(url) 192 | .query(&progenitor_client::QueryParam::new("key", &key)) 193 | .query(&progenitor_client::QueryParam::new( 194 | "uniqueKey", 195 | &unique_key, 196 | )) 197 | .headers(header_map) 198 | .build()?; 199 | let info = OperationInfo { 200 | operation_id: "key_get", 201 | }; 202 | client.pre(&mut request, &info).await?; 203 | let result = client.exec(request, &info).await; 204 | client.post(&result, &info).await?; 205 | let response = result?; 206 | match response.status().as_u16() { 207 | 200u16 => Ok(ResponseValue::empty(response)), 208 | _ => Err(Error::UnexpectedResponse(response)), 209 | } 210 | } 211 | } 212 | } 213 | 214 | /// Items consumers will typically use such as the Client. 215 | pub mod prelude { 216 | pub use self::super::Client; 217 | } 218 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_overrides_builder_tagged.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | ///Client for Parameter override test 41 | /// 42 | ///Minimal API for testing parameter overrides 43 | /// 44 | ///Version: v1 45 | pub struct Client { 46 | pub(crate) baseurl: String, 47 | pub(crate) client: reqwest::Client, 48 | } 49 | 50 | impl Client { 51 | /// Create a new client. 52 | /// 53 | /// `baseurl` is the base URL provided to the internal 54 | /// `reqwest::Client`, and should include a scheme and hostname, 55 | /// as well as port and a path stem if applicable. 56 | pub fn new(baseurl: &str) -> Self { 57 | #[cfg(not(target_arch = "wasm32"))] 58 | let client = { 59 | let dur = std::time::Duration::from_secs(15); 60 | reqwest::ClientBuilder::new() 61 | .connect_timeout(dur) 62 | .timeout(dur) 63 | }; 64 | #[cfg(target_arch = "wasm32")] 65 | let client = reqwest::ClientBuilder::new(); 66 | Self::new_with_client(baseurl, client.build().unwrap()) 67 | } 68 | 69 | /// Construct a new client with an existing `reqwest::Client`, 70 | /// allowing more control over its configuration. 71 | /// 72 | /// `baseurl` is the base URL provided to the internal 73 | /// `reqwest::Client`, and should include a scheme and hostname, 74 | /// as well as port and a path stem if applicable. 75 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 76 | Self { 77 | baseurl: baseurl.to_string(), 78 | client, 79 | } 80 | } 81 | } 82 | 83 | impl ClientInfo<()> for Client { 84 | fn api_version() -> &'static str { 85 | "v1" 86 | } 87 | 88 | fn baseurl(&self) -> &str { 89 | self.baseurl.as_str() 90 | } 91 | 92 | fn client(&self) -> &reqwest::Client { 93 | &self.client 94 | } 95 | 96 | fn inner(&self) -> &() { 97 | &() 98 | } 99 | } 100 | 101 | impl ClientHooks<()> for &Client {} 102 | impl Client { 103 | ///Gets a key 104 | /// 105 | ///Sends a `GET` request to `/key` 106 | /// 107 | ///Arguments: 108 | /// - `key`: The same key parameter that overlaps with the path level 109 | /// parameter 110 | /// - `unique_key`: A key parameter that will not be overridden by the path 111 | /// spec 112 | ///```ignore 113 | /// let response = client.key_get() 114 | /// .key(key) 115 | /// .unique_key(unique_key) 116 | /// .send() 117 | /// .await; 118 | /// ``` 119 | pub fn key_get(&self) -> builder::KeyGet { 120 | builder::KeyGet::new(self) 121 | } 122 | } 123 | 124 | /// Types for composing operation parameters. 125 | #[allow(clippy::all)] 126 | pub mod builder { 127 | use super::types; 128 | #[allow(unused_imports)] 129 | use super::{ 130 | encode_path, ByteStream, ClientHooks, ClientInfo, Error, OperationInfo, RequestBuilderExt, 131 | ResponseValue, 132 | }; 133 | ///Builder for [`Client::key_get`] 134 | /// 135 | ///[`Client::key_get`]: super::Client::key_get 136 | #[derive(Debug, Clone)] 137 | pub struct KeyGet<'a> { 138 | client: &'a super::Client, 139 | key: Result, String>, 140 | unique_key: Result, String>, 141 | } 142 | 143 | impl<'a> KeyGet<'a> { 144 | pub fn new(client: &'a super::Client) -> Self { 145 | Self { 146 | client: client, 147 | key: Ok(None), 148 | unique_key: Ok(None), 149 | } 150 | } 151 | 152 | pub fn key(mut self, value: V) -> Self 153 | where 154 | V: std::convert::TryInto, 155 | { 156 | self.key = value 157 | .try_into() 158 | .map(Some) 159 | .map_err(|_| "conversion to `bool` for key failed".to_string()); 160 | self 161 | } 162 | 163 | pub fn unique_key(mut self, value: V) -> Self 164 | where 165 | V: std::convert::TryInto<::std::string::String>, 166 | { 167 | self.unique_key = value.try_into().map(Some).map_err(|_| { 168 | "conversion to `:: std :: string :: String` for unique_key failed".to_string() 169 | }); 170 | self 171 | } 172 | 173 | ///Sends a `GET` request to `/key` 174 | pub async fn send(self) -> Result, Error<()>> { 175 | let Self { 176 | client, 177 | key, 178 | unique_key, 179 | } = self; 180 | let key = key.map_err(Error::InvalidRequest)?; 181 | let unique_key = unique_key.map_err(Error::InvalidRequest)?; 182 | let url = format!("{}/key", client.baseurl,); 183 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 184 | header_map.append( 185 | ::reqwest::header::HeaderName::from_static("api-version"), 186 | ::reqwest::header::HeaderValue::from_static(super::Client::api_version()), 187 | ); 188 | #[allow(unused_mut)] 189 | let mut request = client 190 | .client 191 | .get(url) 192 | .query(&progenitor_client::QueryParam::new("key", &key)) 193 | .query(&progenitor_client::QueryParam::new( 194 | "uniqueKey", 195 | &unique_key, 196 | )) 197 | .headers(header_map) 198 | .build()?; 199 | let info = OperationInfo { 200 | operation_id: "key_get", 201 | }; 202 | client.pre(&mut request, &info).await?; 203 | let result = client.exec(request, &info).await; 204 | client.post(&result, &info).await?; 205 | let response = result?; 206 | match response.status().as_u16() { 207 | 200u16 => Ok(ResponseValue::empty(response)), 208 | _ => Err(Error::UnexpectedResponse(response)), 209 | } 210 | } 211 | } 212 | } 213 | 214 | /// Items consumers will typically use such as the Client and 215 | /// extension traits. 216 | pub mod prelude { 217 | #[allow(unused_imports)] 218 | pub use super::Client; 219 | } 220 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_overrides_cli.rs: -------------------------------------------------------------------------------- 1 | use crate::param_overrides_builder::*; 2 | pub struct Cli { 3 | client: Client, 4 | config: T, 5 | } 6 | 7 | impl Cli { 8 | pub fn new(client: Client, config: T) -> Self { 9 | Self { client, config } 10 | } 11 | 12 | pub fn get_command(cmd: CliCommand) -> ::clap::Command { 13 | match cmd { 14 | CliCommand::KeyGet => Self::cli_key_get(), 15 | } 16 | } 17 | 18 | pub fn cli_key_get() -> ::clap::Command { 19 | ::clap::Command::new("") 20 | .arg( 21 | ::clap::Arg::new("key") 22 | .long("key") 23 | .value_parser(::clap::value_parser!(bool)) 24 | .required(false) 25 | .help("The same key parameter that overlaps with the path level parameter"), 26 | ) 27 | .arg( 28 | ::clap::Arg::new("unique-key") 29 | .long("unique-key") 30 | .value_parser(::clap::value_parser!(::std::string::String)) 31 | .required(false) 32 | .help("A key parameter that will not be overridden by the path spec"), 33 | ) 34 | .long_about("Gets a key") 35 | } 36 | 37 | pub async fn execute( 38 | &self, 39 | cmd: CliCommand, 40 | matches: &::clap::ArgMatches, 41 | ) -> anyhow::Result<()> { 42 | match cmd { 43 | CliCommand::KeyGet => self.execute_key_get(matches).await, 44 | } 45 | } 46 | 47 | pub async fn execute_key_get(&self, matches: &::clap::ArgMatches) -> anyhow::Result<()> { 48 | let mut request = self.client.key_get(); 49 | if let Some(value) = matches.get_one::("key") { 50 | request = request.key(value.clone()); 51 | } 52 | 53 | if let Some(value) = matches.get_one::<::std::string::String>("unique-key") { 54 | request = request.unique_key(value.clone()); 55 | } 56 | 57 | self.config.execute_key_get(matches, &mut request)?; 58 | let result = request.send().await; 59 | match result { 60 | Ok(r) => { 61 | self.config.success_no_item(&r); 62 | Ok(()) 63 | } 64 | Err(r) => { 65 | self.config.error(&r); 66 | Err(anyhow::Error::new(r)) 67 | } 68 | } 69 | } 70 | } 71 | 72 | pub trait CliConfig { 73 | fn success_item(&self, value: &ResponseValue) 74 | where 75 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 76 | fn success_no_item(&self, value: &ResponseValue<()>); 77 | fn error(&self, value: &Error) 78 | where 79 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 80 | fn list_start(&self) 81 | where 82 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 83 | fn list_item(&self, value: &T) 84 | where 85 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 86 | fn list_end_success(&self) 87 | where 88 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 89 | fn list_end_error(&self, value: &Error) 90 | where 91 | T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug; 92 | fn execute_key_get( 93 | &self, 94 | matches: &::clap::ArgMatches, 95 | request: &mut builder::KeyGet, 96 | ) -> anyhow::Result<()> { 97 | Ok(()) 98 | } 99 | } 100 | 101 | #[derive(Copy, Clone, Debug)] 102 | pub enum CliCommand { 103 | KeyGet, 104 | } 105 | 106 | impl CliCommand { 107 | pub fn iter() -> impl Iterator { 108 | vec![CliCommand::KeyGet].into_iter() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_overrides_httpmock.rs: -------------------------------------------------------------------------------- 1 | pub mod operations { 2 | #![doc = r" [`When`](::httpmock::When) and [`Then`](::httpmock::Then)"] 3 | #![doc = r" wrappers for each operation. Each can be converted to"] 4 | #![doc = r" its inner type with a call to `into_inner()`. This can"] 5 | #![doc = r" be used to explicitly deviate from permitted values."] 6 | use crate::param_overrides_builder::*; 7 | pub struct KeyGetWhen(::httpmock::When); 8 | impl KeyGetWhen { 9 | pub fn new(inner: ::httpmock::When) -> Self { 10 | Self( 11 | inner 12 | .method(::httpmock::Method::GET) 13 | .path_matches(regex::Regex::new("^/key$").unwrap()), 14 | ) 15 | } 16 | 17 | pub fn into_inner(self) -> ::httpmock::When { 18 | self.0 19 | } 20 | 21 | pub fn key(self, value: T) -> Self 22 | where 23 | T: Into>, 24 | { 25 | if let Some(value) = value.into() { 26 | Self(self.0.query_param("key", value.to_string())) 27 | } else { 28 | Self(self.0.matches(|req| { 29 | req.query_params 30 | .as_ref() 31 | .and_then(|qs| qs.iter().find(|(key, _)| key == "key")) 32 | .is_none() 33 | })) 34 | } 35 | } 36 | 37 | pub fn unique_key<'a, T>(self, value: T) -> Self 38 | where 39 | T: Into>, 40 | { 41 | if let Some(value) = value.into() { 42 | Self(self.0.query_param("uniqueKey", value.to_string())) 43 | } else { 44 | Self(self.0.matches(|req| { 45 | req.query_params 46 | .as_ref() 47 | .and_then(|qs| qs.iter().find(|(key, _)| key == "uniqueKey")) 48 | .is_none() 49 | })) 50 | } 51 | } 52 | } 53 | 54 | pub struct KeyGetThen(::httpmock::Then); 55 | impl KeyGetThen { 56 | pub fn new(inner: ::httpmock::Then) -> Self { 57 | Self(inner) 58 | } 59 | 60 | pub fn into_inner(self) -> ::httpmock::Then { 61 | self.0 62 | } 63 | 64 | pub fn ok(self) -> Self { 65 | Self(self.0.status(200u16)) 66 | } 67 | } 68 | } 69 | 70 | #[doc = r" An extension trait for [`MockServer`](::httpmock::MockServer) that"] 71 | #[doc = r" adds a method for each operation. These are the equivalent of"] 72 | #[doc = r" type-checked [`mock()`](::httpmock::MockServer::mock) calls."] 73 | pub trait MockServerExt { 74 | fn key_get(&self, config_fn: F) -> ::httpmock::Mock 75 | where 76 | F: FnOnce(operations::KeyGetWhen, operations::KeyGetThen); 77 | } 78 | 79 | impl MockServerExt for ::httpmock::MockServer { 80 | fn key_get(&self, config_fn: F) -> ::httpmock::Mock 81 | where 82 | F: FnOnce(operations::KeyGetWhen, operations::KeyGetThen), 83 | { 84 | self.mock(|when, then| { 85 | config_fn( 86 | operations::KeyGetWhen::new(when), 87 | operations::KeyGetThen::new(then), 88 | ) 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/param_overrides_positional.rs: -------------------------------------------------------------------------------- 1 | #![allow(elided_named_lifetimes)] 2 | #[allow(unused_imports)] 3 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 4 | #[allow(unused_imports)] 5 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 6 | /// Types used as operation parameters and responses. 7 | #[allow(clippy::all)] 8 | pub mod types { 9 | /// Error types. 10 | pub mod error { 11 | /// Error from a `TryFrom` or `FromStr` implementation. 12 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 13 | impl ::std::error::Error for ConversionError {} 14 | impl ::std::fmt::Display for ConversionError { 15 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 16 | ::std::fmt::Display::fmt(&self.0, f) 17 | } 18 | } 19 | 20 | impl ::std::fmt::Debug for ConversionError { 21 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 22 | ::std::fmt::Debug::fmt(&self.0, f) 23 | } 24 | } 25 | 26 | impl From<&'static str> for ConversionError { 27 | fn from(value: &'static str) -> Self { 28 | Self(value.into()) 29 | } 30 | } 31 | 32 | impl From for ConversionError { 33 | fn from(value: String) -> Self { 34 | Self(value.into()) 35 | } 36 | } 37 | } 38 | } 39 | 40 | #[derive(Clone, Debug)] 41 | ///Client for Parameter override test 42 | /// 43 | ///Minimal API for testing parameter overrides 44 | /// 45 | ///Version: v1 46 | pub struct Client { 47 | pub(crate) baseurl: String, 48 | pub(crate) client: reqwest::Client, 49 | } 50 | 51 | impl Client { 52 | /// Create a new client. 53 | /// 54 | /// `baseurl` is the base URL provided to the internal 55 | /// `reqwest::Client`, and should include a scheme and hostname, 56 | /// as well as port and a path stem if applicable. 57 | pub fn new(baseurl: &str) -> Self { 58 | #[cfg(not(target_arch = "wasm32"))] 59 | let client = { 60 | let dur = std::time::Duration::from_secs(15); 61 | reqwest::ClientBuilder::new() 62 | .connect_timeout(dur) 63 | .timeout(dur) 64 | }; 65 | #[cfg(target_arch = "wasm32")] 66 | let client = reqwest::ClientBuilder::new(); 67 | Self::new_with_client(baseurl, client.build().unwrap()) 68 | } 69 | 70 | /// Construct a new client with an existing `reqwest::Client`, 71 | /// allowing more control over its configuration. 72 | /// 73 | /// `baseurl` is the base URL provided to the internal 74 | /// `reqwest::Client`, and should include a scheme and hostname, 75 | /// as well as port and a path stem if applicable. 76 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 77 | Self { 78 | baseurl: baseurl.to_string(), 79 | client, 80 | } 81 | } 82 | } 83 | 84 | impl ClientInfo<()> for Client { 85 | fn api_version() -> &'static str { 86 | "v1" 87 | } 88 | 89 | fn baseurl(&self) -> &str { 90 | self.baseurl.as_str() 91 | } 92 | 93 | fn client(&self) -> &reqwest::Client { 94 | &self.client 95 | } 96 | 97 | fn inner(&self) -> &() { 98 | &() 99 | } 100 | } 101 | 102 | impl ClientHooks<()> for &Client {} 103 | #[allow(clippy::all)] 104 | #[allow(elided_named_lifetimes)] 105 | impl Client { 106 | ///Gets a key 107 | /// 108 | ///Sends a `GET` request to `/key` 109 | /// 110 | ///Arguments: 111 | /// - `key`: The same key parameter that overlaps with the path level 112 | /// parameter 113 | /// - `unique_key`: A key parameter that will not be overridden by the path 114 | /// spec 115 | pub async fn key_get<'a>( 116 | &'a self, 117 | key: Option, 118 | unique_key: Option<&'a str>, 119 | ) -> Result, Error<()>> { 120 | let url = format!("{}/key", self.baseurl,); 121 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 122 | header_map.append( 123 | ::reqwest::header::HeaderName::from_static("api-version"), 124 | ::reqwest::header::HeaderValue::from_static(Self::api_version()), 125 | ); 126 | #[allow(unused_mut)] 127 | let mut request = self 128 | .client 129 | .get(url) 130 | .query(&progenitor_client::QueryParam::new("key", &key)) 131 | .query(&progenitor_client::QueryParam::new( 132 | "uniqueKey", 133 | &unique_key, 134 | )) 135 | .headers(header_map) 136 | .build()?; 137 | let info = OperationInfo { 138 | operation_id: "key_get", 139 | }; 140 | self.pre(&mut request, &info).await?; 141 | let result = self.exec(request, &info).await; 142 | self.post(&result, &info).await?; 143 | let response = result?; 144 | match response.status().as_u16() { 145 | 200u16 => Ok(ResponseValue::empty(response)), 146 | _ => Err(Error::UnexpectedResponse(response)), 147 | } 148 | } 149 | } 150 | 151 | /// Items consumers will typically use such as the Client. 152 | pub mod prelude { 153 | #[allow(unused_imports)] 154 | pub use super::Client; 155 | } 156 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/test_default_params_positional.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | 38 | ///`BodyWithDefaults` 39 | /// 40 | ///
JSON schema 41 | /// 42 | /// ```json 43 | ///{ 44 | /// "type": "object", 45 | /// "required": [ 46 | /// "s" 47 | /// ], 48 | /// "properties": { 49 | /// "forty-two": { 50 | /// "default": 42, 51 | /// "type": "integer", 52 | /// "format": "uint32", 53 | /// "minimum": 0.0 54 | /// }, 55 | /// "s": { 56 | /// "type": "string" 57 | /// }, 58 | /// "something": { 59 | /// "default": true, 60 | /// "type": [ 61 | /// "boolean", 62 | /// "null" 63 | /// ] 64 | /// }, 65 | /// "yes": { 66 | /// "default": false, 67 | /// "type": "boolean" 68 | /// } 69 | /// } 70 | ///} 71 | /// ``` 72 | ///
73 | #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)] 74 | pub struct BodyWithDefaults { 75 | #[serde(rename = "forty-two", default = "defaults::default_u64::")] 76 | pub forty_two: u32, 77 | pub s: ::std::string::String, 78 | #[serde(default = "defaults::body_with_defaults_something")] 79 | pub something: ::std::option::Option, 80 | #[serde(default)] 81 | pub yes: bool, 82 | } 83 | 84 | impl ::std::convert::From<&BodyWithDefaults> for BodyWithDefaults { 85 | fn from(value: &BodyWithDefaults) -> Self { 86 | value.clone() 87 | } 88 | } 89 | 90 | ///Error information from a response. 91 | /// 92 | ///
JSON schema 93 | /// 94 | /// ```json 95 | ///{ 96 | /// "description": "Error information from a response.", 97 | /// "type": "object", 98 | /// "required": [ 99 | /// "message", 100 | /// "request_id" 101 | /// ], 102 | /// "properties": { 103 | /// "error_code": { 104 | /// "type": "string" 105 | /// }, 106 | /// "message": { 107 | /// "type": "string" 108 | /// }, 109 | /// "request_id": { 110 | /// "type": "string" 111 | /// } 112 | /// } 113 | ///} 114 | /// ``` 115 | ///
116 | #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)] 117 | pub struct Error { 118 | #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] 119 | pub error_code: ::std::option::Option<::std::string::String>, 120 | pub message: ::std::string::String, 121 | pub request_id: ::std::string::String, 122 | } 123 | 124 | impl ::std::convert::From<&Error> for Error { 125 | fn from(value: &Error) -> Self { 126 | value.clone() 127 | } 128 | } 129 | 130 | /// Generation of default values for serde. 131 | pub mod defaults { 132 | pub(super) fn default_u64() -> T 133 | where 134 | T: ::std::convert::TryFrom, 135 | >::Error: ::std::fmt::Debug, 136 | { 137 | T::try_from(V).unwrap() 138 | } 139 | 140 | pub(super) fn body_with_defaults_something() -> ::std::option::Option { 141 | ::std::option::Option::Some(true) 142 | } 143 | } 144 | } 145 | 146 | #[derive(Clone, Debug)] 147 | ///Client for pagination-demo 148 | /// 149 | ///Version: 9000.0.0 150 | pub struct Client { 151 | pub(crate) baseurl: String, 152 | pub(crate) client: reqwest::Client, 153 | } 154 | 155 | impl Client { 156 | /// Create a new client. 157 | /// 158 | /// `baseurl` is the base URL provided to the internal 159 | /// `reqwest::Client`, and should include a scheme and hostname, 160 | /// as well as port and a path stem if applicable. 161 | pub fn new(baseurl: &str) -> Self { 162 | #[cfg(not(target_arch = "wasm32"))] 163 | let client = { 164 | let dur = std::time::Duration::from_secs(15); 165 | reqwest::ClientBuilder::new() 166 | .connect_timeout(dur) 167 | .timeout(dur) 168 | }; 169 | #[cfg(target_arch = "wasm32")] 170 | let client = reqwest::ClientBuilder::new(); 171 | Self::new_with_client(baseurl, client.build().unwrap()) 172 | } 173 | 174 | /// Construct a new client with an existing `reqwest::Client`, 175 | /// allowing more control over its configuration. 176 | /// 177 | /// `baseurl` is the base URL provided to the internal 178 | /// `reqwest::Client`, and should include a scheme and hostname, 179 | /// as well as port and a path stem if applicable. 180 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 181 | Self { 182 | baseurl: baseurl.to_string(), 183 | client, 184 | } 185 | } 186 | } 187 | 188 | impl ClientInfo<()> for Client { 189 | fn api_version() -> &'static str { 190 | "9000.0.0" 191 | } 192 | 193 | fn baseurl(&self) -> &str { 194 | self.baseurl.as_str() 195 | } 196 | 197 | fn client(&self) -> &reqwest::Client { 198 | &self.client 199 | } 200 | 201 | fn inner(&self) -> &() { 202 | &() 203 | } 204 | } 205 | 206 | impl ClientHooks<()> for &Client {} 207 | #[allow(clippy::all)] 208 | #[allow(elided_named_lifetimes)] 209 | impl Client { 210 | ///Sends a `POST` request to `/` 211 | pub async fn default_params<'a>( 212 | &'a self, 213 | body: &'a types::BodyWithDefaults, 214 | ) -> Result, Error> { 215 | let url = format!("{}/", self.baseurl,); 216 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 217 | header_map.append( 218 | ::reqwest::header::HeaderName::from_static("api-version"), 219 | ::reqwest::header::HeaderValue::from_static(Self::api_version()), 220 | ); 221 | #[allow(unused_mut)] 222 | let mut request = self 223 | .client 224 | .post(url) 225 | .json(&body) 226 | .headers(header_map) 227 | .build()?; 228 | let info = OperationInfo { 229 | operation_id: "default_params", 230 | }; 231 | self.pre(&mut request, &info).await?; 232 | let result = self.exec(request, &info).await; 233 | self.post(&result, &info).await?; 234 | let response = result?; 235 | match response.status().as_u16() { 236 | 200..=299 => Ok(ResponseValue::stream(response)), 237 | _ => Err(Error::ErrorResponse(ResponseValue::stream(response))), 238 | } 239 | } 240 | } 241 | 242 | /// Items consumers will typically use such as the Client. 243 | pub mod prelude { 244 | #[allow(unused_imports)] 245 | pub use super::Client; 246 | } 247 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/test_freeform_response.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | 38 | ///Error information from a response. 39 | /// 40 | ///
JSON schema 41 | /// 42 | /// ```json 43 | ///{ 44 | /// "description": "Error information from a response.", 45 | /// "type": "object", 46 | /// "required": [ 47 | /// "message", 48 | /// "request_id" 49 | /// ], 50 | /// "properties": { 51 | /// "error_code": { 52 | /// "type": "string" 53 | /// }, 54 | /// "message": { 55 | /// "type": "string" 56 | /// }, 57 | /// "request_id": { 58 | /// "type": "string" 59 | /// } 60 | /// } 61 | ///} 62 | /// ``` 63 | ///
64 | #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)] 65 | pub struct Error { 66 | #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] 67 | pub error_code: ::std::option::Option<::std::string::String>, 68 | pub message: ::std::string::String, 69 | pub request_id: ::std::string::String, 70 | } 71 | 72 | impl ::std::convert::From<&Error> for Error { 73 | fn from(value: &Error) -> Self { 74 | value.clone() 75 | } 76 | } 77 | } 78 | 79 | #[derive(Clone, Debug)] 80 | ///Client for pagination-demo 81 | /// 82 | ///Version: 9000.0.0 83 | pub struct Client { 84 | pub(crate) baseurl: String, 85 | pub(crate) client: reqwest::Client, 86 | } 87 | 88 | impl Client { 89 | /// Create a new client. 90 | /// 91 | /// `baseurl` is the base URL provided to the internal 92 | /// `reqwest::Client`, and should include a scheme and hostname, 93 | /// as well as port and a path stem if applicable. 94 | pub fn new(baseurl: &str) -> Self { 95 | #[cfg(not(target_arch = "wasm32"))] 96 | let client = { 97 | let dur = std::time::Duration::from_secs(15); 98 | reqwest::ClientBuilder::new() 99 | .connect_timeout(dur) 100 | .timeout(dur) 101 | }; 102 | #[cfg(target_arch = "wasm32")] 103 | let client = reqwest::ClientBuilder::new(); 104 | Self::new_with_client(baseurl, client.build().unwrap()) 105 | } 106 | 107 | /// Construct a new client with an existing `reqwest::Client`, 108 | /// allowing more control over its configuration. 109 | /// 110 | /// `baseurl` is the base URL provided to the internal 111 | /// `reqwest::Client`, and should include a scheme and hostname, 112 | /// as well as port and a path stem if applicable. 113 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 114 | Self { 115 | baseurl: baseurl.to_string(), 116 | client, 117 | } 118 | } 119 | } 120 | 121 | impl ClientInfo<()> for Client { 122 | fn api_version() -> &'static str { 123 | "9000.0.0" 124 | } 125 | 126 | fn baseurl(&self) -> &str { 127 | self.baseurl.as_str() 128 | } 129 | 130 | fn client(&self) -> &reqwest::Client { 131 | &self.client 132 | } 133 | 134 | fn inner(&self) -> &() { 135 | &() 136 | } 137 | } 138 | 139 | impl ClientHooks<()> for &Client {} 140 | #[allow(clippy::all)] 141 | #[allow(elided_named_lifetimes)] 142 | impl Client { 143 | ///Sends a `GET` request to `/` 144 | pub async fn freeform_response<'a>( 145 | &'a self, 146 | ) -> Result, Error> { 147 | let url = format!("{}/", self.baseurl,); 148 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 149 | header_map.append( 150 | ::reqwest::header::HeaderName::from_static("api-version"), 151 | ::reqwest::header::HeaderValue::from_static(Self::api_version()), 152 | ); 153 | #[allow(unused_mut)] 154 | let mut request = self.client.get(url).headers(header_map).build()?; 155 | let info = OperationInfo { 156 | operation_id: "freeform_response", 157 | }; 158 | self.pre(&mut request, &info).await?; 159 | let result = self.exec(request, &info).await; 160 | self.post(&result, &info).await?; 161 | let response = result?; 162 | match response.status().as_u16() { 163 | 200..=299 => Ok(ResponseValue::stream(response)), 164 | _ => Err(Error::ErrorResponse(ResponseValue::stream(response))), 165 | } 166 | } 167 | } 168 | 169 | /// Items consumers will typically use such as the Client. 170 | pub mod prelude { 171 | #[allow(unused_imports)] 172 | pub use super::Client; 173 | } 174 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/test_renamed_parameters.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | 38 | ///Error information from a response. 39 | /// 40 | ///
JSON schema 41 | /// 42 | /// ```json 43 | ///{ 44 | /// "description": "Error information from a response.", 45 | /// "type": "object", 46 | /// "required": [ 47 | /// "message", 48 | /// "request_id" 49 | /// ], 50 | /// "properties": { 51 | /// "error_code": { 52 | /// "type": "string" 53 | /// }, 54 | /// "message": { 55 | /// "type": "string" 56 | /// }, 57 | /// "request_id": { 58 | /// "type": "string" 59 | /// } 60 | /// } 61 | ///} 62 | /// ``` 63 | ///
64 | #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)] 65 | pub struct Error { 66 | #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] 67 | pub error_code: ::std::option::Option<::std::string::String>, 68 | pub message: ::std::string::String, 69 | pub request_id: ::std::string::String, 70 | } 71 | 72 | impl ::std::convert::From<&Error> for Error { 73 | fn from(value: &Error) -> Self { 74 | value.clone() 75 | } 76 | } 77 | } 78 | 79 | #[derive(Clone, Debug)] 80 | ///Client for pagination-demo 81 | /// 82 | ///Version: 9000.0.0 83 | pub struct Client { 84 | pub(crate) baseurl: String, 85 | pub(crate) client: reqwest::Client, 86 | } 87 | 88 | impl Client { 89 | /// Create a new client. 90 | /// 91 | /// `baseurl` is the base URL provided to the internal 92 | /// `reqwest::Client`, and should include a scheme and hostname, 93 | /// as well as port and a path stem if applicable. 94 | pub fn new(baseurl: &str) -> Self { 95 | #[cfg(not(target_arch = "wasm32"))] 96 | let client = { 97 | let dur = std::time::Duration::from_secs(15); 98 | reqwest::ClientBuilder::new() 99 | .connect_timeout(dur) 100 | .timeout(dur) 101 | }; 102 | #[cfg(target_arch = "wasm32")] 103 | let client = reqwest::ClientBuilder::new(); 104 | Self::new_with_client(baseurl, client.build().unwrap()) 105 | } 106 | 107 | /// Construct a new client with an existing `reqwest::Client`, 108 | /// allowing more control over its configuration. 109 | /// 110 | /// `baseurl` is the base URL provided to the internal 111 | /// `reqwest::Client`, and should include a scheme and hostname, 112 | /// as well as port and a path stem if applicable. 113 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 114 | Self { 115 | baseurl: baseurl.to_string(), 116 | client, 117 | } 118 | } 119 | } 120 | 121 | impl ClientInfo<()> for Client { 122 | fn api_version() -> &'static str { 123 | "9000.0.0" 124 | } 125 | 126 | fn baseurl(&self) -> &str { 127 | self.baseurl.as_str() 128 | } 129 | 130 | fn client(&self) -> &reqwest::Client { 131 | &self.client 132 | } 133 | 134 | fn inner(&self) -> &() { 135 | &() 136 | } 137 | } 138 | 139 | impl ClientHooks<()> for &Client {} 140 | #[allow(clippy::all)] 141 | #[allow(elided_named_lifetimes)] 142 | impl Client { 143 | ///Sends a `GET` request to `/{ref}/{type}/{trait}` 144 | pub async fn renamed_parameters<'a>( 145 | &'a self, 146 | ref_: &'a str, 147 | type_: &'a str, 148 | trait_: &'a str, 149 | if_: &'a str, 150 | in_: &'a str, 151 | use_: &'a str, 152 | ) -> Result, Error> { 153 | let url = format!( 154 | "{}/{}/{}/{}", 155 | self.baseurl, 156 | encode_path(&ref_.to_string()), 157 | encode_path(&type_.to_string()), 158 | encode_path(&trait_.to_string()), 159 | ); 160 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 161 | header_map.append( 162 | ::reqwest::header::HeaderName::from_static("api-version"), 163 | ::reqwest::header::HeaderValue::from_static(Self::api_version()), 164 | ); 165 | #[allow(unused_mut)] 166 | let mut request = self 167 | .client 168 | .get(url) 169 | .header( 170 | ::reqwest::header::ACCEPT, 171 | ::reqwest::header::HeaderValue::from_static("application/json"), 172 | ) 173 | .query(&progenitor_client::QueryParam::new("if", &if_)) 174 | .query(&progenitor_client::QueryParam::new("in", &in_)) 175 | .query(&progenitor_client::QueryParam::new("use", &use_)) 176 | .headers(header_map) 177 | .build()?; 178 | let info = OperationInfo { 179 | operation_id: "renamed_parameters", 180 | }; 181 | self.pre(&mut request, &info).await?; 182 | let result = self.exec(request, &info).await; 183 | self.post(&result, &info).await?; 184 | let response = result?; 185 | match response.status().as_u16() { 186 | 204u16 => Ok(ResponseValue::empty(response)), 187 | 400u16..=499u16 => Err(Error::ErrorResponse( 188 | ResponseValue::from_response(response).await?, 189 | )), 190 | 500u16..=599u16 => Err(Error::ErrorResponse( 191 | ResponseValue::from_response(response).await?, 192 | )), 193 | _ => Err(Error::UnexpectedResponse(response)), 194 | } 195 | } 196 | } 197 | 198 | /// Items consumers will typically use such as the Client. 199 | pub mod prelude { 200 | #[allow(unused_imports)] 201 | pub use super::Client; 202 | } 203 | -------------------------------------------------------------------------------- /progenitor-impl/tests/output/src/test_stream_pagination_positional.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt}; 3 | #[allow(unused_imports)] 4 | pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue}; 5 | /// Types used as operation parameters and responses. 6 | #[allow(clippy::all)] 7 | pub mod types { 8 | /// Error types. 9 | pub mod error { 10 | /// Error from a `TryFrom` or `FromStr` implementation. 11 | pub struct ConversionError(::std::borrow::Cow<'static, str>); 12 | impl ::std::error::Error for ConversionError {} 13 | impl ::std::fmt::Display for ConversionError { 14 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 15 | ::std::fmt::Display::fmt(&self.0, f) 16 | } 17 | } 18 | 19 | impl ::std::fmt::Debug for ConversionError { 20 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 21 | ::std::fmt::Debug::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | impl From<&'static str> for ConversionError { 26 | fn from(value: &'static str) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl From for ConversionError { 32 | fn from(value: String) -> Self { 33 | Self(value.into()) 34 | } 35 | } 36 | } 37 | 38 | ///Error information from a response. 39 | /// 40 | ///
JSON schema 41 | /// 42 | /// ```json 43 | ///{ 44 | /// "description": "Error information from a response.", 45 | /// "type": "object", 46 | /// "required": [ 47 | /// "message", 48 | /// "request_id" 49 | /// ], 50 | /// "properties": { 51 | /// "error_code": { 52 | /// "type": "string" 53 | /// }, 54 | /// "message": { 55 | /// "type": "string" 56 | /// }, 57 | /// "request_id": { 58 | /// "type": "string" 59 | /// } 60 | /// } 61 | ///} 62 | /// ``` 63 | ///
64 | #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)] 65 | pub struct Error { 66 | #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] 67 | pub error_code: ::std::option::Option<::std::string::String>, 68 | pub message: ::std::string::String, 69 | pub request_id: ::std::string::String, 70 | } 71 | 72 | impl ::std::convert::From<&Error> for Error { 73 | fn from(value: &Error) -> Self { 74 | value.clone() 75 | } 76 | } 77 | 78 | ///A single page of results 79 | /// 80 | ///
JSON schema 81 | /// 82 | /// ```json 83 | ///{ 84 | /// "description": "A single page of results", 85 | /// "type": "object", 86 | /// "required": [ 87 | /// "items" 88 | /// ], 89 | /// "properties": { 90 | /// "items": { 91 | /// "description": "list of items on this page of results", 92 | /// "type": "array", 93 | /// "items": { 94 | /// "type": "integer", 95 | /// "format": "uint32", 96 | /// "minimum": 0.0 97 | /// } 98 | /// }, 99 | /// "next_page": { 100 | /// "description": "token used to fetch the next page of results (if 101 | /// any)", 102 | /// "type": [ 103 | /// "string", 104 | /// "null" 105 | /// ] 106 | /// } 107 | /// } 108 | ///} 109 | /// ``` 110 | ///
111 | #[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)] 112 | pub struct Uint32ResultsPage { 113 | ///list of items on this page of results 114 | pub items: ::std::vec::Vec, 115 | ///token used to fetch the next page of results (if any) 116 | #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] 117 | pub next_page: ::std::option::Option<::std::string::String>, 118 | } 119 | 120 | impl ::std::convert::From<&Uint32ResultsPage> for Uint32ResultsPage { 121 | fn from(value: &Uint32ResultsPage) -> Self { 122 | value.clone() 123 | } 124 | } 125 | } 126 | 127 | #[derive(Clone, Debug)] 128 | ///Client for test_stream_pagination 129 | /// 130 | ///Version: 1.0.0 131 | pub struct Client { 132 | pub(crate) baseurl: String, 133 | pub(crate) client: reqwest::Client, 134 | } 135 | 136 | impl Client { 137 | /// Create a new client. 138 | /// 139 | /// `baseurl` is the base URL provided to the internal 140 | /// `reqwest::Client`, and should include a scheme and hostname, 141 | /// as well as port and a path stem if applicable. 142 | pub fn new(baseurl: &str) -> Self { 143 | #[cfg(not(target_arch = "wasm32"))] 144 | let client = { 145 | let dur = std::time::Duration::from_secs(15); 146 | reqwest::ClientBuilder::new() 147 | .connect_timeout(dur) 148 | .timeout(dur) 149 | }; 150 | #[cfg(target_arch = "wasm32")] 151 | let client = reqwest::ClientBuilder::new(); 152 | Self::new_with_client(baseurl, client.build().unwrap()) 153 | } 154 | 155 | /// Construct a new client with an existing `reqwest::Client`, 156 | /// allowing more control over its configuration. 157 | /// 158 | /// `baseurl` is the base URL provided to the internal 159 | /// `reqwest::Client`, and should include a scheme and hostname, 160 | /// as well as port and a path stem if applicable. 161 | pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { 162 | Self { 163 | baseurl: baseurl.to_string(), 164 | client, 165 | } 166 | } 167 | } 168 | 169 | impl ClientInfo<()> for Client { 170 | fn api_version() -> &'static str { 171 | "1.0.0" 172 | } 173 | 174 | fn baseurl(&self) -> &str { 175 | self.baseurl.as_str() 176 | } 177 | 178 | fn client(&self) -> &reqwest::Client { 179 | &self.client 180 | } 181 | 182 | fn inner(&self) -> &() { 183 | &() 184 | } 185 | } 186 | 187 | impl ClientHooks<()> for &Client {} 188 | #[allow(clippy::all)] 189 | #[allow(elided_named_lifetimes)] 190 | impl Client { 191 | ///Sends a `GET` request to `/` 192 | /// 193 | ///Arguments: 194 | /// - `limit`: Maximum number of items returned by a single call 195 | /// - `page_token`: Token returned by previous call to retrieve the 196 | /// subsequent page 197 | pub async fn paginated_u32s<'a>( 198 | &'a self, 199 | limit: Option<::std::num::NonZeroU32>, 200 | page_token: Option<&'a str>, 201 | ) -> Result, Error> { 202 | let url = format!("{}/", self.baseurl,); 203 | let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); 204 | header_map.append( 205 | ::reqwest::header::HeaderName::from_static("api-version"), 206 | ::reqwest::header::HeaderValue::from_static(Self::api_version()), 207 | ); 208 | #[allow(unused_mut)] 209 | let mut request = self 210 | .client 211 | .get(url) 212 | .header( 213 | ::reqwest::header::ACCEPT, 214 | ::reqwest::header::HeaderValue::from_static("application/json"), 215 | ) 216 | .query(&progenitor_client::QueryParam::new("limit", &limit)) 217 | .query(&progenitor_client::QueryParam::new( 218 | "page_token", 219 | &page_token, 220 | )) 221 | .headers(header_map) 222 | .build()?; 223 | let info = OperationInfo { 224 | operation_id: "paginated_u32s", 225 | }; 226 | self.pre(&mut request, &info).await?; 227 | let result = self.exec(request, &info).await; 228 | self.post(&result, &info).await?; 229 | let response = result?; 230 | match response.status().as_u16() { 231 | 200u16 => ResponseValue::from_response(response).await, 232 | 400u16..=499u16 => Err(Error::ErrorResponse( 233 | ResponseValue::from_response(response).await?, 234 | )), 235 | 500u16..=599u16 => Err(Error::ErrorResponse( 236 | ResponseValue::from_response(response).await?, 237 | )), 238 | _ => Err(Error::UnexpectedResponse(response)), 239 | } 240 | } 241 | 242 | ///Sends repeated `GET` requests to `/` until there are no more results. 243 | /// 244 | ///Arguments: 245 | /// - `limit`: Maximum number of items returned by a single call 246 | pub fn paginated_u32s_stream<'a>( 247 | &'a self, 248 | limit: Option<::std::num::NonZeroU32>, 249 | ) -> impl futures::Stream>> + Unpin + '_ { 250 | use futures::StreamExt; 251 | use futures::TryFutureExt; 252 | use futures::TryStreamExt; 253 | self.paginated_u32s(limit, None) 254 | .map_ok(move |page| { 255 | let page = page.into_inner(); 256 | let first = futures::stream::iter(page.items).map(Ok); 257 | let rest = futures::stream::try_unfold(page.next_page, move |state| async move { 258 | if state.is_none() { 259 | Ok(None) 260 | } else { 261 | self.paginated_u32s(limit, state.as_deref()) 262 | .map_ok(|page| { 263 | let page = page.into_inner(); 264 | Some((futures::stream::iter(page.items).map(Ok), page.next_page)) 265 | }) 266 | .await 267 | } 268 | }) 269 | .try_flatten(); 270 | first.chain(rest) 271 | }) 272 | .try_flatten_stream() 273 | .boxed() 274 | } 275 | } 276 | 277 | /// Items consumers will typically use such as the Client. 278 | pub mod prelude { 279 | #[allow(unused_imports)] 280 | pub use super::Client; 281 | } 282 | -------------------------------------------------------------------------------- /progenitor-impl/tests/test_output.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use std::{ 4 | fs::File, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | use progenitor_impl::{ 9 | space_out_items, GenerationSettings, Generator, InterfaceStyle, TagStyle, TypeImpl, TypePatch, 10 | }; 11 | 12 | use openapiv3::OpenAPI; 13 | use proc_macro2::TokenStream; 14 | 15 | fn load_api

(p: P) -> OpenAPI 16 | where 17 | P: AsRef + std::clone::Clone + std::fmt::Debug, 18 | { 19 | let mut f = File::open(p.clone()).unwrap(); 20 | match serde_json::from_reader(f) { 21 | Ok(json_value) => json_value, 22 | _ => { 23 | f = File::open(p).unwrap(); 24 | serde_yaml::from_reader(f).unwrap() 25 | } 26 | } 27 | } 28 | 29 | fn generate_formatted(generator: &mut Generator, spec: &OpenAPI) -> String { 30 | let content = generator.generate_tokens(&spec).unwrap(); 31 | reformat_code(content) 32 | } 33 | 34 | fn reformat_code(content: TokenStream) -> String { 35 | let rustfmt_config = rustfmt_wrapper::config::Config { 36 | format_strings: Some(true), 37 | normalize_doc_attributes: Some(true), 38 | wrap_comments: Some(true), 39 | ..Default::default() 40 | }; 41 | space_out_items(rustfmt_wrapper::rustfmt_config(rustfmt_config, content).unwrap()).unwrap() 42 | } 43 | 44 | #[track_caller] 45 | fn verify_apis(openapi_file: &str) { 46 | let mut in_path = PathBuf::from("../sample_openapi"); 47 | in_path.push(openapi_file); 48 | let openapi_stem = openapi_file.split('.').next().unwrap().replace('-', "_"); 49 | 50 | let spec = load_api(in_path); 51 | 52 | // Positional generation. 53 | let mut generator = Generator::default(); 54 | let output = format!( 55 | "{}\n{}", 56 | "#![allow(elided_named_lifetimes)]", 57 | generate_formatted(&mut generator, &spec), 58 | ); 59 | expectorate::assert_contents( 60 | format!("tests/output/src/{}_positional.rs", openapi_stem), 61 | &output, 62 | ); 63 | 64 | // Builder generation with derives and patches. 65 | let mut generator = Generator::new( 66 | GenerationSettings::default() 67 | .with_interface(InterfaceStyle::Builder) 68 | .with_tag(TagStyle::Merged) 69 | .with_derive("schemars::JsonSchema") 70 | .with_patch("Name", TypePatch::default().with_derive("Hash")) 71 | .with_conversion( 72 | schemars::schema::SchemaObject { 73 | instance_type: Some(schemars::schema::InstanceType::Integer.into()), 74 | format: Some("int32".to_string()), 75 | ..Default::default() 76 | }, 77 | "usize", 78 | [TypeImpl::Display].into_iter(), 79 | ), 80 | ); 81 | let output = generate_formatted(&mut generator, &spec); 82 | expectorate::assert_contents( 83 | format!("tests/output/src/{}_builder.rs", openapi_stem), 84 | &output, 85 | ); 86 | 87 | // Builder generation with tags. 88 | let mut generator = Generator::new( 89 | GenerationSettings::default() 90 | .with_interface(InterfaceStyle::Builder) 91 | .with_tag(TagStyle::Separate), 92 | ); 93 | let output = generate_formatted(&mut generator, &spec); 94 | expectorate::assert_contents( 95 | format!("tests/output/src/{}_builder_tagged.rs", openapi_stem), 96 | &output, 97 | ); 98 | 99 | // CLI generation. 100 | let tokens = generator 101 | .cli(&spec, &format!("crate::{openapi_stem}_builder")) 102 | .unwrap(); 103 | let output = reformat_code(tokens); 104 | 105 | expectorate::assert_contents(format!("tests/output/src/{}_cli.rs", openapi_stem), &output); 106 | 107 | // httpmock generation. 108 | let code = generator 109 | .httpmock(&spec, &format!("crate::{openapi_stem}_builder")) 110 | .unwrap(); 111 | 112 | // TODO pending #368 113 | let output = rustfmt_wrapper::rustfmt_config( 114 | rustfmt_wrapper::config::Config { 115 | format_strings: Some(true), 116 | ..Default::default() 117 | }, 118 | code, 119 | ) 120 | .unwrap(); 121 | 122 | let output = progenitor_impl::space_out_items(output).unwrap(); 123 | expectorate::assert_contents( 124 | format!("tests/output/src/{}_httpmock.rs", openapi_stem), 125 | &output, 126 | ); 127 | } 128 | 129 | #[test] 130 | fn test_keeper() { 131 | verify_apis("keeper.json"); 132 | } 133 | 134 | #[test] 135 | fn test_buildomat() { 136 | verify_apis("buildomat.json"); 137 | } 138 | 139 | #[test] 140 | fn test_nexus() { 141 | verify_apis("nexus.json"); 142 | } 143 | 144 | #[test] 145 | fn test_propolis_server() { 146 | verify_apis("propolis-server.json"); 147 | } 148 | 149 | #[test] 150 | fn test_param_override() { 151 | verify_apis("param-overrides.json"); 152 | } 153 | 154 | #[test] 155 | fn test_yaml() { 156 | verify_apis("param-overrides.yaml"); 157 | } 158 | 159 | #[test] 160 | fn test_param_collision() { 161 | verify_apis("param-collision.json"); 162 | } 163 | 164 | #[test] 165 | fn test_cli_gen() { 166 | verify_apis("cli-gen.json"); 167 | } 168 | 169 | // TODO this file is full of inconsistencies and incorrectly specified types. 170 | // It's an interesting test to consider whether we try to do our best to 171 | // interpret the intent or just fail. 172 | #[ignore] 173 | #[test] 174 | fn test_github() { 175 | verify_apis("api.github.com.json"); 176 | } 177 | -------------------------------------------------------------------------------- /progenitor-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "progenitor-macro" 3 | version = "0.11.0" 4 | edition = "2021" 5 | license = "MPL-2.0" 6 | description = "An OpenAPI client generator - macros" 7 | repository = "https://github.com/oxidecomputer/progenitor.git" 8 | readme = "../README.md" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | openapiv3 = "2.2.0" 15 | proc-macro2 = "1.0" 16 | progenitor-impl = { version = "0.11.0", path = "../progenitor-impl" } 17 | quote = "1.0" 18 | schemars = "0.8.22" 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0" 21 | serde_yaml = "0.9" 22 | serde_tokenstream = "0.2.0" 23 | syn = { version = "2.0", features = ["full", "extra-traits"] } 24 | -------------------------------------------------------------------------------- /progenitor-macro/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [] 2 | -------------------------------------------------------------------------------- /progenitor-macro/src/token_utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Oxide Computer Company 2 | 3 | use std::collections::HashSet; 4 | 5 | use quote::ToTokens; 6 | use syn::{ 7 | parse::Parse, 8 | punctuated::Punctuated, 9 | token::{Colon, Plus}, 10 | Ident, Path, Token, TraitBoundModifier, 11 | }; 12 | 13 | use progenitor_impl::TypeImpl; 14 | 15 | #[derive(Debug)] 16 | pub struct TypeAndImpls { 17 | pub type_name: Path, 18 | pub colon_token: Option, 19 | pub impls: Punctuated, 20 | } 21 | 22 | impl TypeAndImpls { 23 | pub(crate) fn into_name_and_impls(self) -> (String, impl Iterator) { 24 | // If there are no traits specified, these are assumed to be 25 | // implemented. A user would use the `?FromStr` syntax to remove one of 26 | // these defaults; 27 | const DEFAULT_IMPLS: [TypeImpl; 2] = [TypeImpl::FromStr, TypeImpl::Display]; 28 | 29 | let name = self.type_name.to_token_stream().to_string(); 30 | let mut impls = DEFAULT_IMPLS.into_iter().collect::>(); 31 | self.impls.into_iter().for_each( 32 | |ImplTrait { 33 | modifier, 34 | impl_name, 35 | .. 36 | }| { 37 | // TODO should this be an error rather than silently ignored? 38 | if let Some(impl_name) = impl_name { 39 | match modifier { 40 | syn::TraitBoundModifier::None => impls.insert(impl_name), 41 | syn::TraitBoundModifier::Maybe(_) => impls.remove(&impl_name), 42 | }; 43 | } 44 | }, 45 | ); 46 | (name, impls.into_iter()) 47 | } 48 | } 49 | 50 | impl Parse for TypeAndImpls { 51 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 52 | let type_name: Path = input.parse()?; 53 | let colon_token: Option = input.parse()?; 54 | let mut impls = Punctuated::default(); 55 | 56 | if colon_token.is_some() { 57 | loop { 58 | let value: ImplTrait = input.parse()?; 59 | impls.push_value(value); 60 | if !input.peek(Token![+]) { 61 | break; 62 | } 63 | let punct: Token![+] = input.parse()?; 64 | impls.push_punct(punct); 65 | } 66 | } 67 | 68 | Ok(Self { 69 | type_name, 70 | colon_token, 71 | impls, 72 | }) 73 | } 74 | } 75 | 76 | impl ToTokens for TypeAndImpls { 77 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 78 | self.type_name.to_tokens(tokens); 79 | self.colon_token.to_tokens(tokens); 80 | self.impls.to_tokens(tokens); 81 | } 82 | } 83 | 84 | #[derive(Debug)] 85 | pub struct ImplTrait { 86 | pub modifier: TraitBoundModifier, 87 | pub impl_ident: Ident, 88 | pub impl_name: Option, 89 | } 90 | 91 | impl Parse for ImplTrait { 92 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 93 | let modifier: TraitBoundModifier = input.parse()?; 94 | let impl_ident: Ident = input.parse()?; 95 | let impl_name = impl_ident.to_string().parse().ok(); 96 | 97 | Ok(Self { 98 | modifier, 99 | impl_ident, 100 | impl_name, 101 | }) 102 | } 103 | } 104 | 105 | impl ToTokens for ImplTrait { 106 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 107 | self.modifier.to_tokens(tokens); 108 | self.impl_ident.to_tokens(tokens); 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::TypeAndImpls; 115 | 116 | use quote::{quote, ToTokens}; 117 | 118 | #[test] 119 | fn test_parse_type_and_impls() { 120 | let input = quote! { my_crate::MyType }; 121 | let value = syn::parse2::(input).unwrap(); 122 | assert_eq!( 123 | value.type_name.to_token_stream().to_string(), 124 | "my_crate :: MyType", 125 | ); 126 | assert_eq!(value.impls.len(), 0); 127 | 128 | let input = quote! { my_crate::MyType: ?Display + Hash }; 129 | let value = syn::parse2::(input).unwrap(); 130 | assert_eq!( 131 | value.type_name.to_token_stream().to_string(), 132 | "my_crate :: MyType", 133 | ); 134 | assert_eq!(value.impls.len(), 2); 135 | let mut ii = value.impls.into_iter(); 136 | assert_eq!( 137 | ii.next().unwrap().to_token_stream().to_string(), 138 | "? Display", 139 | ); 140 | assert_eq!(ii.next().unwrap().to_token_stream().to_string(), "Hash",); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /progenitor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "progenitor" 3 | version = "0.11.0" 4 | edition = "2021" 5 | license = "MPL-2.0" 6 | description = "An OpenAPI client generator" 7 | repository = "https://github.com/oxidecomputer/progenitor.git" 8 | readme = "../README.md" 9 | keywords = ["openapi", "openapiv3", "sdk", "generator", "proc_macro"] 10 | categories = ["api-bindings", "compilers"] 11 | 12 | [features] 13 | default = ["macro"] 14 | macro = ["dep:progenitor-client", "dep:progenitor-macro"] 15 | 16 | [dependencies] 17 | progenitor-client = { workspace = true, optional = true } 18 | progenitor-impl = { workspace = true } 19 | progenitor-macro = { workspace = true, optional = true } 20 | 21 | [dev-dependencies] 22 | base64 = { workspace = true } 23 | chrono = { workspace = true } 24 | futures = { workspace = true } 25 | percent-encoding = { workspace = true } 26 | rand = { workspace = true } 27 | regress = { workspace = true } 28 | reqwest = { workspace = true } 29 | schemars = { workspace = true } 30 | serde = { workspace = true } 31 | serde_json = { workspace = true } 32 | tokio = { workspace = true } 33 | uuid = { workspace = true } 34 | -------------------------------------------------------------------------------- /progenitor/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Oxide Computer Company 2 | 3 | //! Progenitor is a Rust crate for generating opinionated clients from API 4 | //! descriptions specified in the OpenAPI 3.0.x format. It makes use of Rust 5 | //! futures for async API calls and `Streams` for paginated interfaces. 6 | //! 7 | //! It generates a type called `Client` with methods that correspond to the 8 | //! operations specified in the OpenAPI document. 9 | //! 10 | //! For details see the [repo 11 | //! README](https://github.com/oxidecomputer/progenitor/blob/main/README.md) 12 | 13 | #![deny(missing_docs)] 14 | 15 | #[cfg(feature = "macro")] 16 | pub use progenitor_client; 17 | pub use progenitor_impl::CrateVers; 18 | pub use progenitor_impl::Error; 19 | pub use progenitor_impl::GenerationSettings; 20 | pub use progenitor_impl::Generator; 21 | pub use progenitor_impl::InterfaceStyle; 22 | pub use progenitor_impl::TagStyle; 23 | pub use progenitor_impl::TypeImpl; 24 | pub use progenitor_impl::TypePatch; 25 | #[cfg(feature = "macro")] 26 | pub use progenitor_macro::generate_api; 27 | -------------------------------------------------------------------------------- /progenitor/tests/build_buildomat.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | mod positional { 4 | progenitor::generate_api!("../sample_openapi/buildomat.json"); 5 | 6 | fn _ignore() { 7 | let _ = Client::new("").worker_task_upload_chunk("task", vec![0]); 8 | } 9 | } 10 | 11 | mod builder_untagged { 12 | progenitor::generate_api!( 13 | spec = "../sample_openapi/buildomat.json", 14 | interface = Builder, 15 | tags = Merged, 16 | ); 17 | 18 | fn _ignore() { 19 | let _ = Client::new("") 20 | .worker_task_upload_chunk() 21 | .task("task") 22 | .body(vec![0]) 23 | .send(); 24 | } 25 | } 26 | 27 | mod builder_tagged { 28 | progenitor::generate_api!( 29 | spec = "../sample_openapi/buildomat.json", 30 | interface = Builder, 31 | tags = Separate, 32 | ); 33 | 34 | fn _ignore() { 35 | let _ = Client::new("") 36 | .worker_task_upload_chunk() 37 | .task("task") 38 | .body(vec![0]) 39 | .send(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /progenitor/tests/build_keeper.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | mod positional { 4 | progenitor::generate_api!("../sample_openapi/keeper.json"); 5 | 6 | fn _ignore() { 7 | let _ = Client::new("").enrol( 8 | "auth token", 9 | &types::EnrolBody { 10 | host: "".to_string(), 11 | key: "".to_string(), 12 | }, 13 | ); 14 | } 15 | } 16 | 17 | mod builder_untagged { 18 | progenitor::generate_api!( 19 | spec = "../sample_openapi/keeper.json", 20 | interface = Builder, 21 | tags = Merged, 22 | ); 23 | 24 | fn _ignore() { 25 | let _ = Client::new("") 26 | .enrol() 27 | .authorization("") 28 | .body(types::EnrolBody { 29 | host: "".to_string(), 30 | key: "".to_string(), 31 | }) 32 | .send(); 33 | } 34 | } 35 | 36 | mod builder_tagged { 37 | progenitor::generate_api!( 38 | spec = "../sample_openapi/keeper.json", 39 | interface = Builder, 40 | tags = Separate, 41 | ); 42 | 43 | fn _ignore() { 44 | let _ = Client::new("") 45 | .enrol() 46 | .authorization("") 47 | .body(types::EnrolBody { 48 | host: "".to_string(), 49 | key: "".to_string(), 50 | }) 51 | .send(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /progenitor/tests/build_nexus.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | mod positional { 4 | use futures::StreamExt; 5 | 6 | mod nexus_client { 7 | progenitor::generate_api!("../sample_openapi/nexus.json"); 8 | } 9 | 10 | use nexus_client::{types, Client}; 11 | 12 | fn _ignore() { 13 | let _ = async { 14 | let client = Client::new(""); 15 | let org = types::Name::try_from("org").unwrap(); 16 | let project = types::Name::try_from("project").unwrap(); 17 | let instance = types::Name::try_from("instance").unwrap(); 18 | let stream = client.instance_disk_list_stream(&org, &project, &instance, None, None); 19 | let _ = stream.collect::>(); 20 | }; 21 | } 22 | } 23 | 24 | mod builder_untagged { 25 | use futures::StreamExt; 26 | 27 | mod nexus_client { 28 | use std::fmt::Display; 29 | 30 | #[derive(Clone, serde::Serialize, serde::Deserialize, Debug)] 31 | pub struct MyIpv4Net(String); 32 | impl std::str::FromStr for MyIpv4Net { 33 | type Err = std::convert::Infallible; 34 | fn from_str(value: &str) -> Result { 35 | Ok(Self(value.to_string())) 36 | } 37 | } 38 | impl Display for MyIpv4Net { 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 | self.0.fmt(f) 41 | } 42 | } 43 | progenitor::generate_api!( 44 | spec = "../sample_openapi/nexus.json", 45 | interface = Builder, 46 | tags = Merged, 47 | patch = { 48 | Name = { 49 | derives = [Hash], 50 | } 51 | }, 52 | replace = { 53 | Ipv4Net = crate::builder_untagged::nexus_client::MyIpv4Net, 54 | } 55 | ); 56 | } 57 | 58 | use nexus_client::Client; 59 | 60 | pub fn _ignore() { 61 | // Verify the replacement above. 62 | let _ignore = nexus_client::types::IpNet::V4("".parse().unwrap()); 63 | 64 | let client = Client::new(""); 65 | let stream = client 66 | .instance_disk_list() 67 | .organization_name("org") 68 | .project_name("project") 69 | .instance_name("instance") 70 | .stream(); 71 | let _ = stream.collect::>(); 72 | } 73 | } 74 | 75 | mod builder_tagged { 76 | use futures::StreamExt; 77 | 78 | mod nexus_client { 79 | progenitor::generate_api!( 80 | spec = "../sample_openapi/nexus.json", 81 | interface = Builder, 82 | tags = Separate, 83 | ); 84 | } 85 | 86 | use nexus_client::prelude::*; 87 | 88 | use self::nexus_client::types; 89 | 90 | async fn _ignore() { 91 | let client = Client::new(""); 92 | let stream = client 93 | .instance_disk_list() 94 | .organization_name("org") 95 | .project_name("project") 96 | .instance_name("instance") 97 | .stream(); 98 | let _ = stream.collect::>(); 99 | 100 | let _ = client 101 | .instance_create() 102 | .organization_name("org") 103 | .project_name("project") 104 | .body(self::nexus_client::types::InstanceCreate::builder()) 105 | .send() 106 | .await; 107 | } 108 | 109 | #[tokio::test] 110 | #[should_panic = "called `Result::unwrap()` on an `Err` value: Invalid Request: conversion to `SiloCreate` for body failed: no value supplied for description"] 111 | async fn test_decent_error_from_body_builder() { 112 | let _ = Client::new("") 113 | .silo_create() 114 | .body(types::SiloCreate::builder()) 115 | .send() 116 | .await 117 | .unwrap(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /progenitor/tests/build_propolis.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | // ensure that the websocket channel used for serial console compiles. 4 | mod propolis_client { 5 | progenitor::generate_api!( 6 | spec = "../sample_openapi/propolis-server.json", 7 | interface = Builder, 8 | tags = Merged, 9 | ); 10 | } 11 | 12 | use propolis_client::Client; 13 | 14 | pub fn _ignore() { 15 | let _ = async { 16 | let _upgraded: reqwest::Upgraded = Client::new("") 17 | .instance_serial() 18 | .send() 19 | .await 20 | .unwrap() 21 | .into_inner(); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /progenitor/tests/load_yaml.rs: -------------------------------------------------------------------------------- 1 | mod load_yaml { 2 | progenitor::generate_api!("../sample_openapi/param-overrides.yaml"); 3 | 4 | fn _ignore() { 5 | let _ = Client::new("").key_get(None, None); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /progenitor/tests/test_client.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | // Validate that we get useful output from a user-typed error. 4 | #[test] 5 | #[should_panic = "Error Response: \ 6 | status: 403 Forbidden; \ 7 | headers: {}; \ 8 | value: MyErr { msg: \"things went bad\" }"] 9 | fn test_error() { 10 | #[derive(Debug)] 11 | struct MyErr { 12 | #[allow(dead_code)] 13 | msg: String, 14 | } 15 | 16 | let mine = MyErr { 17 | msg: "things went bad".to_string(), 18 | }; 19 | let e = progenitor_client::Error::ErrorResponse(progenitor_client::ResponseValue::new( 20 | mine, 21 | reqwest::StatusCode::FORBIDDEN, 22 | reqwest::header::HeaderMap::default(), 23 | )); 24 | 25 | (Err(e) as Result<(), progenitor_client::Error>).unwrap(); 26 | } 27 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | # This file is used by cargo-release. 2 | 3 | # Update the change log to reflect the new release and set us up for the next release. 4 | pre-release-replacements = [ 5 | # First, replace the current "Unreleased changes" header with one reflecting the new release version and date. 6 | {file="../CHANGELOG.adoc", search="Unreleased changes \\(release date TBD\\)", replace="{{version}} (released {{date}})", exactly=1}, 7 | # Update the link to the list of raw commits in the formerly "Unreleased changes" section. It should end at the tag for the newly-released version. 8 | {file="../CHANGELOG.adoc", search="\\\\.\\.\\.HEAD", replace="\\...{{tag_name}}", exactly=1}, 9 | # Next, append a new "Unreleased changes" header beneath the sentinel line. 10 | {file="../CHANGELOG.adoc", search="// cargo-release: next header goes here \\(do not change this line\\)", replace="// cargo-release: next header goes here (do not change this line)\n\n== Unreleased changes (release date TBD)\n\nhttps://github.com/oxidecomputer/progenitor/compare/{{tag_name}}\\...HEAD[Full list of commits]", exactly=1}, 11 | ] 12 | 13 | pre-release-commit-message = "release progenitor {{version}}" 14 | tag-message = "release {{crate_name}} {{version}}" 15 | tag-prefix = "" 16 | consolidate-commits = true 17 | push = false 18 | shared-version = true 19 | dependent-version = "upgrade" 20 | -------------------------------------------------------------------------------- /sample_openapi/README.md: -------------------------------------------------------------------------------- 1 | # Sources 2 | 3 | This directory contains some OpenAPI definitions included in this project 4 | solely for the purpose of testing various progenitor features. Some of these 5 | have been pulled in from other projects: 6 | 7 | - `api.github.com.json`: https://github.com/github/rest-api-description 8 | - `buildomat.json`: https://github.com/oxidecomputer/buildomat 9 | - `keeper.json`: https://github.com/jclulow/keeper 10 | - `nexus.json`: https://github.com/oxidecomputer/omicron 11 | - `propolis-server.json`: https://github.com/oxidecomputer/propolis 12 | 13 | Note that keeper and buildomat have diverged in order to validate support for various features. The others should not be changed other than updating them from their source repository. -------------------------------------------------------------------------------- /sample_openapi/cli-gen.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "description": "Test case to exercise CLI generation", 5 | "title": "CLI gen test", 6 | "version": "9000" 7 | }, 8 | "paths": { 9 | "/uno": { 10 | "get": { 11 | "operationId": "uno", 12 | "parameters": [ 13 | { 14 | "name": "gateway", 15 | "in": "query", 16 | "required": true, 17 | "schema": { 18 | "type": "string" 19 | } 20 | } 21 | ], 22 | "requestBody": { 23 | "content": { 24 | "application/json": { 25 | "schema": { 26 | "type": "object", 27 | "properties": { 28 | "gateway": { 29 | "type": "string" 30 | } 31 | }, 32 | "required": [ 33 | "required" 34 | ] 35 | } 36 | } 37 | } 38 | }, 39 | "responses": {} 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sample_openapi/param-collision.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "description": "Minimal API for testing collision between parameter names and generated code", 5 | "title": "Parameter name collision test", 6 | "version": "v1" 7 | }, 8 | "paths": { 9 | "/key/{query}": { 10 | "get": { 11 | "description": "Gets a key", 12 | "operationId": "key.get", 13 | "parameters": [ 14 | { 15 | "description": "Parameter name that was previously colliding", 16 | "in": "path", 17 | "name": "query", 18 | "required": true, 19 | "schema": { 20 | "type": "boolean" 21 | } 22 | }, 23 | { 24 | "description": "Parameter name that was previously colliding", 25 | "in": "query", 26 | "name": "url", 27 | "required": true, 28 | "schema": { 29 | "type": "boolean" 30 | } 31 | }, 32 | { 33 | "description": "Parameter name that was previously colliding", 34 | "in": "query", 35 | "name": "request", 36 | "required": true, 37 | "schema": { 38 | "type": "boolean" 39 | } 40 | }, 41 | { 42 | "description": "Parameter name that was previously colliding", 43 | "in": "query", 44 | "name": "response", 45 | "required": true, 46 | "schema": { 47 | "type": "boolean" 48 | } 49 | }, 50 | { 51 | "description": "Parameter name that was previously colliding", 52 | "in": "query", 53 | "name": "result", 54 | "required": true, 55 | "schema": { 56 | "type": "boolean" 57 | } 58 | }, 59 | { 60 | "description": "Parameter name that was previously colliding", 61 | "in": "query", 62 | "name": "client", 63 | "required": true, 64 | "schema": { 65 | "type": "boolean" 66 | } 67 | } 68 | ], 69 | "responses": { 70 | "200": { 71 | "type": "string", 72 | "description": "Successful response" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /sample_openapi/param-overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "description": "Minimal API for testing parameter overrides", 5 | "title": "Parameter override test", 6 | "version": "v1" 7 | }, 8 | "paths": { 9 | "/key": { 10 | "get": { 11 | "description": "Gets a key", 12 | "operationId": "key.get", 13 | "parameters": [ 14 | { 15 | "description": "The same key parameter that overlaps with the path level parameter", 16 | "in": "query", 17 | "name": "key", 18 | "schema": { 19 | "type": "boolean" 20 | } 21 | } 22 | ], 23 | "responses": { 24 | "200": { 25 | "type": "string", 26 | "description": "Successful response" 27 | } 28 | } 29 | }, 30 | "parameters": [ 31 | { 32 | "$ref": "#/components/parameters/key" 33 | }, 34 | { 35 | "$ref": "#/components/parameters/unique-key" 36 | } 37 | ] 38 | } 39 | }, 40 | "components": { 41 | "parameters": { 42 | "key": { 43 | "description": "A key parameter that will be overridden by the path spec", 44 | "in": "query", 45 | "name": "key", 46 | "schema": { 47 | "type": "string" 48 | } 49 | }, 50 | "unique-key": { 51 | "description": "A key parameter that will not be overridden by the path spec", 52 | "in": "query", 53 | "name": "uniqueKey", 54 | "schema": { 55 | "type": "string" 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /sample_openapi/param-overrides.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | description: Minimal API for testing parameter overrides 4 | title: Parameter override test 5 | version: v1 6 | components: 7 | parameters: 8 | key: 9 | description: A key parameter that will be overridden by the path spec 10 | in: query 11 | name: key 12 | schema: 13 | type: string 14 | unique-key: 15 | description: A key parameter that will not be overridden by the path spec 16 | in: query 17 | name: uniqueKey 18 | schema: 19 | type: string 20 | paths: 21 | /key: 22 | get: 23 | description: Gets a key 24 | operationId: key.get 25 | parameters: 26 | - description: The same key parameter that overlaps with the path level parameter 27 | in: query 28 | name: key 29 | schema: 30 | type: boolean 31 | responses: 32 | '200': 33 | description: Successful response 34 | type: string 35 | parameters: 36 | - $ref: '#/components/parameters/key' 37 | - $ref: '#/components/parameters/unique-key' 38 | --------------------------------------------------------------------------------