From 6ae4634639c2e3d9588c6e9c348f17d2f8cf85ca Mon Sep 17 00:00:00 2001 From: Richard Whitehouse Date: Thu, 15 Aug 2024 14:05:28 +0100 Subject: [PATCH] Update samples --- .../multipart-v3/.openapi-generator/FILES | 1 + .../output/multipart-v3/Cargo.toml | 15 + .../rust-server/output/multipart-v3/README.md | 27 + .../output/multipart-v3/bin/cli.rs | 218 ++++ .../no-example-v3/.openapi-generator/FILES | 1 + .../output/no-example-v3/Cargo.toml | 15 + .../output/no-example-v3/README.md | 27 + .../output/no-example-v3/bin/cli.rs | 154 +++ .../openapi-v3/.openapi-generator/FILES | 1 + .../rust-server/output/openapi-v3/Cargo.toml | 15 + .../rust-server/output/openapi-v3/README.md | 50 +- .../output/openapi-v3/api/openapi.yaml | 71 +- .../rust-server/output/openapi-v3/bin/cli.rs | 857 ++++++++++++ .../output/openapi-v3/docs/default_api.md | 95 +- .../output/openapi-v3/examples/client/main.rs | 28 + .../openapi-v3/examples/server/server.rs | 34 +- .../openapi-v3/examples/server/server_auth.rs | 2 + .../output/openapi-v3/src/client/mod.rs | 251 ++++ .../output/openapi-v3/src/context.rs | 19 + .../rust-server/output/openapi-v3/src/lib.rs | 87 +- .../output/openapi-v3/src/server/mod.rs | 261 +++- .../output/ops-v3/.openapi-generator/FILES | 1 + .../rust-server/output/ops-v3/Cargo.toml | 15 + .../rust-server/output/ops-v3/README.md | 27 + .../rust-server/output/ops-v3/bin/cli.rs | 762 +++++++++++ .../.openapi-generator/FILES | 1 + .../Cargo.toml | 17 + .../README.md | 27 + .../bin/cli.rs | 1152 +++++++++++++++++ .../ping-bearer-auth/.openapi-generator/FILES | 1 + .../output/ping-bearer-auth/Cargo.toml | 15 + .../output/ping-bearer-auth/README.md | 27 + .../output/ping-bearer-auth/bin/cli.rs | 159 +++ .../rust-server-test/.openapi-generator/FILES | 1 + .../output/rust-server-test/Cargo.toml | 15 + .../output/rust-server-test/README.md | 27 + .../output/rust-server-test/bin/cli.rs | 315 +++++ 37 files changed, 4763 insertions(+), 28 deletions(-) create mode 100644 samples/server/petstore/rust-server/output/multipart-v3/bin/cli.rs create mode 100644 samples/server/petstore/rust-server/output/no-example-v3/bin/cli.rs create mode 100644 samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs create mode 100644 samples/server/petstore/rust-server/output/ops-v3/bin/cli.rs create mode 100644 samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs create mode 100644 samples/server/petstore/rust-server/output/ping-bearer-auth/bin/cli.rs create mode 100644 samples/server/petstore/rust-server/output/rust-server-test/bin/cli.rs diff --git a/samples/server/petstore/rust-server/output/multipart-v3/.openapi-generator/FILES b/samples/server/petstore/rust-server/output/multipart-v3/.openapi-generator/FILES index c4ea8aa366553..3f051cfa5e44f 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/.openapi-generator/FILES +++ b/samples/server/petstore/rust-server/output/multipart-v3/.openapi-generator/FILES @@ -3,6 +3,7 @@ Cargo.toml README.md api/openapi.yaml +bin/cli.rs docs/MultipartRelatedRequest.md docs/MultipartRequestObjectField.md docs/MultipleIdenticalMimeTypesPostRequest.md diff --git a/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml b/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml index c4fcb575a89b7..444e251bab856 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml @@ -21,6 +21,9 @@ server = [ "hyper_0_10", "mime_multipart", "swagger/multipart_related", "serde_ignored", "hyper", "regex", "percent-encoding", "url", "lazy_static" ] +cli = [ + "anyhow", "clap-verbosity-flag", "simple_logger", "structopt", "tokio" +] conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] @@ -62,6 +65,13 @@ lazy_static = { version = "1.4", optional = true } percent-encoding = {version = "2.1.0", optional = true} regex = {version = "1.3", optional = true} +# CLI-specific +anyhow = { version = "1", optional = true } +clap-verbosity-flag = { version = "0.3", optional = true } +simple_logger = { version = "2.0", features = ["stderr"], optional = true } +structopt = { version = "0.3", optional = true } +tokio = { version = "0.2", features = ["rt-threaded", "macros", "stream"], optional = true } + # Conversion frunk = { version = "0.4.0", optional = true } frunk_derives = { version = "0.4.0", optional = true } @@ -89,3 +99,8 @@ required-features = ["client"] [[example]] name = "server" required-features = ["server"] + +[[bin]] +name = "multipart-v3" +path = "bin/cli.rs" +required-features = ["client", "cli"] diff --git a/samples/server/petstore/rust-server/output/multipart-v3/README.md b/samples/server/petstore/rust-server/output/multipart-v3/README.md index d0cfa65677325..4ce949fca4629 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/README.md +++ b/samples/server/petstore/rust-server/output/multipart-v3/README.md @@ -23,6 +23,7 @@ This autogenerated project defines an API crate `multipart-v3` which contains: * Data types representing the underlying data model. * A `Client` type which implements `Api` and issues HTTP requests for each operation. * A router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. +* A CLI tool to drive basic API operations from the command line. It also contains an example server and client which make use of `multipart-v3`: @@ -36,6 +37,30 @@ It also contains an example server and client which make use of `multipart-v3`: You can use the example server and client as a basis for your own code. See below for [more detail on the examples](#using-the-generated-library). +## CLI + +Run the included CLI tool with: + +``` +cargo run --bin cli --features=cli +``` + +To pass in arguments, put them after `--`, for example: + +``` +cargo run --bin cli --features=cli -- --help +``` + +See the help text for available options. + +To build a standalone tool, use: + +``` +cargo build --bin cli --features=cli --release +``` + +You'll find the binary at `target/release/cli`. + ## Examples Run examples with: @@ -88,6 +113,8 @@ The generated library has a few optional features that can be activated through * The constructed client implements the API trait by making remote API call. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. +* `cli` + * This defaults to disabled and is required for building the included CLI tool. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. diff --git a/samples/server/petstore/rust-server/output/multipart-v3/bin/cli.rs b/samples/server/petstore/rust-server/output/multipart-v3/bin/cli.rs new file mode 100644 index 0000000000000..9c294b660f017 --- /dev/null +++ b/samples/server/petstore/rust-server/output/multipart-v3/bin/cli.rs @@ -0,0 +1,218 @@ +//! CLI tool driving the API client +use anyhow::{anyhow, Context, Result}; +use log::{debug, info}; +// models may be unused if all inputs are primitive types +#[allow(unused_imports)] +use multipart_v3::{ + models, ApiNoContext, Client, ContextWrapperExt, + MultipartRelatedRequestPostResponse, + MultipartRequestPostResponse, + MultipleIdenticalMimeTypesPostResponse, +}; +use simple_logger::SimpleLogger; +use structopt::StructOpt; +use swagger::{AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString}; + +type ClientContext = swagger::make_context_ty!( + ContextBuilder, + EmptyContext, + Option, + XSpanIdString +); + +#[derive(StructOpt, Debug)] +#[structopt( + name = "Multipart OpenAPI V3 Rust Server Test", + version = "1.0.7", + about = "CLI access to Multipart OpenAPI V3 Rust Server Test" +)] +struct Cli { + #[structopt(subcommand)] + operation: Operation, + + /// Address or hostname of the server hosting this API, including optional port + #[structopt(short = "a", long, default_value = "http://localhost")] + server_address: String, + + /// Path to the client private key if using client-side TLS authentication + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-certificate", "server-certificate"]))] + client_key: Option, + + /// Path to the client's public certificate associated with the private key + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-key", "server-certificate"]))] + client_certificate: Option, + + /// Path to CA certificate used to authenticate the server + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long)] + server_certificate: Option, + + /// If set, write output to file instead of stdout + #[structopt(short, long)] + output_file: Option, + + #[structopt(flatten)] + verbosity: clap_verbosity_flag::Verbosity, +} + +#[derive(StructOpt, Debug)] +enum Operation { + MultipartRelatedRequestPost { + #[structopt(parse(try_from_str = parse_json))] + required_binary_field: swagger::ByteArray, + #[structopt(parse(try_from_str = parse_json))] + object_field: Option, + #[structopt(parse(try_from_str = parse_json))] + optional_binary_field: Option, + }, + MultipartRequestPost { + string_field: String, + #[structopt(parse(try_from_str = parse_json))] + binary_field: swagger::ByteArray, + optional_string_field: Option, + #[structopt(parse(try_from_str = parse_json))] + object_field: Option, + }, + MultipleIdenticalMimeTypesPost { + #[structopt(parse(try_from_str = parse_json))] + binary1: Option, + #[structopt(parse(try_from_str = parse_json))] + binary2: Option, + }, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + if args.client_certificate.is_some() { + debug!("Using mutual TLS"); + let client = Client::try_new_https_mutual( + &args.server_address, + args.server_certificate.unwrap(), + args.client_key.unwrap(), + args.client_certificate.unwrap(), + ) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else if args.server_certificate.is_some() { + debug!("Using TLS with pinned server certificate"); + let client = + Client::try_new_https_pinned(&args.server_address, args.server_certificate.unwrap()) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else { + debug!("Using client without certificates"); + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) + } +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::from_args(); + if let Some(log_level) = args.verbosity.log_level() { + SimpleLogger::new().with_level(log_level.to_level_filter()).init()?; + } + + debug!("Arguments: {:?}", &args); + + let auth_data: Option = None; + + #[allow(trivial_casts)] + let context = swagger::make_context!( + ContextBuilder, + EmptyContext, + auth_data, + XSpanIdString::default() + ); + + let client = create_client(&args, context)?; + + let result = match args.operation { + Operation::MultipartRelatedRequestPost { + required_binary_field, + object_field, + optional_binary_field, + } => { + info!("Performing a MultipartRelatedRequestPost request"); + + let result = client.multipart_related_request_post( + required_binary_field, + object_field, + optional_binary_field, + ).await?; + debug!("Result: {:?}", result); + + match result { + MultipartRelatedRequestPostResponse::OK + => "OK\n".to_string() + , + } + } + Operation::MultipartRequestPost { + string_field, + binary_field, + optional_string_field, + object_field, + } => { + info!("Performing a MultipartRequestPost request"); + + let result = client.multipart_request_post( + string_field, + binary_field, + optional_string_field, + object_field, + ).await?; + debug!("Result: {:?}", result); + + match result { + MultipartRequestPostResponse::OK + => "OK\n".to_string() + , + } + } + Operation::MultipleIdenticalMimeTypesPost { + binary1, + binary2, + } => { + info!("Performing a MultipleIdenticalMimeTypesPost request"); + + let result = client.multiple_identical_mime_types_post( + binary1, + binary2, + ).await?; + debug!("Result: {:?}", result); + + match result { + MultipleIdenticalMimeTypesPostResponse::OK + => "OK\n".to_string() + , + } + } + }; + + if let Some(output_file) = args.output_file { + std::fs::write(output_file, result)? + } else { + println!("{}", result); + } + Ok(()) +} + +// May be unused if all inputs are primitive types +#[allow(dead_code)] +fn parse_json<'a, T: serde::de::Deserialize<'a>>(json_string: &'a str) -> Result { + serde_json::from_str(json_string).map_err(|err| anyhow!("Error parsing input: {}", err)) +} diff --git a/samples/server/petstore/rust-server/output/no-example-v3/.openapi-generator/FILES b/samples/server/petstore/rust-server/output/no-example-v3/.openapi-generator/FILES index c41dce9b6f0ce..7c4254323479e 100644 --- a/samples/server/petstore/rust-server/output/no-example-v3/.openapi-generator/FILES +++ b/samples/server/petstore/rust-server/output/no-example-v3/.openapi-generator/FILES @@ -3,6 +3,7 @@ Cargo.toml README.md api/openapi.yaml +bin/cli.rs docs/OpGetRequest.md docs/default_api.md examples/ca.pem diff --git a/samples/server/petstore/rust-server/output/no-example-v3/Cargo.toml b/samples/server/petstore/rust-server/output/no-example-v3/Cargo.toml index 293d8173ef3c0..1f1e452d0471b 100644 --- a/samples/server/petstore/rust-server/output/no-example-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/no-example-v3/Cargo.toml @@ -15,6 +15,9 @@ client = [ server = [ "serde_ignored", "hyper", "regex", "percent-encoding", "url", "lazy_static" ] +cli = [ + "anyhow", "clap-verbosity-flag", "simple_logger", "structopt", "tokio" +] conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] @@ -52,6 +55,13 @@ lazy_static = { version = "1.4", optional = true } percent-encoding = {version = "2.1.0", optional = true} regex = {version = "1.3", optional = true} +# CLI-specific +anyhow = { version = "1", optional = true } +clap-verbosity-flag = { version = "0.3", optional = true } +simple_logger = { version = "2.0", features = ["stderr"], optional = true } +structopt = { version = "0.3", optional = true } +tokio = { version = "0.2", features = ["rt-threaded", "macros", "stream"], optional = true } + # Conversion frunk = { version = "0.4.0", optional = true } frunk_derives = { version = "0.4.0", optional = true } @@ -79,3 +89,8 @@ required-features = ["client"] [[example]] name = "server" required-features = ["server"] + +[[bin]] +name = "no-example-v3" +path = "bin/cli.rs" +required-features = ["client", "cli"] diff --git a/samples/server/petstore/rust-server/output/no-example-v3/README.md b/samples/server/petstore/rust-server/output/no-example-v3/README.md index 604b8325ed624..8024bdab66250 100644 --- a/samples/server/petstore/rust-server/output/no-example-v3/README.md +++ b/samples/server/petstore/rust-server/output/no-example-v3/README.md @@ -23,6 +23,7 @@ This autogenerated project defines an API crate `no-example-v3` which contains: * Data types representing the underlying data model. * A `Client` type which implements `Api` and issues HTTP requests for each operation. * A router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. +* A CLI tool to drive basic API operations from the command line. It also contains an example server and client which make use of `no-example-v3`: @@ -36,6 +37,30 @@ It also contains an example server and client which make use of `no-example-v3`: You can use the example server and client as a basis for your own code. See below for [more detail on the examples](#using-the-generated-library). +## CLI + +Run the included CLI tool with: + +``` +cargo run --bin cli --features=cli +``` + +To pass in arguments, put them after `--`, for example: + +``` +cargo run --bin cli --features=cli -- --help +``` + +See the help text for available options. + +To build a standalone tool, use: + +``` +cargo build --bin cli --features=cli --release +``` + +You'll find the binary at `target/release/cli`. + ## Examples Run examples with: @@ -85,6 +110,8 @@ The generated library has a few optional features that can be activated through * The constructed client implements the API trait by making remote API call. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. +* `cli` + * This defaults to disabled and is required for building the included CLI tool. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. diff --git a/samples/server/petstore/rust-server/output/no-example-v3/bin/cli.rs b/samples/server/petstore/rust-server/output/no-example-v3/bin/cli.rs new file mode 100644 index 0000000000000..9da03e6c0435c --- /dev/null +++ b/samples/server/petstore/rust-server/output/no-example-v3/bin/cli.rs @@ -0,0 +1,154 @@ +//! CLI tool driving the API client +use anyhow::{anyhow, Context, Result}; +use log::{debug, info}; +// models may be unused if all inputs are primitive types +#[allow(unused_imports)] +use no_example_v3::{ + models, ApiNoContext, Client, ContextWrapperExt, + OpGetResponse, +}; +use simple_logger::SimpleLogger; +use structopt::StructOpt; +use swagger::{AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString}; + +type ClientContext = swagger::make_context_ty!( + ContextBuilder, + EmptyContext, + Option, + XSpanIdString +); + +#[derive(StructOpt, Debug)] +#[structopt( + name = "Regression test for an API which doesn't have any example", + version = "0.0.1", + about = "CLI access to Regression test for an API which doesn't have any example" +)] +struct Cli { + #[structopt(subcommand)] + operation: Operation, + + /// Address or hostname of the server hosting this API, including optional port + #[structopt(short = "a", long, default_value = "http://localhost")] + server_address: String, + + /// Path to the client private key if using client-side TLS authentication + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-certificate", "server-certificate"]))] + client_key: Option, + + /// Path to the client's public certificate associated with the private key + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-key", "server-certificate"]))] + client_certificate: Option, + + /// Path to CA certificate used to authenticate the server + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long)] + server_certificate: Option, + + /// If set, write output to file instead of stdout + #[structopt(short, long)] + output_file: Option, + + #[structopt(flatten)] + verbosity: clap_verbosity_flag::Verbosity, +} + +#[derive(StructOpt, Debug)] +enum Operation { + OpGet { + #[structopt(parse(try_from_str = parse_json))] + op_get_request: models::OpGetRequest, + }, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + if args.client_certificate.is_some() { + debug!("Using mutual TLS"); + let client = Client::try_new_https_mutual( + &args.server_address, + args.server_certificate.unwrap(), + args.client_key.unwrap(), + args.client_certificate.unwrap(), + ) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else if args.server_certificate.is_some() { + debug!("Using TLS with pinned server certificate"); + let client = + Client::try_new_https_pinned(&args.server_address, args.server_certificate.unwrap()) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else { + debug!("Using client without certificates"); + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) + } +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::from_args(); + if let Some(log_level) = args.verbosity.log_level() { + SimpleLogger::new().with_level(log_level.to_level_filter()).init()?; + } + + debug!("Arguments: {:?}", &args); + + let auth_data: Option = None; + + #[allow(trivial_casts)] + let context = swagger::make_context!( + ContextBuilder, + EmptyContext, + auth_data, + XSpanIdString::default() + ); + + let client = create_client(&args, context)?; + + let result = match args.operation { + Operation::OpGet { + op_get_request, + } => { + info!("Performing a OpGet request"); + + let result = client.op_get( + op_get_request, + ).await?; + debug!("Result: {:?}", result); + + match result { + OpGetResponse::OK + => "OK\n".to_string() + , + } + } + }; + + if let Some(output_file) = args.output_file { + std::fs::write(output_file, result)? + } else { + println!("{}", result); + } + Ok(()) +} + +// May be unused if all inputs are primitive types +#[allow(dead_code)] +fn parse_json<'a, T: serde::de::Deserialize<'a>>(json_string: &'a str) -> Result { + serde_json::from_str(json_string).map_err(|err| anyhow!("Error parsing input: {}", err)) +} diff --git a/samples/server/petstore/rust-server/output/openapi-v3/.openapi-generator/FILES b/samples/server/petstore/rust-server/output/openapi-v3/.openapi-generator/FILES index 7d5a613139442..35f1766cbe8cb 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/.openapi-generator/FILES +++ b/samples/server/petstore/rust-server/output/openapi-v3/.openapi-generator/FILES @@ -3,6 +3,7 @@ Cargo.toml README.md api/openapi.yaml +bin/cli.rs docs/AdditionalPropertiesWithList.md docs/AnotherXmlArray.md docs/AnotherXmlInner.md diff --git a/samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml b/samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml index 1a1a99497514b..9161d71eff507 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml @@ -17,6 +17,9 @@ server = [ "native-tls", "hyper-openssl", "hyper-tls", "openssl", "serde_ignored", "hyper", "regex", "percent-encoding", "url", "lazy_static" ] +cli = [ + "anyhow", "clap-verbosity-flag", "simple_logger", "structopt", "tokio" +] conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] @@ -58,6 +61,13 @@ lazy_static = { version = "1.4", optional = true } percent-encoding = {version = "2.1.0", optional = true} regex = {version = "1.3", optional = true} +# CLI-specific +anyhow = { version = "1", optional = true } +clap-verbosity-flag = { version = "0.3", optional = true } +simple_logger = { version = "2.0", features = ["stderr"], optional = true } +structopt = { version = "0.3", optional = true } +tokio = { version = "0.2", features = ["rt-threaded", "macros", "stream"], optional = true } + # Conversion frunk = { version = "0.4.0", optional = true } frunk_derives = { version = "0.4.0", optional = true } @@ -85,3 +95,8 @@ required-features = ["client"] [[example]] name = "server" required-features = ["server"] + +[[bin]] +name = "openapi-v3" +path = "bin/cli.rs" +required-features = ["client", "cli"] diff --git a/samples/server/petstore/rust-server/output/openapi-v3/README.md b/samples/server/petstore/rust-server/output/openapi-v3/README.md index d190384fdcf7c..f06ee05c16729 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/README.md +++ b/samples/server/petstore/rust-server/output/openapi-v3/README.md @@ -23,6 +23,7 @@ This autogenerated project defines an API crate `openapi-v3` which contains: * Data types representing the underlying data model. * A `Client` type which implements `Api` and issues HTTP requests for each operation. * A router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. +* A CLI tool to drive basic API operations from the command line. It also contains an example server and client which make use of `openapi-v3`: @@ -36,6 +37,30 @@ It also contains an example server and client which make use of `openapi-v3`: You can use the example server and client as a basis for your own code. See below for [more detail on the examples](#using-the-generated-library). +## CLI + +Run the included CLI tool with: + +``` +cargo run --bin cli --features=cli +``` + +To pass in arguments, put them after `--`, for example: + +``` +cargo run --bin cli --features=cli -- --help +``` + +See the help text for available options. + +To build a standalone tool, use: + +``` +cargo build --bin cli --features=cli --release +``` + +You'll find the binary at `target/release/cli`. + ## Examples Run examples with: @@ -64,6 +89,7 @@ To run a client, follow one of the following simple steps: cargo run --example client AnyOfGet cargo run --example client CallbackWithHeaderPost cargo run --example client ComplexQueryParamGet +cargo run --example client GetWithBooleanParameter cargo run --example client JsonComplexQueryParamGet cargo run --example client MandatoryRequestHeaderGet cargo run --example client MergePatchJsonGet @@ -77,6 +103,7 @@ cargo run --example client RegisterCallbackPost cargo run --example client RequiredOctetStreamPut cargo run --example client ResponsesWithHeadersGet cargo run --example client Rfc7807Get +cargo run --example client TwoFirstLetterHeaders cargo run --example client UntypedPropertyGet cargo run --example client UuidGet cargo run --example client XmlExtraPost @@ -84,6 +111,7 @@ cargo run --example client XmlOtherPost cargo run --example client XmlOtherPut cargo run --example client XmlPost cargo run --example client XmlPut +cargo run --example client MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet cargo run --example client CreateRepo cargo run --example client GetRepoInfo ``` @@ -110,6 +138,8 @@ The generated library has a few optional features that can be activated through * The constructed client implements the API trait by making remote API call. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. +* `cli` + * This defaults to disabled and is required for building the included CLI tool. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. @@ -122,6 +152,7 @@ Method | HTTP request | Description [****](docs/default_api.md#) | **GET** /any-of | [****](docs/default_api.md#) | **POST** /callback-with-header | [****](docs/default_api.md#) | **GET** /complex-query-param | +[**GetWithBooleanParameter**](docs/default_api.md#GetWithBooleanParameter) | **GET** /get-with-bool | [****](docs/default_api.md#) | **GET** /json-complex-query-param | [****](docs/default_api.md#) | **GET** /mandatory-request-header | [****](docs/default_api.md#) | **GET** /merge-patch-json | @@ -135,14 +166,16 @@ Method | HTTP request | Description [****](docs/default_api.md#) | **PUT** /required_octet_stream | [****](docs/default_api.md#) | **GET** /responses_with_headers | [****](docs/default_api.md#) | **GET** /rfc7807 | +[**TwoFirstLetterHeaders**](docs/default_api.md#TwoFirstLetterHeaders) | **POST** /operation-two-first-letter-headers | [****](docs/default_api.md#) | **GET** /untyped_property | [****](docs/default_api.md#) | **GET** /uuid | [****](docs/default_api.md#) | **POST** /xml_extra | [****](docs/default_api.md#) | **POST** /xml_other | [****](docs/default_api.md#) | **PUT** /xml_other | -[****](docs/default_api.md#) | **POST** /xml | Post an array +[****](docs/default_api.md#) | **POST** /xml | Post an array. It's important we test apostrophes, so include one here. [****](docs/default_api.md#) | **PUT** /xml | [****](docs/default_api.md#) | **GET** /enum_in_path/{path_param} | +[****](docs/default_api.md#) | **GET** /multiple-path-params-with-very-long-path-to-test-formatting/{path_param_a}/{path_param_b} | [**CreateRepo**](docs/repo_api.md#CreateRepo) | **POST** /repos | [**GetRepoInfo**](docs/repo_api.md#GetRepoInfo) | **GET** /repos/{repoId} | @@ -197,6 +230,21 @@ Example ``` ``` +Or via OAuth2 module to automatically refresh tokens and perform user authentication. +``` +``` +### additionalAuthScheme +- **Type**: OAuth +- **Flow**: accessCode +- **Authorization URL**: http://example.org +- **Scopes**: + - **additional.test.read**: Allowed to read state. + - **additional.test.write**: Allowed to change state. + +Example +``` +``` + Or via OAuth2 module to automatically refresh tokens and perform user authentication. ``` ``` diff --git a/samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml b/samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml index 729b977ae329b..7eb8d5d46cbce 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml +++ b/samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml @@ -19,7 +19,8 @@ paths: description: OK "400": description: Bad Request - summary: Post an array + summary: "Post an array. It's important we test apostrophes, so include one\ + \ here." put: requestBody: content: @@ -465,6 +466,65 @@ paths: responses: "200": description: Success + /multiple-path-params-with-very-long-path-to-test-formatting/{path_param_a}/{path_param_b}: + get: + parameters: + - explode: false + in: path + name: path_param_a + required: true + schema: + $ref: '#/components/schemas/StringObject' + style: simple + - explode: false + in: path + name: path_param_b + required: true + schema: + $ref: '#/components/schemas/StringObject' + style: simple + responses: + "200": + description: Success + /get-with-bool: + get: + description: Get with a boolean parameter + operationId: GetWithBooleanParameter + parameters: + - description: Let's check apostrophes get encoded properly! + explode: true + in: query + name: iambool + required: true + schema: + type: boolean + style: form + responses: + "200": + description: OK + /operation-two-first-letter-headers: + post: + description: Check we don't barf if two boolean parameters have the same first + letter + operationId: TwoFirstLetterHeaders + parameters: + - explode: false + in: header + name: x-header-one + required: false + schema: + type: boolean + style: simple + - explode: false + in: header + name: x-header-two + required: false + schema: + type: boolean + style: simple + responses: + "200": + description: OK components: schemas: AnyOfProperty: @@ -711,4 +771,13 @@ components: test.write: Allowed to change state. tokenUrl: http://example.org type: oauth2 + additionalAuthScheme: + flows: + authorizationCode: + authorizationUrl: http://example.org + scopes: + additional.test.read: Allowed to read state. + additional.test.write: Allowed to change state. + tokenUrl: http://example.org + type: oauth2 diff --git a/samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs b/samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs new file mode 100644 index 0000000000000..d898fbde78440 --- /dev/null +++ b/samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs @@ -0,0 +1,857 @@ +//! CLI tool driving the API client +use anyhow::{anyhow, Context, Result}; +use log::{debug, info}; +// models may be unused if all inputs are primitive types +#[allow(unused_imports)] +use openapi_v3::{ + models, ApiNoContext, Client, ContextWrapperExt, + AnyOfGetResponse, + CallbackWithHeaderPostResponse, + ComplexQueryParamGetResponse, + GetWithBooleanParameterResponse, + JsonComplexQueryParamGetResponse, + MandatoryRequestHeaderGetResponse, + MergePatchJsonGetResponse, + MultigetGetResponse, + MultipleAuthSchemeGetResponse, + OneOfGetResponse, + OverrideServerGetResponse, + ParamgetGetResponse, + ReadonlyAuthSchemeGetResponse, + RegisterCallbackPostResponse, + RequiredOctetStreamPutResponse, + ResponsesWithHeadersGetResponse, + Rfc7807GetResponse, + TwoFirstLetterHeadersResponse, + UntypedPropertyGetResponse, + UuidGetResponse, + XmlExtraPostResponse, + XmlOtherPostResponse, + XmlOtherPutResponse, + XmlPostResponse, + XmlPutResponse, + EnumInPathPathParamGetResponse, + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse, + CreateRepoResponse, + GetRepoInfoResponse, +}; +use simple_logger::SimpleLogger; +use structopt::StructOpt; +use swagger::{AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString}; + +type ClientContext = swagger::make_context_ty!( + ContextBuilder, + EmptyContext, + Option, + XSpanIdString +); + +#[derive(StructOpt, Debug)] +#[structopt( + name = "My title", + version = "1.0.7", + about = "CLI access to My title" +)] +struct Cli { + #[structopt(subcommand)] + operation: Operation, + + /// Address or hostname of the server hosting this API, including optional port + #[structopt(short = "a", long, default_value = "http://localhost")] + server_address: String, + + /// Path to the client private key if using client-side TLS authentication + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-certificate", "server-certificate"]))] + client_key: Option, + + /// Path to the client's public certificate associated with the private key + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-key", "server-certificate"]))] + client_certificate: Option, + + /// Path to CA certificate used to authenticate the server + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long)] + server_certificate: Option, + + /// If set, write output to file instead of stdout + #[structopt(short, long)] + output_file: Option, + + #[structopt(flatten)] + verbosity: clap_verbosity_flag::Verbosity, + + /// Bearer token if used for authentication + #[structopt(env = "OPENAPI_V3_BEARER_TOKEN", hide_env_values = true)] + bearer_token: Option, +} + +#[derive(StructOpt, Debug)] +enum Operation { + AnyOfGet { + /// list of any of objects + #[structopt(parse(try_from_str = parse_json), long)] + any_of: Option>, + }, + CallbackWithHeaderPost { + url: String, + }, + ComplexQueryParamGet { + #[structopt(parse(try_from_str = parse_json), long)] + list_of_strings: Option>, + }, + GetWithBooleanParameter { + /// Let's check apostrophes get encoded properly! + #[structopt(short, long)] + iambool: bool, + }, + JsonComplexQueryParamGet { + #[structopt(parse(try_from_str = parse_json), long)] + list_of_strings: Option>, + }, + MandatoryRequestHeaderGet { + x_header: String, + }, + MergePatchJsonGet { + }, + /// Get some stuff. + MultigetGet { + }, + MultipleAuthSchemeGet { + }, + OneOfGet { + }, + OverrideServerGet { + }, + /// Get some stuff with parameters. + ParamgetGet { + /// The stuff to get + #[structopt(parse(try_from_str = parse_json))] + uuid: Option, + /// Some object to pass as query parameter + #[structopt(parse(try_from_str = parse_json))] + some_object: Option, + /// Some list to pass as query parameter + #[structopt(parse(try_from_str = parse_json))] + some_list: Option, + }, + ReadonlyAuthSchemeGet { + }, + RegisterCallbackPost { + url: String, + }, + RequiredOctetStreamPut { + #[structopt(parse(try_from_str = parse_json))] + body: swagger::ByteArray, + }, + ResponsesWithHeadersGet { + }, + Rfc7807Get { + }, + TwoFirstLetterHeaders { + #[structopt(long)] + x_header_one: Option, + #[structopt(long)] + x_header_two: Option, + }, + UntypedPropertyGet { + #[structopt(parse(try_from_str = parse_json))] + object_untyped_props: Option, + }, + UuidGet { + }, + XmlExtraPost { + #[structopt(parse(try_from_str = parse_json))] + duplicate_xml_object: Option, + }, + XmlOtherPost { + #[structopt(parse(try_from_str = parse_json))] + another_xml_object: Option, + }, + XmlOtherPut { + #[structopt(parse(try_from_str = parse_json))] + another_xml_array: Option, + }, + /// Post an array. It's important we test apostrophes, so include one here. + XmlPost { + #[structopt(parse(try_from_str = parse_json))] + xml_array: Option, + }, + XmlPut { + #[structopt(parse(try_from_str = parse_json))] + xml_object: Option, + }, + EnumInPathPathParamGet { + #[structopt(parse(try_from_str = parse_json))] + path_param: models::StringEnum, + }, + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet { + path_param_a: String, + path_param_b: String, + }, + CreateRepo { + #[structopt(parse(try_from_str = parse_json))] + object_param: models::ObjectParam, + }, + GetRepoInfo { + repo_id: String, + }, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + if args.client_certificate.is_some() { + debug!("Using mutual TLS"); + let client = Client::try_new_https_mutual( + &args.server_address, + args.server_certificate.unwrap(), + args.client_key.unwrap(), + args.client_certificate.unwrap(), + ) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else if args.server_certificate.is_some() { + debug!("Using TLS with pinned server certificate"); + let client = + Client::try_new_https_pinned(&args.server_address, args.server_certificate.unwrap()) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else { + debug!("Using client without certificates"); + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) + } +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::from_args(); + if let Some(log_level) = args.verbosity.log_level() { + SimpleLogger::new().with_level(log_level.to_level_filter()).init()?; + } + + debug!("Arguments: {:?}", &args); + + let mut auth_data: Option = None; + + if let Some(ref bearer_token) = args.bearer_token { + debug!("Using bearer token"); + auth_data = Some(AuthData::bearer(bearer_token)); + } + + #[allow(trivial_casts)] + let context = swagger::make_context!( + ContextBuilder, + EmptyContext, + auth_data, + XSpanIdString::default() + ); + + let client = create_client(&args, context)?; + + let result = match args.operation { + Operation::AnyOfGet { + any_of, + } => { + info!("Performing a AnyOfGet request"); + + let result = client.any_of_get( + any_of.as_ref(), + ).await?; + debug!("Result: {:?}", result); + + match result { + AnyOfGetResponse::Success + (body) + => "Success\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + AnyOfGetResponse::AlternateSuccess + (body) + => "AlternateSuccess\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + AnyOfGetResponse::AnyOfSuccess + (body) + => "AnyOfSuccess\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::CallbackWithHeaderPost { + url, + } => { + info!("Performing a CallbackWithHeaderPost request"); + + let result = client.callback_with_header_post( + url, + ).await?; + debug!("Result: {:?}", result); + + match result { + CallbackWithHeaderPostResponse::OK + => "OK\n".to_string() + , + } + } + Operation::ComplexQueryParamGet { + list_of_strings, + } => { + info!("Performing a ComplexQueryParamGet request"); + + let result = client.complex_query_param_get( + list_of_strings.as_ref(), + ).await?; + debug!("Result: {:?}", result); + + match result { + ComplexQueryParamGetResponse::Success + => "Success\n".to_string() + , + } + } + Operation::GetWithBooleanParameter { + iambool, + } => { + info!("Performing a GetWithBooleanParameter request"); + + let result = client.get_with_boolean_parameter( + iambool, + ).await?; + debug!("Result: {:?}", result); + + match result { + GetWithBooleanParameterResponse::OK + => "OK\n".to_string() + , + } + } + Operation::JsonComplexQueryParamGet { + list_of_strings, + } => { + info!("Performing a JsonComplexQueryParamGet request"); + + let result = client.json_complex_query_param_get( + list_of_strings.as_ref(), + ).await?; + debug!("Result: {:?}", result); + + match result { + JsonComplexQueryParamGetResponse::Success + => "Success\n".to_string() + , + } + } + Operation::MandatoryRequestHeaderGet { + x_header, + } => { + info!("Performing a MandatoryRequestHeaderGet request"); + + let result = client.mandatory_request_header_get( + x_header, + ).await?; + debug!("Result: {:?}", result); + + match result { + MandatoryRequestHeaderGetResponse::Success + => "Success\n".to_string() + , + } + } + Operation::MergePatchJsonGet { + } => { + info!("Performing a MergePatchJsonGet request"); + + let result = client.merge_patch_json_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + MergePatchJsonGetResponse::Merge + (body) + => "Merge\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::MultigetGet { + } => { + info!("Performing a MultigetGet request"); + + let result = client.multiget_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + MultigetGetResponse::JSONRsp + (body) + => "JSONRsp\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + MultigetGetResponse::XMLRsp + (body) + => "XMLRsp\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + MultigetGetResponse::OctetRsp + (body) + => "OctetRsp\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + MultigetGetResponse::StringRsp + (body) + => "StringRsp\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + MultigetGetResponse::DuplicateResponseLongText + (body) + => "DuplicateResponseLongText\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + MultigetGetResponse::DuplicateResponseLongText_2 + (body) + => "DuplicateResponseLongText_2\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + MultigetGetResponse::DuplicateResponseLongText_3 + (body) + => "DuplicateResponseLongText_3\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::MultipleAuthSchemeGet { + } => { + info!("Performing a MultipleAuthSchemeGet request"); + + let result = client.multiple_auth_scheme_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + MultipleAuthSchemeGetResponse::CheckThatLimitingToMultipleRequiredAuthSchemesWorks + => "CheckThatLimitingToMultipleRequiredAuthSchemesWorks\n".to_string() + , + } + } + Operation::OneOfGet { + } => { + info!("Performing a OneOfGet request"); + + let result = client.one_of_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + OneOfGetResponse::Success + (body) + => "Success\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::OverrideServerGet { + } => { + info!("Performing a OverrideServerGet request"); + + let result = client.override_server_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + OverrideServerGetResponse::Success + => "Success\n".to_string() + , + } + } + Operation::ParamgetGet { + uuid, + some_object, + some_list, + } => { + info!("Performing a ParamgetGet request"); + + let result = client.paramget_get( + uuid, + some_object, + some_list, + ).await?; + debug!("Result: {:?}", result); + + match result { + ParamgetGetResponse::JSONRsp + (body) + => "JSONRsp\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::ReadonlyAuthSchemeGet { + } => { + info!("Performing a ReadonlyAuthSchemeGet request"); + + let result = client.readonly_auth_scheme_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + ReadonlyAuthSchemeGetResponse::CheckThatLimitingToASingleRequiredAuthSchemeWorks + => "CheckThatLimitingToASingleRequiredAuthSchemeWorks\n".to_string() + , + } + } + Operation::RegisterCallbackPost { + url, + } => { + info!("Performing a RegisterCallbackPost request"); + + let result = client.register_callback_post( + url, + ).await?; + debug!("Result: {:?}", result); + + match result { + RegisterCallbackPostResponse::OK + => "OK\n".to_string() + , + } + } + Operation::RequiredOctetStreamPut { + body, + } => { + info!("Performing a RequiredOctetStreamPut request"); + + let result = client.required_octet_stream_put( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + RequiredOctetStreamPutResponse::OK + => "OK\n".to_string() + , + } + } + Operation::ResponsesWithHeadersGet { + } => { + info!("Performing a ResponsesWithHeadersGet request"); + + let result = client.responses_with_headers_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + ResponsesWithHeadersGetResponse::Success + { + body, + success_info, + bool_header, + object_header, + } + => "Success\n".to_string() + + + &format!("body: {}\n", serde_json::to_string_pretty(&body)?) + + &format!( + "success_info: {}\n", + serde_json::to_string_pretty(&success_info)? + ) + + &format!( + "bool_header: {}\n", + serde_json::to_string_pretty(&bool_header)? + ) + + &format!( + "object_header: {}\n", + serde_json::to_string_pretty(&object_header)? + ), + ResponsesWithHeadersGetResponse::PreconditionFailed + { + further_info, + failure_info, + } + => "PreconditionFailed\n".to_string() + + + &format!( + "further_info: {}\n", + serde_json::to_string_pretty(&further_info)? + ) + + &format!( + "failure_info: {}\n", + serde_json::to_string_pretty(&failure_info)? + ), + } + } + Operation::Rfc7807Get { + } => { + info!("Performing a Rfc7807Get request"); + + let result = client.rfc7807_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Rfc7807GetResponse::OK + (body) + => "OK\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + Rfc7807GetResponse::NotFound + (body) + => "NotFound\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + Rfc7807GetResponse::NotAcceptable + (body) + => "NotAcceptable\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::TwoFirstLetterHeaders { + x_header_one, + x_header_two, + } => { + info!("Performing a TwoFirstLetterHeaders request"); + + let result = client.two_first_letter_headers( + x_header_one, + x_header_two, + ).await?; + debug!("Result: {:?}", result); + + match result { + TwoFirstLetterHeadersResponse::OK + => "OK\n".to_string() + , + } + } + Operation::UntypedPropertyGet { + object_untyped_props, + } => { + info!("Performing a UntypedPropertyGet request"); + + let result = client.untyped_property_get( + object_untyped_props, + ).await?; + debug!("Result: {:?}", result); + + match result { + UntypedPropertyGetResponse::CheckThatUntypedPropertiesWorks + => "CheckThatUntypedPropertiesWorks\n".to_string() + , + } + } + Operation::UuidGet { + } => { + info!("Performing a UuidGet request"); + + let result = client.uuid_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + UuidGetResponse::DuplicateResponseLongText + (body) + => "DuplicateResponseLongText\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::XmlExtraPost { + duplicate_xml_object, + } => { + info!("Performing a XmlExtraPost request"); + + let result = client.xml_extra_post( + duplicate_xml_object, + ).await?; + debug!("Result: {:?}", result); + + match result { + XmlExtraPostResponse::OK + => "OK\n".to_string() + , + XmlExtraPostResponse::BadRequest + => "BadRequest\n".to_string() + , + } + } + Operation::XmlOtherPost { + another_xml_object, + } => { + info!("Performing a XmlOtherPost request"); + + let result = client.xml_other_post( + another_xml_object, + ).await?; + debug!("Result: {:?}", result); + + match result { + XmlOtherPostResponse::OK + (body) + => "OK\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + XmlOtherPostResponse::BadRequest + => "BadRequest\n".to_string() + , + } + } + Operation::XmlOtherPut { + another_xml_array, + } => { + info!("Performing a XmlOtherPut request"); + + let result = client.xml_other_put( + another_xml_array, + ).await?; + debug!("Result: {:?}", result); + + match result { + XmlOtherPutResponse::OK + => "OK\n".to_string() + , + XmlOtherPutResponse::BadRequest + => "BadRequest\n".to_string() + , + } + } + Operation::XmlPost { + xml_array, + } => { + info!("Performing a XmlPost request"); + + let result = client.xml_post( + xml_array, + ).await?; + debug!("Result: {:?}", result); + + match result { + XmlPostResponse::OK + => "OK\n".to_string() + , + XmlPostResponse::BadRequest + => "BadRequest\n".to_string() + , + } + } + Operation::XmlPut { + xml_object, + } => { + info!("Performing a XmlPut request"); + + let result = client.xml_put( + xml_object, + ).await?; + debug!("Result: {:?}", result); + + match result { + XmlPutResponse::OK + => "OK\n".to_string() + , + XmlPutResponse::BadRequest + => "BadRequest\n".to_string() + , + } + } + Operation::EnumInPathPathParamGet { + path_param, + } => { + info!("Performing a EnumInPathPathParamGet request on {:?}", ( + &path_param + )); + + let result = client.enum_in_path_path_param_get( + path_param, + ).await?; + debug!("Result: {:?}", result); + + match result { + EnumInPathPathParamGetResponse::Success + => "Success\n".to_string() + , + } + } + Operation::MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet { + path_param_a, + path_param_b, + } => { + info!("Performing a MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet request on {:?}", ( + &path_param_a, + &path_param_b + )); + + let result = client.multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get( + path_param_a, + path_param_b, + ).await?; + debug!("Result: {:?}", result); + + match result { + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse::Success + => "Success\n".to_string() + , + } + } + Operation::CreateRepo { + object_param, + } => { + info!("Performing a CreateRepo request"); + + let result = client.create_repo( + object_param, + ).await?; + debug!("Result: {:?}", result); + + match result { + CreateRepoResponse::Success + => "Success\n".to_string() + , + } + } + Operation::GetRepoInfo { + repo_id, + } => { + info!("Performing a GetRepoInfo request on {:?}", ( + &repo_id + )); + + let result = client.get_repo_info( + repo_id, + ).await?; + debug!("Result: {:?}", result); + + match result { + GetRepoInfoResponse::OK + (body) + => "OK\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + }; + + if let Some(output_file) = args.output_file { + std::fs::write(output_file, result)? + } else { + println!("{}", result); + } + Ok(()) +} + +// May be unused if all inputs are primitive types +#[allow(dead_code)] +fn parse_json<'a, T: serde::de::Deserialize<'a>>(json_string: &'a str) -> Result { + serde_json::from_str(json_string).map_err(|err| anyhow!("Error parsing input: {}", err)) +} diff --git a/samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md b/samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md index e0897cd495b78..820bc2bb8ea90 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md +++ b/samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md @@ -7,6 +7,7 @@ Method | HTTP request | Description ****](default_api.md#) | **GET** /any-of | ****](default_api.md#) | **POST** /callback-with-header | ****](default_api.md#) | **GET** /complex-query-param | +**GetWithBooleanParameter**](default_api.md#GetWithBooleanParameter) | **GET** /get-with-bool | ****](default_api.md#) | **GET** /json-complex-query-param | ****](default_api.md#) | **GET** /mandatory-request-header | ****](default_api.md#) | **GET** /merge-patch-json | @@ -20,14 +21,16 @@ Method | HTTP request | Description ****](default_api.md#) | **PUT** /required_octet_stream | ****](default_api.md#) | **GET** /responses_with_headers | ****](default_api.md#) | **GET** /rfc7807 | +**TwoFirstLetterHeaders**](default_api.md#TwoFirstLetterHeaders) | **POST** /operation-two-first-letter-headers | ****](default_api.md#) | **GET** /untyped_property | ****](default_api.md#) | **GET** /uuid | ****](default_api.md#) | **POST** /xml_extra | ****](default_api.md#) | **POST** /xml_other | ****](default_api.md#) | **PUT** /xml_other | -****](default_api.md#) | **POST** /xml | Post an array +****](default_api.md#) | **POST** /xml | Post an array. It's important we test apostrophes, so include one here. ****](default_api.md#) | **PUT** /xml | ****](default_api.md#) | **GET** /enum_in_path/{path_param} | +****](default_api.md#) | **GET** /multiple-path-params-with-very-long-path-to-test-formatting/{path_param_a}/{path_param_b} | # **** @@ -119,6 +122,33 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **GetWithBooleanParameter** +> GetWithBooleanParameter(iambool) + + +Get with a boolean parameter + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **iambool** | **bool**| Let's check apostrophes get encoded properly! | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **** > (optional) @@ -436,6 +466,41 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **TwoFirstLetterHeaders** +> TwoFirstLetterHeaders(optional) + + +Check we don't barf if two boolean parameters have the same first letter + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **optional** | **map[string]interface{}** | optional parameters | nil if no parameters + +### Optional Parameters +Optional parameters are passed through a map[string]interface{}. + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **x_header_one** | **bool**| | + **x_header_two** | **bool**| | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **** > (optional) @@ -588,7 +653,7 @@ No authorization required # **** > (optional) -Post an array +Post an array. It's important we test apostrophes, so include one here. @@ -677,3 +742,29 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **** +> (path_param_a, path_param_b) + + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **path_param_a** | **String**| | + **path_param_b** | **String**| | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs b/samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs index 3a4b47067d396..01b71b583ba93 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs @@ -9,6 +9,7 @@ use openapi_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models, AnyOfGetResponse, CallbackWithHeaderPostResponse, ComplexQueryParamGetResponse, + GetWithBooleanParameterResponse, JsonComplexQueryParamGetResponse, MandatoryRequestHeaderGetResponse, MergePatchJsonGetResponse, @@ -22,6 +23,7 @@ use openapi_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models, RequiredOctetStreamPutResponse, ResponsesWithHeadersGetResponse, Rfc7807GetResponse, + TwoFirstLetterHeadersResponse, UntypedPropertyGetResponse, UuidGetResponse, XmlExtraPostResponse, @@ -30,6 +32,7 @@ use openapi_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models, XmlPostResponse, XmlPutResponse, EnumInPathPathParamGetResponse, + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse, CreateRepoResponse, GetRepoInfoResponse, }; @@ -63,6 +66,7 @@ fn main() { "AnyOfGet", "CallbackWithHeaderPost", "ComplexQueryParamGet", + "GetWithBooleanParameter", "JsonComplexQueryParamGet", "MandatoryRequestHeaderGet", "MergePatchJsonGet", @@ -76,6 +80,7 @@ fn main() { "RequiredOctetStreamPut", "ResponsesWithHeadersGet", "Rfc7807Get", + "TwoFirstLetterHeaders", "UntypedPropertyGet", "UuidGet", "XmlExtraPost", @@ -83,6 +88,7 @@ fn main() { "XmlOtherPut", "XmlPost", "XmlPut", + "MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet", "CreateRepo", "GetRepoInfo", ]) @@ -120,6 +126,8 @@ fn main() { [ "test.read", "test.write", + "additional.test.read", + "additional.test.write", ].join::<&str>(", ") }, b"secret").unwrap(); @@ -177,6 +185,12 @@ fn main() { )); info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has).get().clone()); }, + Some("GetWithBooleanParameter") => { + let result = rt.block_on(client.get_with_boolean_parameter( + true + )); + info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has).get().clone()); + }, Some("JsonComplexQueryParamGet") => { let result = rt.block_on(client.json_complex_query_param_get( Some(&Vec::new()) @@ -249,6 +263,13 @@ fn main() { )); info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has).get().clone()); }, + Some("TwoFirstLetterHeaders") => { + let result = rt.block_on(client.two_first_letter_headers( + Some(true), + Some(true) + )); + info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has).get().clone()); + }, Some("UntypedPropertyGet") => { let result = rt.block_on(client.untyped_property_get( None @@ -298,6 +319,13 @@ fn main() { info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has).get().clone()); }, */ + Some("MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet") => { + let result = rt.block_on(client.multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get( + "path_param_a_example".to_string(), + "path_param_b_example".to_string() + )); + info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has).get().clone()); + }, Some("CreateRepo") => { let result = rt.block_on(client.create_repo( serde_json::from_str::(r#"{"requiredParam":true}"#).expect("Failed to parse JSON example") diff --git a/samples/server/petstore/rust-server/output/openapi-v3/examples/server/server.rs b/samples/server/petstore/rust-server/output/openapi-v3/examples/server/server.rs index 7d1ea3e6973dc..0f86aae741c89 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/examples/server/server.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/examples/server/server.rs @@ -105,6 +105,7 @@ use openapi_v3::{ AnyOfGetResponse, CallbackWithHeaderPostResponse, ComplexQueryParamGetResponse, + GetWithBooleanParameterResponse, JsonComplexQueryParamGetResponse, MandatoryRequestHeaderGetResponse, MergePatchJsonGetResponse, @@ -118,6 +119,7 @@ use openapi_v3::{ RequiredOctetStreamPutResponse, ResponsesWithHeadersGetResponse, Rfc7807GetResponse, + TwoFirstLetterHeadersResponse, UntypedPropertyGetResponse, UuidGetResponse, XmlExtraPostResponse, @@ -126,6 +128,7 @@ use openapi_v3::{ XmlPostResponse, XmlPutResponse, EnumInPathPathParamGetResponse, + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse, CreateRepoResponse, GetRepoInfoResponse, }; @@ -163,6 +166,15 @@ impl Api for Server where C: Has + Send + Sync Err(ApiError("Api-Error: Operation is NOT implemented".into())) } + async fn get_with_boolean_parameter( + &self, + iambool: bool, + context: &C) -> Result + { + info!("get_with_boolean_parameter({}) - X-Span-ID: {:?}", iambool, context.get().0.clone()); + Err(ApiError("Api-Error: Operation is NOT implemented".into())) + } + async fn json_complex_query_param_get( &self, list_of_strings: Option<&Vec>, @@ -276,6 +288,16 @@ impl Api for Server where C: Has + Send + Sync Err(ApiError("Api-Error: Operation is NOT implemented".into())) } + async fn two_first_letter_headers( + &self, + x_header_one: Option, + x_header_two: Option, + context: &C) -> Result + { + info!("two_first_letter_headers({:?}, {:?}) - X-Span-ID: {:?}", x_header_one, x_header_two, context.get().0.clone()); + Err(ApiError("Api-Error: Operation is NOT implemented".into())) + } + async fn untyped_property_get( &self, object_untyped_props: Option, @@ -320,7 +342,7 @@ impl Api for Server where C: Has + Send + Sync Err(ApiError("Api-Error: Operation is NOT implemented".into())) } - /// Post an array + /// Post an array. It's important we test apostrophes, so include one here. async fn xml_post( &self, xml_array: Option, @@ -348,6 +370,16 @@ impl Api for Server where C: Has + Send + Sync Err(ApiError("Api-Error: Operation is NOT implemented".into())) } + async fn multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get( + &self, + path_param_a: String, + path_param_b: String, + context: &C) -> Result + { + info!("multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get(\"{}\", \"{}\") - X-Span-ID: {:?}", path_param_a, path_param_b, context.get().0.clone()); + Err(ApiError("Api-Error: Operation is NOT implemented".into())) + } + async fn create_repo( &self, object_param: models::ObjectParam, diff --git a/samples/server/petstore/rust-server/output/openapi-v3/examples/server/server_auth.rs b/samples/server/petstore/rust-server/output/openapi-v3/examples/server/server_auth.rs index 00f8adeeb6aa2..1c9b18394f212 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/examples/server/server_auth.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/examples/server/server_auth.rs @@ -27,6 +27,8 @@ fn full_permission_claim() -> Claims { [ "test.read", "test.write", + "additional.test.read", + "additional.test.write", ].join::<&str>(", ") } } diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs index f6c7c70359895..5087537615db9 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs @@ -39,6 +39,7 @@ use crate::{Api, AnyOfGetResponse, CallbackWithHeaderPostResponse, ComplexQueryParamGetResponse, + GetWithBooleanParameterResponse, JsonComplexQueryParamGetResponse, MandatoryRequestHeaderGetResponse, MergePatchJsonGetResponse, @@ -52,6 +53,7 @@ use crate::{Api, RequiredOctetStreamPutResponse, ResponsesWithHeadersGetResponse, Rfc7807GetResponse, + TwoFirstLetterHeadersResponse, UntypedPropertyGetResponse, UuidGetResponse, XmlExtraPostResponse, @@ -60,6 +62,7 @@ use crate::{Api, XmlPostResponse, XmlPutResponse, EnumInPathPathParamGetResponse, + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse, CreateRepoResponse, GetRepoInfoResponse }; @@ -667,6 +670,77 @@ impl Api for Client where } } + async fn get_with_boolean_parameter( + &self, + param_iambool: bool, + context: &C) -> Result + { + let mut client_service = self.client_service.clone(); + let mut uri = format!( + "{}/get-with-bool", + self.base_path + ); + + // Query parameters + let query_string = { + let mut query_string = form_urlencoded::Serializer::new("".to_owned()); + query_string.append_pair("iambool", + ¶m_iambool.to_string()); + query_string.finish() + }; + if !query_string.is_empty() { + uri += "?"; + uri += &query_string; + } + + let uri = match Uri::from_str(&uri) { + Ok(uri) => uri, + Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))), + }; + + let mut request = match Request::builder() + .method("GET") + .uri(uri) + .body(Body::empty()) { + Ok(req) => req, + Err(e) => return Err(ApiError(format!("Unable to create request: {}", e))) + }; + + let header = HeaderValue::from_str(Has::::get(context).0.as_str()); + request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header { + Ok(h) => h, + Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e))) + }); + + let response = client_service.call((request, context.clone())) + .map_err(|e| ApiError(format!("No response received: {}", e))).await?; + + match response.status().as_u16() { + 200 => { + Ok( + GetWithBooleanParameterResponse::OK + ) + } + code => { + let headers = response.headers().clone(); + let body = response.into_body() + .take(100) + .into_raw().await; + Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", + code, + headers, + match body { + Ok(body) => match String::from_utf8(body) { + Ok(body) => body, + Err(e) => format!("", e), + }, + Err(e) => format!("", e), + } + ))) + } + } + } + async fn json_complex_query_param_get( &self, param_list_of_strings: Option<&Vec>, @@ -1918,6 +1992,111 @@ impl Api for Client where } } + async fn two_first_letter_headers( + &self, + param_x_header_one: Option, + param_x_header_two: Option, + context: &C) -> Result + { + let mut client_service = self.client_service.clone(); + let mut uri = format!( + "{}/operation-two-first-letter-headers", + self.base_path + ); + + // Query parameters + let query_string = { + let mut query_string = form_urlencoded::Serializer::new("".to_owned()); + query_string.finish() + }; + if !query_string.is_empty() { + uri += "?"; + uri += &query_string; + } + + let uri = match Uri::from_str(&uri) { + Ok(uri) => uri, + Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))), + }; + + let mut request = match Request::builder() + .method("POST") + .uri(uri) + .body(Body::empty()) { + Ok(req) => req, + Err(e) => return Err(ApiError(format!("Unable to create request: {}", e))) + }; + + let header = HeaderValue::from_str(Has::::get(context).0.as_str()); + request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header { + Ok(h) => h, + Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e))) + }); + + // Header parameters + #[allow(clippy::single_match)] + match param_x_header_one { + Some(param_x_header_one) => { + request.headers_mut().append( + HeaderName::from_static("x-header-one"), + #[allow(clippy::redundant_clone)] + match header::IntoHeaderValue(param_x_header_one.clone()).try_into() { + Ok(header) => header, + Err(e) => { + return Err(ApiError(format!( + "Invalid header x_header_one - {}", e))); + }, + }); + }, + None => {} + } + + #[allow(clippy::single_match)] + match param_x_header_two { + Some(param_x_header_two) => { + request.headers_mut().append( + HeaderName::from_static("x-header-two"), + #[allow(clippy::redundant_clone)] + match header::IntoHeaderValue(param_x_header_two.clone()).try_into() { + Ok(header) => header, + Err(e) => { + return Err(ApiError(format!( + "Invalid header x_header_two - {}", e))); + }, + }); + }, + None => {} + } + + let response = client_service.call((request, context.clone())) + .map_err(|e| ApiError(format!("No response received: {}", e))).await?; + + match response.status().as_u16() { + 200 => { + Ok( + TwoFirstLetterHeadersResponse::OK + ) + } + code => { + let headers = response.headers().clone(); + let body = response.into_body() + .take(100) + .into_raw().await; + Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", + code, + headers, + match body { + Ok(body) => match String::from_utf8(body) { + Ok(body) => body, + Err(e) => format!("", e), + }, + Err(e) => format!("", e), + } + ))) + } + } + } + async fn untyped_property_get( &self, param_object_untyped_props: Option, @@ -2597,6 +2776,78 @@ impl Api for Client where } } + async fn multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get( + &self, + param_path_param_a: String, + param_path_param_b: String, + context: &C) -> Result + { + let mut client_service = self.client_service.clone(); + let mut uri = format!( + "{}/multiple-path-params-with-very-long-path-to-test-formatting/{path_param_a}/{path_param_b}", + self.base_path + ,path_param_a=utf8_percent_encode(¶m_path_param_a.to_string(), ID_ENCODE_SET) + ,path_param_b=utf8_percent_encode(¶m_path_param_b.to_string(), ID_ENCODE_SET) + ); + + // Query parameters + let query_string = { + let mut query_string = form_urlencoded::Serializer::new("".to_owned()); + query_string.finish() + }; + if !query_string.is_empty() { + uri += "?"; + uri += &query_string; + } + + let uri = match Uri::from_str(&uri) { + Ok(uri) => uri, + Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))), + }; + + let mut request = match Request::builder() + .method("GET") + .uri(uri) + .body(Body::empty()) { + Ok(req) => req, + Err(e) => return Err(ApiError(format!("Unable to create request: {}", e))) + }; + + let header = HeaderValue::from_str(Has::::get(context).0.as_str()); + request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header { + Ok(h) => h, + Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e))) + }); + + let response = client_service.call((request, context.clone())) + .map_err(|e| ApiError(format!("No response received: {}", e))).await?; + + match response.status().as_u16() { + 200 => { + Ok( + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse::Success + ) + } + code => { + let headers = response.headers().clone(); + let body = response.into_body() + .take(100) + .into_raw().await; + Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", + code, + headers, + match body { + Ok(body) => match String::from_utf8(body) { + Ok(body) => body, + Err(e) => format!("", e), + }, + Err(e) => format!("", e), + } + ))) + } + } + } + async fn create_repo( &self, param_object_param: models::ObjectParam, diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/context.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/context.rs index e01187ca3c821..2b3485f7bbe87 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/context.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/context.rs @@ -105,6 +105,25 @@ impl Service> for AddContext(headers) { + let authorization = self.inner.bearer_authorization(&bearer); + let auth_data = AuthData::Bearer(bearer); + + let context = context.push(Some(auth_data)); + let context = match authorization { + Ok(auth) => context.push(Some(auth)), + Err(err) => { + error!("Error during Authorization: {err:?}"); + context.push(None::) + } + }; + + return self.inner.call((request, context)) + } + } { use swagger::auth::Bearer; use std::ops::Deref; diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/lib.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/lib.rs index ed43bd647d314..3b1cdc5a4f6c1 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/lib.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/lib.rs @@ -48,6 +48,12 @@ pub enum ComplexQueryParamGetResponse { Success } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub enum GetWithBooleanParameterResponse { + /// OK + OK +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub enum JsonComplexQueryParamGetResponse { /// Success @@ -195,6 +201,12 @@ pub enum Rfc7807GetResponse { (models::ObjectWithArrayOfObjects) } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub enum TwoFirstLetterHeadersResponse { + /// OK + OK +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub enum UntypedPropertyGetResponse { /// Check that untyped properties works @@ -265,6 +277,12 @@ pub enum EnumInPathPathParamGetResponse { Success } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub enum MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse { + /// Success + Success +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub enum CreateRepoResponse { /// Success @@ -301,6 +319,11 @@ pub trait Api { list_of_strings: Option<&Vec>, context: &C) -> Result; + async fn get_with_boolean_parameter( + &self, + iambool: bool, + context: &C) -> Result; + async fn json_complex_query_param_get( &self, list_of_strings: Option<&Vec>, @@ -362,6 +385,12 @@ pub trait Api { &self, context: &C) -> Result; + async fn two_first_letter_headers( + &self, + x_header_one: Option, + x_header_two: Option, + context: &C) -> Result; + async fn untyped_property_get( &self, object_untyped_props: Option, @@ -386,7 +415,7 @@ pub trait Api { another_xml_array: Option, context: &C) -> Result; - /// Post an array + /// Post an array. It's important we test apostrophes, so include one here. async fn xml_post( &self, xml_array: Option, @@ -402,6 +431,12 @@ pub trait Api { path_param: models::StringEnum, context: &C) -> Result; + async fn multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get( + &self, + path_param_a: String, + path_param_b: String, + context: &C) -> Result; + async fn create_repo( &self, object_param: models::ObjectParam, @@ -438,6 +473,11 @@ pub trait ApiNoContext { list_of_strings: Option<&Vec>, ) -> Result; + async fn get_with_boolean_parameter( + &self, + iambool: bool, + ) -> Result; + async fn json_complex_query_param_get( &self, list_of_strings: Option<&Vec>, @@ -499,6 +539,12 @@ pub trait ApiNoContext { &self, ) -> Result; + async fn two_first_letter_headers( + &self, + x_header_one: Option, + x_header_two: Option, + ) -> Result; + async fn untyped_property_get( &self, object_untyped_props: Option, @@ -523,7 +569,7 @@ pub trait ApiNoContext { another_xml_array: Option, ) -> Result; - /// Post an array + /// Post an array. It's important we test apostrophes, so include one here. async fn xml_post( &self, xml_array: Option, @@ -539,6 +585,12 @@ pub trait ApiNoContext { path_param: models::StringEnum, ) -> Result; + async fn multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get( + &self, + path_param_a: String, + path_param_b: String, + ) -> Result; + async fn create_repo( &self, object_param: models::ObjectParam, @@ -601,6 +653,15 @@ impl + Send + Sync, C: Clone + Send + Sync> ApiNoContext for Contex self.api().complex_query_param_get(list_of_strings, &context).await } + async fn get_with_boolean_parameter( + &self, + iambool: bool, + ) -> Result + { + let context = self.context().clone(); + self.api().get_with_boolean_parameter(iambool, &context).await + } + async fn json_complex_query_param_get( &self, list_of_strings: Option<&Vec>, @@ -714,6 +775,16 @@ impl + Send + Sync, C: Clone + Send + Sync> ApiNoContext for Contex self.api().rfc7807_get(&context).await } + async fn two_first_letter_headers( + &self, + x_header_one: Option, + x_header_two: Option, + ) -> Result + { + let context = self.context().clone(); + self.api().two_first_letter_headers(x_header_one, x_header_two, &context).await + } + async fn untyped_property_get( &self, object_untyped_props: Option, @@ -758,7 +829,7 @@ impl + Send + Sync, C: Clone + Send + Sync> ApiNoContext for Contex self.api().xml_other_put(another_xml_array, &context).await } - /// Post an array + /// Post an array. It's important we test apostrophes, so include one here. async fn xml_post( &self, xml_array: Option, @@ -786,6 +857,16 @@ impl + Send + Sync, C: Clone + Send + Sync> ApiNoContext for Contex self.api().enum_in_path_path_param_get(path_param, &context).await } + async fn multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get( + &self, + path_param_a: String, + path_param_b: String, + ) -> Result + { + let context = self.context().clone(); + self.api().multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get(path_param_a, path_param_b, &context).await + } + async fn create_repo( &self, object_param: models::ObjectParam, diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs index 025004ed700e3..1b29a70d627c4 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs @@ -24,6 +24,7 @@ use crate::{Api, AnyOfGetResponse, CallbackWithHeaderPostResponse, ComplexQueryParamGetResponse, + GetWithBooleanParameterResponse, JsonComplexQueryParamGetResponse, MandatoryRequestHeaderGetResponse, MergePatchJsonGetResponse, @@ -37,6 +38,7 @@ use crate::{Api, RequiredOctetStreamPutResponse, ResponsesWithHeadersGetResponse, Rfc7807GetResponse, + TwoFirstLetterHeadersResponse, UntypedPropertyGetResponse, UuidGetResponse, XmlExtraPostResponse, @@ -45,6 +47,7 @@ use crate::{Api, XmlPostResponse, XmlPutResponse, EnumInPathPathParamGetResponse, + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse, CreateRepoResponse, GetRepoInfoResponse }; @@ -62,12 +65,15 @@ mod paths { r"^/callback-with-header$", r"^/complex-query-param$", r"^/enum_in_path/(?P[^/?#]*)$", + r"^/get-with-bool$", r"^/json-complex-query-param$", r"^/mandatory-request-header$", r"^/merge-patch-json$", r"^/multiget$", + r"^/multiple-path-params-with-very-long-path-to-test-formatting/(?P[^/?#]*)/(?P[^/?#]*)$", r"^/multiple_auth_scheme$", r"^/one-of$", + r"^/operation-two-first-letter-headers$", r"^/override-server$", r"^/paramget$", r"^/readonly_auth_scheme$", @@ -95,32 +101,41 @@ mod paths { regex::Regex::new(r"^/enum_in_path/(?P[^/?#]*)$") .expect("Unable to create regex for ENUM_IN_PATH_PATH_PARAM"); } - pub(crate) static ID_JSON_COMPLEX_QUERY_PARAM: usize = 4; - pub(crate) static ID_MANDATORY_REQUEST_HEADER: usize = 5; - pub(crate) static ID_MERGE_PATCH_JSON: usize = 6; - pub(crate) static ID_MULTIGET: usize = 7; - pub(crate) static ID_MULTIPLE_AUTH_SCHEME: usize = 8; - pub(crate) static ID_ONE_OF: usize = 9; - pub(crate) static ID_OVERRIDE_SERVER: usize = 10; - pub(crate) static ID_PARAMGET: usize = 11; - pub(crate) static ID_READONLY_AUTH_SCHEME: usize = 12; - pub(crate) static ID_REGISTER_CALLBACK: usize = 13; - pub(crate) static ID_REPOS: usize = 14; - pub(crate) static ID_REPOS_REPOID: usize = 15; + pub(crate) static ID_GET_WITH_BOOL: usize = 4; + pub(crate) static ID_JSON_COMPLEX_QUERY_PARAM: usize = 5; + pub(crate) static ID_MANDATORY_REQUEST_HEADER: usize = 6; + pub(crate) static ID_MERGE_PATCH_JSON: usize = 7; + pub(crate) static ID_MULTIGET: usize = 8; + pub(crate) static ID_MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B: usize = 9; + lazy_static! { + pub static ref REGEX_MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B: regex::Regex = + #[allow(clippy::invalid_regex)] + regex::Regex::new(r"^/multiple-path-params-with-very-long-path-to-test-formatting/(?P[^/?#]*)/(?P[^/?#]*)$") + .expect("Unable to create regex for MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B"); + } + pub(crate) static ID_MULTIPLE_AUTH_SCHEME: usize = 10; + pub(crate) static ID_ONE_OF: usize = 11; + pub(crate) static ID_OPERATION_TWO_FIRST_LETTER_HEADERS: usize = 12; + pub(crate) static ID_OVERRIDE_SERVER: usize = 13; + pub(crate) static ID_PARAMGET: usize = 14; + pub(crate) static ID_READONLY_AUTH_SCHEME: usize = 15; + pub(crate) static ID_REGISTER_CALLBACK: usize = 16; + pub(crate) static ID_REPOS: usize = 17; + pub(crate) static ID_REPOS_REPOID: usize = 18; lazy_static! { pub static ref REGEX_REPOS_REPOID: regex::Regex = #[allow(clippy::invalid_regex)] regex::Regex::new(r"^/repos/(?P[^/?#]*)$") .expect("Unable to create regex for REPOS_REPOID"); } - pub(crate) static ID_REQUIRED_OCTET_STREAM: usize = 16; - pub(crate) static ID_RESPONSES_WITH_HEADERS: usize = 17; - pub(crate) static ID_RFC7807: usize = 18; - pub(crate) static ID_UNTYPED_PROPERTY: usize = 19; - pub(crate) static ID_UUID: usize = 20; - pub(crate) static ID_XML: usize = 21; - pub(crate) static ID_XML_EXTRA: usize = 22; - pub(crate) static ID_XML_OTHER: usize = 23; + pub(crate) static ID_REQUIRED_OCTET_STREAM: usize = 19; + pub(crate) static ID_RESPONSES_WITH_HEADERS: usize = 20; + pub(crate) static ID_RFC7807: usize = 21; + pub(crate) static ID_UNTYPED_PROPERTY: usize = 22; + pub(crate) static ID_UUID: usize = 23; + pub(crate) static ID_XML: usize = 24; + pub(crate) static ID_XML_EXTRA: usize = 25; + pub(crate) static ID_XML_OTHER: usize = 26; } @@ -406,6 +421,64 @@ impl hyper::service::Service<(Request, C)> for Service where Ok(response) }, + // GetWithBooleanParameter - GET /get-with-bool + hyper::Method::GET if path.matched(paths::ID_GET_WITH_BOOL) => { + // Query parameters (note that non-required or collection query parameters will ignore garbage values, rather than causing a 400 response) + let query_params = form_urlencoded::parse(uri.query().unwrap_or_default().as_bytes()).collect::>(); + let param_iambool = query_params.iter().filter(|e| e.0 == "iambool").map(|e| e.1.clone()) + .next(); + let param_iambool = match param_iambool { + Some(param_iambool) => { + let param_iambool = + ::from_str + (¶m_iambool); + match param_iambool { + Ok(param_iambool) => Some(param_iambool), + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(format!("Couldn't parse query parameter iambool - doesn't match schema: {}", e))) + .expect("Unable to create Bad Request response for invalid query parameter iambool")), + } + }, + None => None, + }; + let param_iambool = match param_iambool { + Some(param_iambool) => param_iambool, + None => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from("Missing required query parameter iambool")) + .expect("Unable to create Bad Request response for missing query parameter iambool")), + }; + + let result = api_impl.get_with_boolean_parameter( + param_iambool, + &context + ).await; + let mut response = Response::new(Body::empty()); + response.headers_mut().insert( + HeaderName::from_static("x-span-id"), + HeaderValue::from_str((&context as &dyn Has).get().0.clone().as_str()) + .expect("Unable to create X-Span-ID header value")); + + match result { + Ok(rsp) => match rsp { + GetWithBooleanParameterResponse::OK + => { + *response.status_mut() = StatusCode::from_u16(200).expect("Unable to turn 200 into a StatusCode"); + + }, + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + *response.body_mut() = Body::from("An internal error occurred"); + }, + } + + Ok(response) + }, + // JsonComplexQueryParamGet - GET /json-complex-query-param hyper::Method::GET if path.matched(paths::ID_JSON_COMPLEX_QUERY_PARAM) => { // Query parameters (note that non-required or collection query parameters will ignore garbage values, rather than causing a 400 response) @@ -1261,6 +1334,76 @@ impl hyper::service::Service<(Request, C)> for Service where Ok(response) }, + // TwoFirstLetterHeaders - POST /operation-two-first-letter-headers + hyper::Method::POST if path.matched(paths::ID_OPERATION_TWO_FIRST_LETTER_HEADERS) => { + // Header parameters + let param_x_header_one = headers.get(HeaderName::from_static("x-header-one")); + + let param_x_header_one = match param_x_header_one { + Some(v) => match header::IntoHeaderValue::::try_from((*v).clone()) { + Ok(result) => + Some(result.0), + Err(err) => { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(format!("Invalid header x-header-one - {}", err))) + .expect("Unable to create Bad Request response for invalid header x-header-one")); + + }, + }, + None => { + None + } + }; + let param_x_header_two = headers.get(HeaderName::from_static("x-header-two")); + + let param_x_header_two = match param_x_header_two { + Some(v) => match header::IntoHeaderValue::::try_from((*v).clone()) { + Ok(result) => + Some(result.0), + Err(err) => { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(format!("Invalid header x-header-two - {}", err))) + .expect("Unable to create Bad Request response for invalid header x-header-two")); + + }, + }, + None => { + None + } + }; + + let result = api_impl.two_first_letter_headers( + param_x_header_one, + param_x_header_two, + &context + ).await; + let mut response = Response::new(Body::empty()); + response.headers_mut().insert( + HeaderName::from_static("x-span-id"), + HeaderValue::from_str((&context as &dyn Has).get().0.clone().as_str()) + .expect("Unable to create X-Span-ID header value")); + + match result { + Ok(rsp) => match rsp { + TwoFirstLetterHeadersResponse::OK + => { + *response.status_mut() = StatusCode::from_u16(200).expect("Unable to turn 200 into a StatusCode"); + + }, + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + *response.body_mut() = Body::from("An internal error occurred"); + }, + } + + Ok(response) + }, + // UntypedPropertyGet - GET /untyped_property hyper::Method::GET if path.matched(paths::ID_UNTYPED_PROPERTY) => { // Handle body parameters (note that non-required body parameters will ignore garbage @@ -1774,6 +1917,75 @@ impl hyper::service::Service<(Request, C)> for Service where Ok(response) }, + // MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet - GET /multiple-path-params-with-very-long-path-to-test-formatting/{path_param_a}/{path_param_b} + hyper::Method::GET if path.matched(paths::ID_MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B) => { + // Path parameters + let path: &str = uri.path(); + let path_params = + paths::REGEX_MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B + .captures(path) + .unwrap_or_else(|| + panic!("Path {} matched RE MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B in set but failed match against \"{}\"", path, paths::REGEX_MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B.as_str()) + ); + + let param_path_param_a = match percent_encoding::percent_decode(path_params["path_param_a"].as_bytes()).decode_utf8() { + Ok(param_path_param_a) => match param_path_param_a.parse::() { + Ok(param_path_param_a) => param_path_param_a, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(format!("Couldn't parse path parameter path_param_a: {}", e))) + .expect("Unable to create Bad Request response for invalid path parameter")), + }, + Err(_) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(format!("Couldn't percent-decode path parameter as UTF-8: {}", &path_params["path_param_a"]))) + .expect("Unable to create Bad Request response for invalid percent decode")) + }; + + let param_path_param_b = match percent_encoding::percent_decode(path_params["path_param_b"].as_bytes()).decode_utf8() { + Ok(param_path_param_b) => match param_path_param_b.parse::() { + Ok(param_path_param_b) => param_path_param_b, + Err(e) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(format!("Couldn't parse path parameter path_param_b: {}", e))) + .expect("Unable to create Bad Request response for invalid path parameter")), + }, + Err(_) => return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(format!("Couldn't percent-decode path parameter as UTF-8: {}", &path_params["path_param_b"]))) + .expect("Unable to create Bad Request response for invalid percent decode")) + }; + + let result = api_impl.multiple_path_params_with_very_long_path_to_test_formatting_path_param_a_path_param_b_get( + param_path_param_a, + param_path_param_b, + &context + ).await; + let mut response = Response::new(Body::empty()); + response.headers_mut().insert( + HeaderName::from_static("x-span-id"), + HeaderValue::from_str((&context as &dyn Has).get().0.clone().as_str()) + .expect("Unable to create X-Span-ID header value")); + + match result { + Ok(rsp) => match rsp { + MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGetResponse::Success + => { + *response.status_mut() = StatusCode::from_u16(200).expect("Unable to turn 200 into a StatusCode"); + + }, + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + *response.body_mut() = Body::from("An internal error occurred"); + }, + } + + Ok(response) + }, + // CreateRepo - POST /repos hyper::Method::POST if path.matched(paths::ID_REPOS) => { // Handle body parameters (note that non-required body parameters will ignore garbage @@ -1914,12 +2126,15 @@ impl hyper::service::Service<(Request, C)> for Service where _ if path.matched(paths::ID_CALLBACK_WITH_HEADER) => method_not_allowed(), _ if path.matched(paths::ID_COMPLEX_QUERY_PARAM) => method_not_allowed(), _ if path.matched(paths::ID_ENUM_IN_PATH_PATH_PARAM) => method_not_allowed(), + _ if path.matched(paths::ID_GET_WITH_BOOL) => method_not_allowed(), _ if path.matched(paths::ID_JSON_COMPLEX_QUERY_PARAM) => method_not_allowed(), _ if path.matched(paths::ID_MANDATORY_REQUEST_HEADER) => method_not_allowed(), _ if path.matched(paths::ID_MERGE_PATCH_JSON) => method_not_allowed(), _ if path.matched(paths::ID_MULTIGET) => method_not_allowed(), + _ if path.matched(paths::ID_MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B) => method_not_allowed(), _ if path.matched(paths::ID_MULTIPLE_AUTH_SCHEME) => method_not_allowed(), _ if path.matched(paths::ID_ONE_OF) => method_not_allowed(), + _ if path.matched(paths::ID_OPERATION_TWO_FIRST_LETTER_HEADERS) => method_not_allowed(), _ if path.matched(paths::ID_OVERRIDE_SERVER) => method_not_allowed(), _ if path.matched(paths::ID_PARAMGET) => method_not_allowed(), _ if path.matched(paths::ID_READONLY_AUTH_SCHEME) => method_not_allowed(), @@ -1958,6 +2173,8 @@ impl RequestParser for ApiRequestParser { hyper::Method::POST if path.matched(paths::ID_CALLBACK_WITH_HEADER) => Some("CallbackWithHeaderPost"), // ComplexQueryParamGet - GET /complex-query-param hyper::Method::GET if path.matched(paths::ID_COMPLEX_QUERY_PARAM) => Some("ComplexQueryParamGet"), + // GetWithBooleanParameter - GET /get-with-bool + hyper::Method::GET if path.matched(paths::ID_GET_WITH_BOOL) => Some("GetWithBooleanParameter"), // JsonComplexQueryParamGet - GET /json-complex-query-param hyper::Method::GET if path.matched(paths::ID_JSON_COMPLEX_QUERY_PARAM) => Some("JsonComplexQueryParamGet"), // MandatoryRequestHeaderGet - GET /mandatory-request-header @@ -1984,6 +2201,8 @@ impl RequestParser for ApiRequestParser { hyper::Method::GET if path.matched(paths::ID_RESPONSES_WITH_HEADERS) => Some("ResponsesWithHeadersGet"), // Rfc7807Get - GET /rfc7807 hyper::Method::GET if path.matched(paths::ID_RFC7807) => Some("Rfc7807Get"), + // TwoFirstLetterHeaders - POST /operation-two-first-letter-headers + hyper::Method::POST if path.matched(paths::ID_OPERATION_TWO_FIRST_LETTER_HEADERS) => Some("TwoFirstLetterHeaders"), // UntypedPropertyGet - GET /untyped_property hyper::Method::GET if path.matched(paths::ID_UNTYPED_PROPERTY) => Some("UntypedPropertyGet"), // UuidGet - GET /uuid @@ -2000,6 +2219,8 @@ impl RequestParser for ApiRequestParser { hyper::Method::PUT if path.matched(paths::ID_XML) => Some("XmlPut"), // EnumInPathPathParamGet - GET /enum_in_path/{path_param} hyper::Method::GET if path.matched(paths::ID_ENUM_IN_PATH_PATH_PARAM) => Some("EnumInPathPathParamGet"), + // MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet - GET /multiple-path-params-with-very-long-path-to-test-formatting/{path_param_a}/{path_param_b} + hyper::Method::GET if path.matched(paths::ID_MULTIPLE_PATH_PARAMS_WITH_VERY_LONG_PATH_TO_TEST_FORMATTING_PATH_PARAM_A_PATH_PARAM_B) => Some("MultiplePathParamsWithVeryLongPathToTestFormattingPathParamAPathParamBGet"), // CreateRepo - POST /repos hyper::Method::POST if path.matched(paths::ID_REPOS) => Some("CreateRepo"), // GetRepoInfo - GET /repos/{repoId} diff --git a/samples/server/petstore/rust-server/output/ops-v3/.openapi-generator/FILES b/samples/server/petstore/rust-server/output/ops-v3/.openapi-generator/FILES index f67b7ce47ca8a..ce2109b1de2e5 100644 --- a/samples/server/petstore/rust-server/output/ops-v3/.openapi-generator/FILES +++ b/samples/server/petstore/rust-server/output/ops-v3/.openapi-generator/FILES @@ -3,6 +3,7 @@ Cargo.toml README.md api/openapi.yaml +bin/cli.rs docs/default_api.md examples/ca.pem examples/client/client_auth.rs diff --git a/samples/server/petstore/rust-server/output/ops-v3/Cargo.toml b/samples/server/petstore/rust-server/output/ops-v3/Cargo.toml index 1ccfe1a2c98a2..fc19dc60a4822 100644 --- a/samples/server/petstore/rust-server/output/ops-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/ops-v3/Cargo.toml @@ -15,6 +15,9 @@ client = [ server = [ "serde_ignored", "hyper", "regex", "percent-encoding", "url", "lazy_static" ] +cli = [ + "anyhow", "clap-verbosity-flag", "simple_logger", "structopt", "tokio" +] conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] @@ -52,6 +55,13 @@ lazy_static = { version = "1.4", optional = true } percent-encoding = {version = "2.1.0", optional = true} regex = {version = "1.3", optional = true} +# CLI-specific +anyhow = { version = "1", optional = true } +clap-verbosity-flag = { version = "0.3", optional = true } +simple_logger = { version = "2.0", features = ["stderr"], optional = true } +structopt = { version = "0.3", optional = true } +tokio = { version = "0.2", features = ["rt-threaded", "macros", "stream"], optional = true } + # Conversion frunk = { version = "0.4.0", optional = true } frunk_derives = { version = "0.4.0", optional = true } @@ -79,3 +89,8 @@ required-features = ["client"] [[example]] name = "server" required-features = ["server"] + +[[bin]] +name = "ops-v3" +path = "bin/cli.rs" +required-features = ["client", "cli"] diff --git a/samples/server/petstore/rust-server/output/ops-v3/README.md b/samples/server/petstore/rust-server/output/ops-v3/README.md index 80b7daa6f6b8e..5e340f918ba28 100644 --- a/samples/server/petstore/rust-server/output/ops-v3/README.md +++ b/samples/server/petstore/rust-server/output/ops-v3/README.md @@ -23,6 +23,7 @@ This autogenerated project defines an API crate `ops-v3` which contains: * Data types representing the underlying data model. * A `Client` type which implements `Api` and issues HTTP requests for each operation. * A router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. +* A CLI tool to drive basic API operations from the command line. It also contains an example server and client which make use of `ops-v3`: @@ -36,6 +37,30 @@ It also contains an example server and client which make use of `ops-v3`: You can use the example server and client as a basis for your own code. See below for [more detail on the examples](#using-the-generated-library). +## CLI + +Run the included CLI tool with: + +``` +cargo run --bin cli --features=cli +``` + +To pass in arguments, put them after `--`, for example: + +``` +cargo run --bin cli --features=cli -- --help +``` + +See the help text for available options. + +To build a standalone tool, use: + +``` +cargo build --bin cli --features=cli --release +``` + +You'll find the binary at `target/release/cli`. + ## Examples Run examples with: @@ -122,6 +147,8 @@ The generated library has a few optional features that can be activated through * The constructed client implements the API trait by making remote API call. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. +* `cli` + * This defaults to disabled and is required for building the included CLI tool. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. diff --git a/samples/server/petstore/rust-server/output/ops-v3/bin/cli.rs b/samples/server/petstore/rust-server/output/ops-v3/bin/cli.rs new file mode 100644 index 0000000000000..13032815fd79f --- /dev/null +++ b/samples/server/petstore/rust-server/output/ops-v3/bin/cli.rs @@ -0,0 +1,762 @@ +//! CLI tool driving the API client +use anyhow::{anyhow, Context, Result}; +use log::{debug, info}; +// models may be unused if all inputs are primitive types +#[allow(unused_imports)] +use ops_v3::{ + models, ApiNoContext, Client, ContextWrapperExt, + Op10GetResponse, + Op11GetResponse, + Op12GetResponse, + Op13GetResponse, + Op14GetResponse, + Op15GetResponse, + Op16GetResponse, + Op17GetResponse, + Op18GetResponse, + Op19GetResponse, + Op1GetResponse, + Op20GetResponse, + Op21GetResponse, + Op22GetResponse, + Op23GetResponse, + Op24GetResponse, + Op25GetResponse, + Op26GetResponse, + Op27GetResponse, + Op28GetResponse, + Op29GetResponse, + Op2GetResponse, + Op30GetResponse, + Op31GetResponse, + Op32GetResponse, + Op33GetResponse, + Op34GetResponse, + Op35GetResponse, + Op36GetResponse, + Op37GetResponse, + Op3GetResponse, + Op4GetResponse, + Op5GetResponse, + Op6GetResponse, + Op7GetResponse, + Op8GetResponse, + Op9GetResponse, +}; +use simple_logger::SimpleLogger; +use structopt::StructOpt; +use swagger::{AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString}; + +type ClientContext = swagger::make_context_ty!( + ContextBuilder, + EmptyContext, + Option, + XSpanIdString +); + +#[derive(StructOpt, Debug)] +#[structopt( + name = "Regression test for large number of operations", + version = "0.0.1", + about = "CLI access to Regression test for large number of operations" +)] +struct Cli { + #[structopt(subcommand)] + operation: Operation, + + /// Address or hostname of the server hosting this API, including optional port + #[structopt(short = "a", long, default_value = "http://localhost")] + server_address: String, + + /// Path to the client private key if using client-side TLS authentication + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-certificate", "server-certificate"]))] + client_key: Option, + + /// Path to the client's public certificate associated with the private key + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-key", "server-certificate"]))] + client_certificate: Option, + + /// Path to CA certificate used to authenticate the server + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long)] + server_certificate: Option, + + /// If set, write output to file instead of stdout + #[structopt(short, long)] + output_file: Option, + + #[structopt(flatten)] + verbosity: clap_verbosity_flag::Verbosity, +} + +#[derive(StructOpt, Debug)] +enum Operation { + Op10Get { + }, + Op11Get { + }, + Op12Get { + }, + Op13Get { + }, + Op14Get { + }, + Op15Get { + }, + Op16Get { + }, + Op17Get { + }, + Op18Get { + }, + Op19Get { + }, + Op1Get { + }, + Op20Get { + }, + Op21Get { + }, + Op22Get { + }, + Op23Get { + }, + Op24Get { + }, + Op25Get { + }, + Op26Get { + }, + Op27Get { + }, + Op28Get { + }, + Op29Get { + }, + Op2Get { + }, + Op30Get { + }, + Op31Get { + }, + Op32Get { + }, + Op33Get { + }, + Op34Get { + }, + Op35Get { + }, + Op36Get { + }, + Op37Get { + }, + Op3Get { + }, + Op4Get { + }, + Op5Get { + }, + Op6Get { + }, + Op7Get { + }, + Op8Get { + }, + Op9Get { + }, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + if args.client_certificate.is_some() { + debug!("Using mutual TLS"); + let client = Client::try_new_https_mutual( + &args.server_address, + args.server_certificate.unwrap(), + args.client_key.unwrap(), + args.client_certificate.unwrap(), + ) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else if args.server_certificate.is_some() { + debug!("Using TLS with pinned server certificate"); + let client = + Client::try_new_https_pinned(&args.server_address, args.server_certificate.unwrap()) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else { + debug!("Using client without certificates"); + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) + } +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::from_args(); + if let Some(log_level) = args.verbosity.log_level() { + SimpleLogger::new().with_level(log_level.to_level_filter()).init()?; + } + + debug!("Arguments: {:?}", &args); + + let auth_data: Option = None; + + #[allow(trivial_casts)] + let context = swagger::make_context!( + ContextBuilder, + EmptyContext, + auth_data, + XSpanIdString::default() + ); + + let client = create_client(&args, context)?; + + let result = match args.operation { + Operation::Op10Get { + } => { + info!("Performing a Op10Get request"); + + let result = client.op10_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op10GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op11Get { + } => { + info!("Performing a Op11Get request"); + + let result = client.op11_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op11GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op12Get { + } => { + info!("Performing a Op12Get request"); + + let result = client.op12_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op12GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op13Get { + } => { + info!("Performing a Op13Get request"); + + let result = client.op13_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op13GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op14Get { + } => { + info!("Performing a Op14Get request"); + + let result = client.op14_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op14GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op15Get { + } => { + info!("Performing a Op15Get request"); + + let result = client.op15_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op15GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op16Get { + } => { + info!("Performing a Op16Get request"); + + let result = client.op16_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op16GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op17Get { + } => { + info!("Performing a Op17Get request"); + + let result = client.op17_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op17GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op18Get { + } => { + info!("Performing a Op18Get request"); + + let result = client.op18_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op18GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op19Get { + } => { + info!("Performing a Op19Get request"); + + let result = client.op19_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op19GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op1Get { + } => { + info!("Performing a Op1Get request"); + + let result = client.op1_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op1GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op20Get { + } => { + info!("Performing a Op20Get request"); + + let result = client.op20_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op20GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op21Get { + } => { + info!("Performing a Op21Get request"); + + let result = client.op21_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op21GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op22Get { + } => { + info!("Performing a Op22Get request"); + + let result = client.op22_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op22GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op23Get { + } => { + info!("Performing a Op23Get request"); + + let result = client.op23_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op23GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op24Get { + } => { + info!("Performing a Op24Get request"); + + let result = client.op24_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op24GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op25Get { + } => { + info!("Performing a Op25Get request"); + + let result = client.op25_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op25GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op26Get { + } => { + info!("Performing a Op26Get request"); + + let result = client.op26_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op26GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op27Get { + } => { + info!("Performing a Op27Get request"); + + let result = client.op27_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op27GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op28Get { + } => { + info!("Performing a Op28Get request"); + + let result = client.op28_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op28GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op29Get { + } => { + info!("Performing a Op29Get request"); + + let result = client.op29_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op29GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op2Get { + } => { + info!("Performing a Op2Get request"); + + let result = client.op2_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op2GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op30Get { + } => { + info!("Performing a Op30Get request"); + + let result = client.op30_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op30GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op31Get { + } => { + info!("Performing a Op31Get request"); + + let result = client.op31_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op31GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op32Get { + } => { + info!("Performing a Op32Get request"); + + let result = client.op32_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op32GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op33Get { + } => { + info!("Performing a Op33Get request"); + + let result = client.op33_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op33GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op34Get { + } => { + info!("Performing a Op34Get request"); + + let result = client.op34_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op34GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op35Get { + } => { + info!("Performing a Op35Get request"); + + let result = client.op35_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op35GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op36Get { + } => { + info!("Performing a Op36Get request"); + + let result = client.op36_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op36GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op37Get { + } => { + info!("Performing a Op37Get request"); + + let result = client.op37_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op37GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op3Get { + } => { + info!("Performing a Op3Get request"); + + let result = client.op3_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op3GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op4Get { + } => { + info!("Performing a Op4Get request"); + + let result = client.op4_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op4GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op5Get { + } => { + info!("Performing a Op5Get request"); + + let result = client.op5_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op5GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op6Get { + } => { + info!("Performing a Op6Get request"); + + let result = client.op6_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op6GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op7Get { + } => { + info!("Performing a Op7Get request"); + + let result = client.op7_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op7GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op8Get { + } => { + info!("Performing a Op8Get request"); + + let result = client.op8_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op8GetResponse::OK + => "OK\n".to_string() + , + } + } + Operation::Op9Get { + } => { + info!("Performing a Op9Get request"); + + let result = client.op9_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + Op9GetResponse::OK + => "OK\n".to_string() + , + } + } + }; + + if let Some(output_file) = args.output_file { + std::fs::write(output_file, result)? + } else { + println!("{}", result); + } + Ok(()) +} + +// May be unused if all inputs are primitive types +#[allow(dead_code)] +fn parse_json<'a, T: serde::de::Deserialize<'a>>(json_string: &'a str) -> Result { + serde_json::from_str(json_string).map_err(|err| anyhow!("Error parsing input: {}", err)) +} diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/FILES b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/FILES index d72d04fabfab1..84b057bc55518 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/FILES +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/FILES @@ -3,6 +3,7 @@ Cargo.toml README.md api/openapi.yaml +bin/cli.rs docs/AdditionalPropertiesClass.md docs/Animal.md docs/AnimalFarm.md diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml index 2005c6c81d40c..db8bccb72c4f9 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml @@ -20,6 +20,10 @@ server = [ "multipart", "multipart/server", "swagger/multipart_form", "serde_ignored", "hyper", "regex", "percent-encoding", "url", "lazy_static" ] +cli = [ + "dialoguer", + "anyhow", "clap-verbosity-flag", "simple_logger", "structopt", "tokio" +] conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] @@ -64,6 +68,14 @@ lazy_static = { version = "1.4", optional = true } percent-encoding = {version = "2.1.0", optional = true} regex = {version = "1.3", optional = true} +# CLI-specific +anyhow = { version = "1", optional = true } +clap-verbosity-flag = { version = "0.3", optional = true } +simple_logger = { version = "2.0", features = ["stderr"], optional = true } +structopt = { version = "0.3", optional = true } +tokio = { version = "0.2", features = ["rt-threaded", "macros", "stream"], optional = true } +dialoguer = { version = "0.8", optional = true } + # Conversion frunk = { version = "0.4.0", optional = true } frunk_derives = { version = "0.4.0", optional = true } @@ -91,3 +103,8 @@ required-features = ["client"] [[example]] name = "server" required-features = ["server"] + +[[bin]] +name = "petstore-with-fake-endpoints-models-for-testing" +path = "bin/cli.rs" +required-features = ["client", "cli"] diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/README.md b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/README.md index 459405c67b5a3..46d45740d4997 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/README.md +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/README.md @@ -23,6 +23,7 @@ This autogenerated project defines an API crate `petstore-with-fake-endpoints-mo * Data types representing the underlying data model. * A `Client` type which implements `Api` and issues HTTP requests for each operation. * A router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. +* A CLI tool to drive basic API operations from the command line. It also contains an example server and client which make use of `petstore-with-fake-endpoints-models-for-testing`: @@ -36,6 +37,30 @@ It also contains an example server and client which make use of `petstore-with-f You can use the example server and client as a basis for your own code. See below for [more detail on the examples](#using-the-generated-library). +## CLI + +Run the included CLI tool with: + +``` +cargo run --bin cli --features=cli +``` + +To pass in arguments, put them after `--`, for example: + +``` +cargo run --bin cli --features=cli -- --help +``` + +See the help text for available options. + +To build a standalone tool, use: + +``` +cargo build --bin cli --features=cli --release +``` + +You'll find the binary at `target/release/cli`. + ## Examples Run examples with: @@ -110,6 +135,8 @@ The generated library has a few optional features that can be activated through * The constructed client implements the API trait by making remote API call. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. +* `cli` + * This defaults to disabled and is required for building the included CLI tool. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs new file mode 100644 index 0000000000000..eabb8d602d752 --- /dev/null +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs @@ -0,0 +1,1152 @@ +//! CLI tool driving the API client +use anyhow::{anyhow, Context, Result}; +use dialoguer::Confirm; +use log::{debug, info}; +// models may be unused if all inputs are primitive types +#[allow(unused_imports)] +use petstore_with_fake_endpoints_models_for_testing::{ + models, ApiNoContext, Client, ContextWrapperExt, + TestSpecialTagsResponse, + Call123exampleResponse, + FakeOuterBooleanSerializeResponse, + FakeOuterCompositeSerializeResponse, + FakeOuterNumberSerializeResponse, + FakeOuterStringSerializeResponse, + FakeResponseWithNumericalDescriptionResponse, + TestBodyWithQueryParamsResponse, + TestClientModelResponse, + TestEndpointParametersResponse, + TestEnumParametersResponse, + TestInlineAdditionalPropertiesResponse, + TestJsonFormDataResponse, + HyphenParamResponse, + TestClassnameResponse, + AddPetResponse, + FindPetsByStatusResponse, + FindPetsByTagsResponse, + UpdatePetResponse, + DeletePetResponse, + GetPetByIdResponse, + UpdatePetWithFormResponse, + UploadFileResponse, + GetInventoryResponse, + PlaceOrderResponse, + DeleteOrderResponse, + GetOrderByIdResponse, + CreateUserResponse, + CreateUsersWithArrayInputResponse, + CreateUsersWithListInputResponse, + LoginUserResponse, + LogoutUserResponse, + DeleteUserResponse, + GetUserByNameResponse, + UpdateUserResponse, +}; +use simple_logger::SimpleLogger; +use structopt::StructOpt; +use swagger::{AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString}; + +type ClientContext = swagger::make_context_ty!( + ContextBuilder, + EmptyContext, + Option, + XSpanIdString +); + +#[derive(StructOpt, Debug)] +#[structopt( + name = "OpenAPI Petstore", + version = "1.0.0", + about = "CLI access to OpenAPI Petstore" +)] +struct Cli { + #[structopt(subcommand)] + operation: Operation, + + /// Address or hostname of the server hosting this API, including optional port + #[structopt(short = "a", long, default_value = "http://localhost")] + server_address: String, + + /// Path to the client private key if using client-side TLS authentication + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-certificate", "server-certificate"]))] + client_key: Option, + + /// Path to the client's public certificate associated with the private key + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-key", "server-certificate"]))] + client_certificate: Option, + + /// Path to CA certificate used to authenticate the server + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long)] + server_certificate: Option, + + /// If set, write output to file instead of stdout + #[structopt(short, long)] + output_file: Option, + + #[structopt(flatten)] + verbosity: clap_verbosity_flag::Verbosity, + + /// Don't ask for any confirmation prompts + #[allow(dead_code)] + #[structopt(short, long)] + force: bool, + + /// Bearer token if used for authentication + #[structopt(env = "PETSTORE_WITH_FAKE_ENDPOINTS_MODELS_FOR_TESTING_BEARER_TOKEN", hide_env_values = true)] + bearer_token: Option, +} + +#[derive(StructOpt, Debug)] +enum Operation { + /// To test special tags + TestSpecialTags { + /// client model + #[structopt(parse(try_from_str = parse_json))] + body: models::Client, + }, + Call123example { + }, + FakeOuterBooleanSerialize { + /// Input boolean as post body + #[structopt(parse(try_from_str = parse_json))] + body: Option, + }, + FakeOuterCompositeSerialize { + /// Input composite as post body + #[structopt(parse(try_from_str = parse_json))] + body: Option, + }, + FakeOuterNumberSerialize { + /// Input number as post body + #[structopt(parse(try_from_str = parse_json))] + body: Option, + }, + FakeOuterStringSerialize { + /// Input string as post body + #[structopt(parse(try_from_str = parse_json))] + body: Option, + }, + FakeResponseWithNumericalDescription { + }, + TestBodyWithQueryParams { + query: String, + #[structopt(parse(try_from_str = parse_json))] + body: models::User, + }, + /// To test \"client\" model + TestClientModel { + /// client model + #[structopt(parse(try_from_str = parse_json))] + body: models::Client, + }, + /// Fake endpoint for testing various parameters 假端點 偽のエンドポイント 가짜 엔드 포인트 + TestEndpointParameters { + /// None + number: f64, + /// None + double: f64, + /// None + pattern_without_delimiter: String, + /// None + #[structopt(parse(try_from_str = parse_json))] + byte: swagger::ByteArray, + /// None + integer: Option, + /// None + int32: Option, + /// None + int64: Option, + /// None + float: Option, + /// None + string: Option, + /// None + #[structopt(parse(try_from_str = parse_json))] + binary: Option, + /// None + date: Option, + /// None + date_time: Option>, + /// None + password: Option, + /// None + callback: Option, + }, + /// To test enum parameters + TestEnumParameters { + /// Header parameter enum test (string array) + #[structopt(parse(try_from_str = parse_json), long)] + enum_header_string_array: Option>, + /// Header parameter enum test (string) + enum_header_string: Option, + /// Query parameter enum test (string array) + #[structopt(parse(try_from_str = parse_json), long)] + enum_query_string_array: Option>, + /// Query parameter enum test (string) + enum_query_string: Option, + /// Query parameter enum test (double) + enum_query_integer: Option, + /// Query parameter enum test (double) + enum_query_double: Option, + /// Form parameter enum test (string) + enum_form_string: Option, + }, + /// test inline additionalProperties + TestInlineAdditionalProperties { + /// request body + #[structopt(parse(try_from_str = parse_json))] + param: std::collections::HashMap, + }, + /// test json serialization of form data + TestJsonFormData { + /// field1 + param: String, + /// field2 + param2: String, + }, + HyphenParam { + /// Parameter with hyphen in name + hyphen_param: String, + }, + /// To test class name in snake case + TestClassname { + /// client model + #[structopt(parse(try_from_str = parse_json))] + body: models::Client, + }, + /// Add a new pet to the store + AddPet { + /// Pet object that needs to be added to the store + #[structopt(parse(try_from_str = parse_json))] + body: models::Pet, + }, + /// Finds Pets by status + FindPetsByStatus { + /// Status values that need to be considered for filter + #[structopt(parse(try_from_str = parse_json), long)] + status: Vec, + }, + /// Finds Pets by tags + FindPetsByTags { + /// Tags to filter by + #[structopt(parse(try_from_str = parse_json), long)] + tags: Vec, + }, + /// Update an existing pet + UpdatePet { + /// Pet object that needs to be added to the store + #[structopt(parse(try_from_str = parse_json))] + body: models::Pet, + }, + /// Deletes a pet + DeletePet { + /// Pet id to delete + pet_id: i64, + api_key: Option, + }, + /// Find pet by ID + GetPetById { + /// ID of pet to return + pet_id: i64, + }, + /// Updates a pet in the store with form data + UpdatePetWithForm { + /// ID of pet that needs to be updated + pet_id: i64, + /// Updated name of the pet + name: Option, + /// Updated status of the pet + status: Option, + }, + /// uploads an image + UploadFile { + /// ID of pet to update + pet_id: i64, + /// Additional data to pass to server + additional_metadata: Option, + /// file to upload + #[structopt(parse(try_from_str = parse_json))] + file: Option, + }, + /// Returns pet inventories by status + GetInventory { + }, + /// Place an order for a pet + PlaceOrder { + /// order placed for purchasing the pet + #[structopt(parse(try_from_str = parse_json))] + body: models::Order, + }, + /// Delete purchase order by ID + DeleteOrder { + /// ID of the order that needs to be deleted + order_id: String, + }, + /// Find purchase order by ID + GetOrderById { + /// ID of pet that needs to be fetched + order_id: i64, + }, + /// Create user + CreateUser { + /// Created user object + #[structopt(parse(try_from_str = parse_json))] + body: models::User, + }, + /// Creates list of users with given input array + CreateUsersWithArrayInput { + /// List of user object + #[structopt(parse(try_from_str = parse_json), long)] + body: Vec, + }, + /// Creates list of users with given input array + CreateUsersWithListInput { + /// List of user object + #[structopt(parse(try_from_str = parse_json), long)] + body: Vec, + }, + /// Logs user into the system + LoginUser { + /// The user name for login + username: String, + /// The password for login in clear text + password: String, + }, + /// Logs out current logged in user session + LogoutUser { + }, + /// Delete user + DeleteUser { + /// The name that needs to be deleted + username: String, + }, + /// Get user by user name + GetUserByName { + /// The name that needs to be fetched. Use user1 for testing. + username: String, + }, + /// Updated user + UpdateUser { + /// name that need to be deleted + username: String, + /// Updated user object + #[structopt(parse(try_from_str = parse_json))] + body: models::User, + }, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + if args.client_certificate.is_some() { + debug!("Using mutual TLS"); + let client = Client::try_new_https_mutual( + &args.server_address, + args.server_certificate.unwrap(), + args.client_key.unwrap(), + args.client_certificate.unwrap(), + ) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else if args.server_certificate.is_some() { + debug!("Using TLS with pinned server certificate"); + let client = + Client::try_new_https_pinned(&args.server_address, args.server_certificate.unwrap()) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else { + debug!("Using client without certificates"); + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) + } +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::from_args(); + if let Some(log_level) = args.verbosity.log_level() { + SimpleLogger::new().with_level(log_level.to_level_filter()).init()?; + } + + debug!("Arguments: {:?}", &args); + + let mut auth_data: Option = None; + + if let Some(ref bearer_token) = args.bearer_token { + debug!("Using bearer token"); + auth_data = Some(AuthData::bearer(bearer_token)); + } + + #[allow(trivial_casts)] + let context = swagger::make_context!( + ContextBuilder, + EmptyContext, + auth_data, + XSpanIdString::default() + ); + + let client = create_client(&args, context)?; + + let result = match args.operation { + Operation::TestSpecialTags { + body, + } => { + info!("Performing a TestSpecialTags request"); + + let result = client.test_special_tags( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + TestSpecialTagsResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::Call123example { + } => { + info!("Performing a Call123example request"); + + let result = client.call123example( + ).await?; + debug!("Result: {:?}", result); + + match result { + Call123exampleResponse::Success + => "Success\n".to_string() + , + } + } + Operation::FakeOuterBooleanSerialize { + body, + } => { + info!("Performing a FakeOuterBooleanSerialize request"); + + let result = client.fake_outer_boolean_serialize( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + FakeOuterBooleanSerializeResponse::OutputBoolean + (body) + => "OutputBoolean\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::FakeOuterCompositeSerialize { + body, + } => { + info!("Performing a FakeOuterCompositeSerialize request"); + + let result = client.fake_outer_composite_serialize( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + FakeOuterCompositeSerializeResponse::OutputComposite + (body) + => "OutputComposite\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::FakeOuterNumberSerialize { + body, + } => { + info!("Performing a FakeOuterNumberSerialize request"); + + let result = client.fake_outer_number_serialize( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + FakeOuterNumberSerializeResponse::OutputNumber + (body) + => "OutputNumber\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::FakeOuterStringSerialize { + body, + } => { + info!("Performing a FakeOuterStringSerialize request"); + + let result = client.fake_outer_string_serialize( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + FakeOuterStringSerializeResponse::OutputString + (body) + => "OutputString\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::FakeResponseWithNumericalDescription { + } => { + info!("Performing a FakeResponseWithNumericalDescription request"); + + let result = client.fake_response_with_numerical_description( + ).await?; + debug!("Result: {:?}", result); + + match result { + FakeResponseWithNumericalDescriptionResponse::Status200 + => "Status200\n".to_string() + , + } + } + Operation::TestBodyWithQueryParams { + query, + body, + } => { + info!("Performing a TestBodyWithQueryParams request"); + + let result = client.test_body_with_query_params( + query, + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + TestBodyWithQueryParamsResponse::Success + => "Success\n".to_string() + , + } + } + Operation::TestClientModel { + body, + } => { + info!("Performing a TestClientModel request"); + + let result = client.test_client_model( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + TestClientModelResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::TestEndpointParameters { + number, + double, + pattern_without_delimiter, + byte, + integer, + int32, + int64, + float, + string, + binary, + date, + date_time, + password, + callback, + } => { + info!("Performing a TestEndpointParameters request"); + + let result = client.test_endpoint_parameters( + number, + double, + pattern_without_delimiter, + byte, + integer, + int32, + int64, + float, + string, + binary, + date, + date_time, + password, + callback, + ).await?; + debug!("Result: {:?}", result); + + match result { + TestEndpointParametersResponse::InvalidUsernameSupplied + => "InvalidUsernameSupplied\n".to_string() + , + TestEndpointParametersResponse::UserNotFound + => "UserNotFound\n".to_string() + , + } + } + Operation::TestEnumParameters { + enum_header_string_array, + enum_header_string, + enum_query_string_array, + enum_query_string, + enum_query_integer, + enum_query_double, + enum_form_string, + } => { + info!("Performing a TestEnumParameters request"); + + let result = client.test_enum_parameters( + enum_header_string_array.as_ref(), + enum_header_string, + enum_query_string_array.as_ref(), + enum_query_string, + enum_query_integer, + enum_query_double, + enum_form_string, + ).await?; + debug!("Result: {:?}", result); + + match result { + TestEnumParametersResponse::InvalidRequest + => "InvalidRequest\n".to_string() + , + TestEnumParametersResponse::NotFound + => "NotFound\n".to_string() + , + } + } + Operation::TestInlineAdditionalProperties { + param, + } => { + info!("Performing a TestInlineAdditionalProperties request"); + + let result = client.test_inline_additional_properties( + param, + ).await?; + debug!("Result: {:?}", result); + + match result { + TestInlineAdditionalPropertiesResponse::SuccessfulOperation + => "SuccessfulOperation\n".to_string() + , + } + } + Operation::TestJsonFormData { + param, + param2, + } => { + info!("Performing a TestJsonFormData request"); + + let result = client.test_json_form_data( + param, + param2, + ).await?; + debug!("Result: {:?}", result); + + match result { + TestJsonFormDataResponse::SuccessfulOperation + => "SuccessfulOperation\n".to_string() + , + } + } + Operation::HyphenParam { + hyphen_param, + } => { + info!("Performing a HyphenParam request on {:?}", ( + &hyphen_param + )); + + let result = client.hyphen_param( + hyphen_param, + ).await?; + debug!("Result: {:?}", result); + + match result { + HyphenParamResponse::Success + => "Success\n".to_string() + , + } + } + Operation::TestClassname { + body, + } => { + info!("Performing a TestClassname request"); + + let result = client.test_classname( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + TestClassnameResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::AddPet { + body, + } => { + info!("Performing a AddPet request"); + + let result = client.add_pet( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + AddPetResponse::InvalidInput + => "InvalidInput\n".to_string() + , + } + } + Operation::FindPetsByStatus { + status, + } => { + info!("Performing a FindPetsByStatus request"); + + let result = client.find_pets_by_status( + status.as_ref(), + ).await?; + debug!("Result: {:?}", result); + + match result { + FindPetsByStatusResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + FindPetsByStatusResponse::InvalidStatusValue + => "InvalidStatusValue\n".to_string() + , + } + } + Operation::FindPetsByTags { + tags, + } => { + info!("Performing a FindPetsByTags request"); + + let result = client.find_pets_by_tags( + tags.as_ref(), + ).await?; + debug!("Result: {:?}", result); + + match result { + FindPetsByTagsResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + FindPetsByTagsResponse::InvalidTagValue + => "InvalidTagValue\n".to_string() + , + } + } + Operation::UpdatePet { + body, + } => { + info!("Performing a UpdatePet request"); + + let result = client.update_pet( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + UpdatePetResponse::InvalidIDSupplied + => "InvalidIDSupplied\n".to_string() + , + UpdatePetResponse::PetNotFound + => "PetNotFound\n".to_string() + , + UpdatePetResponse::ValidationException + => "ValidationException\n".to_string() + , + } + } + Operation::DeletePet { + pet_id, + api_key, + } => { + prompt(args.force, "This will delete the given entry, are you sure?")?; + info!("Performing a DeletePet request on {:?}", ( + &pet_id + )); + + let result = client.delete_pet( + pet_id, + api_key, + ).await?; + debug!("Result: {:?}", result); + + match result { + DeletePetResponse::InvalidPetValue + => "InvalidPetValue\n".to_string() + , + } + } + Operation::GetPetById { + pet_id, + } => { + info!("Performing a GetPetById request on {:?}", ( + &pet_id + )); + + let result = client.get_pet_by_id( + pet_id, + ).await?; + debug!("Result: {:?}", result); + + match result { + GetPetByIdResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + GetPetByIdResponse::InvalidIDSupplied + => "InvalidIDSupplied\n".to_string() + , + GetPetByIdResponse::PetNotFound + => "PetNotFound\n".to_string() + , + } + } + Operation::UpdatePetWithForm { + pet_id, + name, + status, + } => { + info!("Performing a UpdatePetWithForm request on {:?}", ( + &pet_id + )); + + let result = client.update_pet_with_form( + pet_id, + name, + status, + ).await?; + debug!("Result: {:?}", result); + + match result { + UpdatePetWithFormResponse::InvalidInput + => "InvalidInput\n".to_string() + , + } + } + Operation::UploadFile { + pet_id, + additional_metadata, + file, + } => { + info!("Performing a UploadFile request on {:?}", ( + &pet_id + )); + + let result = client.upload_file( + pet_id, + additional_metadata, + file, + ).await?; + debug!("Result: {:?}", result); + + match result { + UploadFileResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::GetInventory { + } => { + info!("Performing a GetInventory request"); + + let result = client.get_inventory( + ).await?; + debug!("Result: {:?}", result); + + match result { + GetInventoryResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::PlaceOrder { + body, + } => { + info!("Performing a PlaceOrder request"); + + let result = client.place_order( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + PlaceOrderResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + PlaceOrderResponse::InvalidOrder + => "InvalidOrder\n".to_string() + , + } + } + Operation::DeleteOrder { + order_id, + } => { + prompt(args.force, "This will delete the given entry, are you sure?")?; + info!("Performing a DeleteOrder request on {:?}", ( + &order_id + )); + + let result = client.delete_order( + order_id, + ).await?; + debug!("Result: {:?}", result); + + match result { + DeleteOrderResponse::InvalidIDSupplied + => "InvalidIDSupplied\n".to_string() + , + DeleteOrderResponse::OrderNotFound + => "OrderNotFound\n".to_string() + , + } + } + Operation::GetOrderById { + order_id, + } => { + info!("Performing a GetOrderById request on {:?}", ( + &order_id + )); + + let result = client.get_order_by_id( + order_id, + ).await?; + debug!("Result: {:?}", result); + + match result { + GetOrderByIdResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + GetOrderByIdResponse::InvalidIDSupplied + => "InvalidIDSupplied\n".to_string() + , + GetOrderByIdResponse::OrderNotFound + => "OrderNotFound\n".to_string() + , + } + } + Operation::CreateUser { + body, + } => { + info!("Performing a CreateUser request"); + + let result = client.create_user( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + CreateUserResponse::SuccessfulOperation + => "SuccessfulOperation\n".to_string() + , + } + } + Operation::CreateUsersWithArrayInput { + body, + } => { + info!("Performing a CreateUsersWithArrayInput request"); + + let result = client.create_users_with_array_input( + body.as_ref(), + ).await?; + debug!("Result: {:?}", result); + + match result { + CreateUsersWithArrayInputResponse::SuccessfulOperation + => "SuccessfulOperation\n".to_string() + , + } + } + Operation::CreateUsersWithListInput { + body, + } => { + info!("Performing a CreateUsersWithListInput request"); + + let result = client.create_users_with_list_input( + body.as_ref(), + ).await?; + debug!("Result: {:?}", result); + + match result { + CreateUsersWithListInputResponse::SuccessfulOperation + => "SuccessfulOperation\n".to_string() + , + } + } + Operation::LoginUser { + username, + password, + } => { + info!("Performing a LoginUser request"); + + let result = client.login_user( + username, + password, + ).await?; + debug!("Result: {:?}", result); + + match result { + LoginUserResponse::SuccessfulOperation + { + body, + x_rate_limit, + x_expires_after, + } + => "SuccessfulOperation\n".to_string() + + + &format!("body: {}\n", serde_json::to_string_pretty(&body)?) + + &format!( + "x_rate_limit: {}\n", + serde_json::to_string_pretty(&x_rate_limit)? + ) + + &format!( + "x_expires_after: {}\n", + serde_json::to_string_pretty(&x_expires_after)? + ), + LoginUserResponse::InvalidUsername + => "InvalidUsername\n".to_string() + , + } + } + Operation::LogoutUser { + } => { + info!("Performing a LogoutUser request"); + + let result = client.logout_user( + ).await?; + debug!("Result: {:?}", result); + + match result { + LogoutUserResponse::SuccessfulOperation + => "SuccessfulOperation\n".to_string() + , + } + } + Operation::DeleteUser { + username, + } => { + prompt(args.force, "This will delete the given entry, are you sure?")?; + info!("Performing a DeleteUser request on {:?}", ( + &username + )); + + let result = client.delete_user( + username, + ).await?; + debug!("Result: {:?}", result); + + match result { + DeleteUserResponse::InvalidUsernameSupplied + => "InvalidUsernameSupplied\n".to_string() + , + DeleteUserResponse::UserNotFound + => "UserNotFound\n".to_string() + , + } + } + Operation::GetUserByName { + username, + } => { + info!("Performing a GetUserByName request on {:?}", ( + &username + )); + + let result = client.get_user_by_name( + username, + ).await?; + debug!("Result: {:?}", result); + + match result { + GetUserByNameResponse::SuccessfulOperation + (body) + => "SuccessfulOperation\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + GetUserByNameResponse::InvalidUsernameSupplied + => "InvalidUsernameSupplied\n".to_string() + , + GetUserByNameResponse::UserNotFound + => "UserNotFound\n".to_string() + , + } + } + Operation::UpdateUser { + username, + body, + } => { + info!("Performing a UpdateUser request on {:?}", ( + &username + )); + + let result = client.update_user( + username, + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + UpdateUserResponse::InvalidUserSupplied + => "InvalidUserSupplied\n".to_string() + , + UpdateUserResponse::UserNotFound + => "UserNotFound\n".to_string() + , + } + } + }; + + if let Some(output_file) = args.output_file { + std::fs::write(output_file, result)? + } else { + println!("{}", result); + } + Ok(()) +} + +fn prompt(force: bool, text: &str) -> Result<()> { + if force || Confirm::new().with_prompt(text).interact()? { + Ok(()) + } else { + Err(anyhow!("Aborting")) + } +} + +// May be unused if all inputs are primitive types +#[allow(dead_code)] +fn parse_json<'a, T: serde::de::Deserialize<'a>>(json_string: &'a str) -> Result { + serde_json::from_str(json_string).map_err(|err| anyhow!("Error parsing input: {}", err)) +} diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/FILES b/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/FILES index f67b7ce47ca8a..ce2109b1de2e5 100644 --- a/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/FILES +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/FILES @@ -3,6 +3,7 @@ Cargo.toml README.md api/openapi.yaml +bin/cli.rs docs/default_api.md examples/ca.pem examples/client/client_auth.rs diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml b/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml index b77567e316ee6..db9f608a74190 100644 --- a/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml @@ -15,6 +15,9 @@ client = [ server = [ "serde_ignored", "hyper", "regex", "percent-encoding", "url", "lazy_static" ] +cli = [ + "anyhow", "clap-verbosity-flag", "simple_logger", "structopt", "tokio" +] conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] @@ -52,6 +55,13 @@ lazy_static = { version = "1.4", optional = true } percent-encoding = {version = "2.1.0", optional = true} regex = {version = "1.3", optional = true} +# CLI-specific +anyhow = { version = "1", optional = true } +clap-verbosity-flag = { version = "0.3", optional = true } +simple_logger = { version = "2.0", features = ["stderr"], optional = true } +structopt = { version = "0.3", optional = true } +tokio = { version = "0.2", features = ["rt-threaded", "macros", "stream"], optional = true } + # Conversion frunk = { version = "0.4.0", optional = true } frunk_derives = { version = "0.4.0", optional = true } @@ -79,3 +89,8 @@ required-features = ["client"] [[example]] name = "server" required-features = ["server"] + +[[bin]] +name = "ping-bearer-auth" +path = "bin/cli.rs" +required-features = ["client", "cli"] diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md b/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md index 690e410b08268..2f5e78eb8ff57 100644 --- a/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md @@ -23,6 +23,7 @@ This autogenerated project defines an API crate `ping-bearer-auth` which contain * Data types representing the underlying data model. * A `Client` type which implements `Api` and issues HTTP requests for each operation. * A router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. +* A CLI tool to drive basic API operations from the command line. It also contains an example server and client which make use of `ping-bearer-auth`: @@ -36,6 +37,30 @@ It also contains an example server and client which make use of `ping-bearer-aut You can use the example server and client as a basis for your own code. See below for [more detail on the examples](#using-the-generated-library). +## CLI + +Run the included CLI tool with: + +``` +cargo run --bin cli --features=cli +``` + +To pass in arguments, put them after `--`, for example: + +``` +cargo run --bin cli --features=cli -- --help +``` + +See the help text for available options. + +To build a standalone tool, use: + +``` +cargo build --bin cli --features=cli --release +``` + +You'll find the binary at `target/release/cli`. + ## Examples Run examples with: @@ -86,6 +111,8 @@ The generated library has a few optional features that can be activated through * The constructed client implements the API trait by making remote API call. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. +* `cli` + * This defaults to disabled and is required for building the included CLI tool. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/bin/cli.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/bin/cli.rs new file mode 100644 index 0000000000000..2159d36a518e4 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/bin/cli.rs @@ -0,0 +1,159 @@ +//! CLI tool driving the API client +use anyhow::{anyhow, Context, Result}; +use log::{debug, info}; +// models may be unused if all inputs are primitive types +#[allow(unused_imports)] +use ping_bearer_auth::{ + models, ApiNoContext, Client, ContextWrapperExt, + PingGetResponse, +}; +use simple_logger::SimpleLogger; +use structopt::StructOpt; +use swagger::{AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString}; + +type ClientContext = swagger::make_context_ty!( + ContextBuilder, + EmptyContext, + Option, + XSpanIdString +); + +#[derive(StructOpt, Debug)] +#[structopt( + name = "ping test", + version = "1.0", + about = "CLI access to ping test" +)] +struct Cli { + #[structopt(subcommand)] + operation: Operation, + + /// Address or hostname of the server hosting this API, including optional port + #[structopt(short = "a", long, default_value = "http://localhost")] + server_address: String, + + /// Path to the client private key if using client-side TLS authentication + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-certificate", "server-certificate"]))] + client_key: Option, + + /// Path to the client's public certificate associated with the private key + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-key", "server-certificate"]))] + client_certificate: Option, + + /// Path to CA certificate used to authenticate the server + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long)] + server_certificate: Option, + + /// If set, write output to file instead of stdout + #[structopt(short, long)] + output_file: Option, + + #[structopt(flatten)] + verbosity: clap_verbosity_flag::Verbosity, + + /// Bearer token if used for authentication + #[structopt(env = "PING_BEARER_AUTH_BEARER_TOKEN", hide_env_values = true)] + bearer_token: Option, +} + +#[derive(StructOpt, Debug)] +enum Operation { + PingGet { + }, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + if args.client_certificate.is_some() { + debug!("Using mutual TLS"); + let client = Client::try_new_https_mutual( + &args.server_address, + args.server_certificate.unwrap(), + args.client_key.unwrap(), + args.client_certificate.unwrap(), + ) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else if args.server_certificate.is_some() { + debug!("Using TLS with pinned server certificate"); + let client = + Client::try_new_https_pinned(&args.server_address, args.server_certificate.unwrap()) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else { + debug!("Using client without certificates"); + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) + } +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::from_args(); + if let Some(log_level) = args.verbosity.log_level() { + SimpleLogger::new().with_level(log_level.to_level_filter()).init()?; + } + + debug!("Arguments: {:?}", &args); + + let mut auth_data: Option = None; + + if let Some(ref bearer_token) = args.bearer_token { + debug!("Using bearer token"); + auth_data = Some(AuthData::bearer(bearer_token)); + } + + #[allow(trivial_casts)] + let context = swagger::make_context!( + ContextBuilder, + EmptyContext, + auth_data, + XSpanIdString::default() + ); + + let client = create_client(&args, context)?; + + let result = match args.operation { + Operation::PingGet { + } => { + info!("Performing a PingGet request"); + + let result = client.ping_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + PingGetResponse::OK + => "OK\n".to_string() + , + } + } + }; + + if let Some(output_file) = args.output_file { + std::fs::write(output_file, result)? + } else { + println!("{}", result); + } + Ok(()) +} + +// May be unused if all inputs are primitive types +#[allow(dead_code)] +fn parse_json<'a, T: serde::de::Deserialize<'a>>(json_string: &'a str) -> Result { + serde_json::from_str(json_string).map_err(|err| anyhow!("Error parsing input: {}", err)) +} diff --git a/samples/server/petstore/rust-server/output/rust-server-test/.openapi-generator/FILES b/samples/server/petstore/rust-server/output/rust-server-test/.openapi-generator/FILES index 2a4b5711f84d3..e27ac4b0b61a2 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/.openapi-generator/FILES +++ b/samples/server/petstore/rust-server/output/rust-server-test/.openapi-generator/FILES @@ -3,6 +3,7 @@ Cargo.toml README.md api/openapi.yaml +bin/cli.rs docs/ANullableContainer.md docs/AdditionalPropertiesObject.md docs/AllOfObject.md diff --git a/samples/server/petstore/rust-server/output/rust-server-test/Cargo.toml b/samples/server/petstore/rust-server/output/rust-server-test/Cargo.toml index 50ef5c7d4bd4b..730e5121bb179 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/Cargo.toml +++ b/samples/server/petstore/rust-server/output/rust-server-test/Cargo.toml @@ -15,6 +15,9 @@ client = [ server = [ "serde_ignored", "hyper", "regex", "percent-encoding", "url", "lazy_static" ] +cli = [ + "anyhow", "clap-verbosity-flag", "simple_logger", "structopt", "tokio" +] conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] @@ -52,6 +55,13 @@ lazy_static = { version = "1.4", optional = true } percent-encoding = {version = "2.1.0", optional = true} regex = {version = "1.3", optional = true} +# CLI-specific +anyhow = { version = "1", optional = true } +clap-verbosity-flag = { version = "0.3", optional = true } +simple_logger = { version = "2.0", features = ["stderr"], optional = true } +structopt = { version = "0.3", optional = true } +tokio = { version = "0.2", features = ["rt-threaded", "macros", "stream"], optional = true } + # Conversion frunk = { version = "0.4.0", optional = true } frunk_derives = { version = "0.4.0", optional = true } @@ -79,3 +89,8 @@ required-features = ["client"] [[example]] name = "server" required-features = ["server"] + +[[bin]] +name = "rust-server-test" +path = "bin/cli.rs" +required-features = ["client", "cli"] diff --git a/samples/server/petstore/rust-server/output/rust-server-test/README.md b/samples/server/petstore/rust-server/output/rust-server-test/README.md index 48ce3f2fec00c..034fabc281713 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/README.md +++ b/samples/server/petstore/rust-server/output/rust-server-test/README.md @@ -23,6 +23,7 @@ This autogenerated project defines an API crate `rust-server-test` which contain * Data types representing the underlying data model. * A `Client` type which implements `Api` and issues HTTP requests for each operation. * A router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. +* A CLI tool to drive basic API operations from the command line. It also contains an example server and client which make use of `rust-server-test`: @@ -36,6 +37,30 @@ It also contains an example server and client which make use of `rust-server-tes You can use the example server and client as a basis for your own code. See below for [more detail on the examples](#using-the-generated-library). +## CLI + +Run the included CLI tool with: + +``` +cargo run --bin cli --features=cli +``` + +To pass in arguments, put them after `--`, for example: + +``` +cargo run --bin cli --features=cli -- --help +``` + +See the help text for available options. + +To build a standalone tool, use: + +``` +cargo build --bin cli --features=cli --release +``` + +You'll find the binary at `target/release/cli`. + ## Examples Run examples with: @@ -92,6 +117,8 @@ The generated library has a few optional features that can be activated through * The constructed client implements the API trait by making remote API call. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. +* `cli` + * This defaults to disabled and is required for building the included CLI tool. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. diff --git a/samples/server/petstore/rust-server/output/rust-server-test/bin/cli.rs b/samples/server/petstore/rust-server/output/rust-server-test/bin/cli.rs new file mode 100644 index 0000000000000..08653dede16db --- /dev/null +++ b/samples/server/petstore/rust-server/output/rust-server-test/bin/cli.rs @@ -0,0 +1,315 @@ +//! CLI tool driving the API client +use anyhow::{anyhow, Context, Result}; +use log::{debug, info}; +// models may be unused if all inputs are primitive types +#[allow(unused_imports)] +use rust_server_test::{ + models, ApiNoContext, Client, ContextWrapperExt, + AllOfGetResponse, + DummyGetResponse, + DummyPutResponse, + FileResponseGetResponse, + GetStructuredYamlResponse, + HtmlPostResponse, + PostYamlResponse, + RawJsonGetResponse, + SoloObjectPostResponse, +}; +use simple_logger::SimpleLogger; +use structopt::StructOpt; +use swagger::{AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString}; + +type ClientContext = swagger::make_context_ty!( + ContextBuilder, + EmptyContext, + Option, + XSpanIdString +); + +#[derive(StructOpt, Debug)] +#[structopt( + name = "rust-server-test", + version = "2.3.4", + about = "CLI access to rust-server-test" +)] +struct Cli { + #[structopt(subcommand)] + operation: Operation, + + /// Address or hostname of the server hosting this API, including optional port + #[structopt(short = "a", long, default_value = "http://localhost")] + server_address: String, + + /// Path to the client private key if using client-side TLS authentication + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-certificate", "server-certificate"]))] + client_key: Option, + + /// Path to the client's public certificate associated with the private key + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long, requires_all(&["client-key", "server-certificate"]))] + client_certificate: Option, + + /// Path to CA certificate used to authenticate the server + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(feature = "client-tls")] + #[structopt(long)] + server_certificate: Option, + + /// If set, write output to file instead of stdout + #[structopt(short, long)] + output_file: Option, + + #[structopt(flatten)] + verbosity: clap_verbosity_flag::Verbosity, +} + +#[derive(StructOpt, Debug)] +enum Operation { + AllOfGet { + }, + /// A dummy endpoint to make the spec valid. + DummyGet { + }, + DummyPut { + #[structopt(parse(try_from_str = parse_json))] + nested_response: models::DummyPutRequest, + }, + /// Get a file + FileResponseGet { + }, + GetStructuredYaml { + }, + /// Test HTML handling + HtmlPost { + body: String, + }, + PostYaml { + /// The YAML body to test + value: String, + }, + /// Get an arbitrary JSON blob. + RawJsonGet { + }, + /// Send an arbitrary JSON blob + SoloObjectPost { + value: serde_json::Value, + }, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + if args.client_certificate.is_some() { + debug!("Using mutual TLS"); + let client = Client::try_new_https_mutual( + &args.server_address, + args.server_certificate.unwrap(), + args.client_key.unwrap(), + args.client_certificate.unwrap(), + ) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else if args.server_certificate.is_some() { + debug!("Using TLS with pinned server certificate"); + let client = + Client::try_new_https_pinned(&args.server_address, args.server_certificate.unwrap()) + .context("Failed to create HTTPS client")?; + Ok(Box::new(client.with_context(context))) + } else { + debug!("Using client without certificates"); + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) + } +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +fn create_client(args: &Cli, context: ClientContext) -> Result>> { + let client = + Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; + Ok(Box::new(client.with_context(context))) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::from_args(); + if let Some(log_level) = args.verbosity.log_level() { + SimpleLogger::new().with_level(log_level.to_level_filter()).init()?; + } + + debug!("Arguments: {:?}", &args); + + let auth_data: Option = None; + + #[allow(trivial_casts)] + let context = swagger::make_context!( + ContextBuilder, + EmptyContext, + auth_data, + XSpanIdString::default() + ); + + let client = create_client(&args, context)?; + + let result = match args.operation { + Operation::AllOfGet { + } => { + info!("Performing a AllOfGet request"); + + let result = client.all_of_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + AllOfGetResponse::OK + (body) + => "OK\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::DummyGet { + } => { + info!("Performing a DummyGet request"); + + let result = client.dummy_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + DummyGetResponse::Success + => "Success\n".to_string() + , + } + } + Operation::DummyPut { + nested_response, + } => { + info!("Performing a DummyPut request"); + + let result = client.dummy_put( + nested_response, + ).await?; + debug!("Result: {:?}", result); + + match result { + DummyPutResponse::Success + => "Success\n".to_string() + , + } + } + Operation::FileResponseGet { + } => { + info!("Performing a FileResponseGet request"); + + let result = client.file_response_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + FileResponseGetResponse::Success + (body) + => "Success\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::GetStructuredYaml { + } => { + info!("Performing a GetStructuredYaml request"); + + let result = client.get_structured_yaml( + ).await?; + debug!("Result: {:?}", result); + + match result { + GetStructuredYamlResponse::OK + (body) + => "OK\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::HtmlPost { + body, + } => { + info!("Performing a HtmlPost request"); + + let result = client.html_post( + body, + ).await?; + debug!("Result: {:?}", result); + + match result { + HtmlPostResponse::Success + (body) + => "Success\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::PostYaml { + value, + } => { + info!("Performing a PostYaml request"); + + let result = client.post_yaml( + value, + ).await?; + debug!("Result: {:?}", result); + + match result { + PostYamlResponse::OK + => "OK\n".to_string() + , + } + } + Operation::RawJsonGet { + } => { + info!("Performing a RawJsonGet request"); + + let result = client.raw_json_get( + ).await?; + debug!("Result: {:?}", result); + + match result { + RawJsonGetResponse::Success + (body) + => "Success\n".to_string() + + + &serde_json::to_string_pretty(&body)?, + } + } + Operation::SoloObjectPost { + value, + } => { + info!("Performing a SoloObjectPost request"); + + let result = client.solo_object_post( + value, + ).await?; + debug!("Result: {:?}", result); + + match result { + SoloObjectPostResponse::OK + => "OK\n".to_string() + , + } + } + }; + + if let Some(output_file) = args.output_file { + std::fs::write(output_file, result)? + } else { + println!("{}", result); + } + Ok(()) +} + +// May be unused if all inputs are primitive types +#[allow(dead_code)] +fn parse_json<'a, T: serde::de::Deserialize<'a>>(json_string: &'a str) -> Result { + serde_json::from_str(json_string).map_err(|err| anyhow!("Error parsing input: {}", err)) +}