From 25192b5dc10326ae5bbe6733191a1f4620263cb0 Mon Sep 17 00:00:00 2001 From: Adam Leventhal Date: Tue, 8 Feb 2022 08:59:38 -0800 Subject: [PATCH] Add a wrapper types for success and error responses (#26) --- Cargo.lock | 9 +- README.md | 142 ++ docs/generated_methods.md | 67 + docs/progenitor-client.md | 87 ++ example-build/Cargo.toml | 3 +- example-build/build.rs | 10 +- example-macro/Cargo.toml | 2 - progenitor-client/Cargo.toml | 4 +- progenitor-client/src/lib.rs | 176 ++- progenitor-impl/Cargo.toml | 1 - progenitor-impl/src/lib.rs | 403 ++++- progenitor-impl/src/template.rs | 32 +- progenitor-impl/tests/output/buildomat.out | 211 ++- progenitor-impl/tests/output/keeper.out | 80 +- progenitor-impl/tests/output/nexus.out | 1651 ++++++++++++++------ progenitor-macro/src/lib.rs | 43 + progenitor/Cargo.toml | 3 +- progenitor/src/lib.rs | 13 +- progenitor/src/main.rs | 78 +- sample_openapi/nexus.json | 1249 ++++++++++++--- 20 files changed, 3262 insertions(+), 1002 deletions(-) create mode 100644 README.md create mode 100644 docs/generated_methods.md create mode 100644 docs/progenitor-client.md diff --git a/Cargo.lock b/Cargo.lock index e25c71c..0eb0144 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,10 +120,9 @@ dependencies = [ name = "example-build" version = "0.0.1" dependencies = [ - "anyhow", "chrono", - "percent-encoding", "progenitor", + "progenitor-client", "reqwest", "serde", "serde_json", @@ -134,9 +133,7 @@ dependencies = [ name = "example-macro" version = "0.0.1" dependencies = [ - "anyhow", "chrono", - "percent-encoding", "progenitor", "reqwest", "schemars", @@ -681,6 +678,7 @@ dependencies = [ "getopts", "openapiv3", "percent-encoding", + "progenitor-client", "progenitor-impl", "progenitor-macro", "reqwest", @@ -694,7 +692,9 @@ dependencies = [ name = "progenitor-client" version = "0.0.0" dependencies = [ + "percent-encoding", "reqwest", + "serde", "serde_json", ] @@ -702,7 +702,6 @@ dependencies = [ name = "progenitor-impl" version = "0.0.0" dependencies = [ - "anyhow", "convert_case", "expectorate", "getopts", diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa72fb5 --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ +# Progenitor + +Progenitor is a Rust crate for generating opinionated clients from API +descriptions specified in the OpenAPI 3.0.x format. It makes use of Rust +futures for async API calls and `Streams` for paginated interfaces. + +It generates a type called `Client` with methods that correspond to the +operations specified in the OpenAPI document. + +## Using Progenitor + +There are three different ways of using the `progenitor` crate. The one you +choose will depend on your use case and preferences. + +### Macro + +The simplest way to use Progenitor is via its `generate_api!` macro. + +In a source file (often `main.rs`, `lib.rs`, or `mod.rs`) simply invoke the +macro: + +```rust +generate_api("path/to/openapi_document.json"); +``` + +You'll need to add add the following to `Cargo.toml`: + +```diff +[dependencies] ++progenitor = { git = "https://github.com/oxidecomputer/progenitor" } ++reqwest = { version = "0.11", features = ["json", "stream"] } ++serde = { version = "1.0", features = ["derive"] } +``` + +In addition, if the OpenAPI document contains string types with the `format` +field set to `date` or `date-time`, include + +```diff +[dependencies] ++chrono = { version = "0.4", features = ["serde"] } +``` + +Similarly if there is a `format` field set to `uuid`: + +```diff +[dependencies] ++uuid = { version = "0.8", features = ["serde", "v4"] } +``` + +Note that the macro will be re-evaluated when the OpenAPI json document +changes (when it's mtime is updated). + +### Builder + +Progenitor includes an interface appropriate for use in a +[`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) +file. While slightly more onerous than the macro, a builder has the advantage of making the generated code visible. + +The `build.rs` file should look something like this: + +```rust +fn main() { + let src = "../sample_openapi/keeper.json"; + println!("cargo:rerun-if-changed={}", src); + let file = File::open(src).unwrap(); + let spec = serde_json::from_reader(file).unwrap(); + let mut generator = progenitor::Generator::new(); + + let content = generator.generate_text(&spec).unwrap(); + + let mut out_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf(); + out_file.push("codegen.rs"); + + fs::write(out_file, content).unwrap(); +} +``` + +In a source file (often `main.rs`, `lib.rs`, or `mod.rs`) include the generated +code: + +```rust +include!(concat!(env!("OUT_DIR"), "/codegen.rs")); +``` + +You'll need to add add the following to `Cargo.toml`: + +```diff +[dependencies] ++progenitor-client = { git = "https://github.com/oxidecomputer/progenitor" } ++reqwest = { version = "0.11", features = ["json", "stream"] } ++serde = { version = "1.0", features = ["derive"] } + +[build-dependencies] ++progenitor = { git = "https://github.com/oxidecomputer/progenitor" } ++serde_json = "1.0" +``` + +(`chrono` and `uuid` as above) + +Note that `progenitor` is used by `build.rs`, but the generated code required +`progenitor-client`. + + +### Static Crate + +Progenitor can be run to emit a stand-alone crate for the generated client. +This ensures no unexpected changes (e.g. from updates to progenitor). It is +however, the most manual way to use Progenitor. + +Usage: + +``` +progenitor + +Options: + -i INPUT OpenAPI definition document (JSON) + -o OUTPUT Generated Rust crate directory + -n CRATE Target Rust crate name + -v VERSION Target Rust crate version +``` + +For example: + +`cargo run --bin progenitor -- -i sample_penapi/keeper.json -o keeper -n keeper -v 0.1.0` + +This will produce a package in the specified directory. The output has no +persistent dependency on Progenitor including the `progenitor-client` crate. +Here's a excerpt from the emitted `Cargo.toml`: + +```toml +[dependencies] +chrono = { version = "0.4", features = ["serde"] } +futures = "0.3" +percent-encoding = "2.1" +reqwest = { version = "0.11", features = ["json", "stream"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "0.8", features = ["serde", "v4"] } +``` + +Note that there is a dependency on `percent-encoding` which macro- and +build.rs-generated clients is included from `progenitor-client`. \ No newline at end of file diff --git a/docs/generated_methods.md b/docs/generated_methods.md new file mode 100644 index 0000000..fb2bc4d --- /dev/null +++ b/docs/generated_methods.md @@ -0,0 +1,67 @@ +# Generated Methods + +Progenitor generates methods according to operations within an OpenAPI +document. Each method takes the following general form: + +```rust +impl Client { + pub async fn operation_name<'a>( + &'a self, + // Path parameters (if any) come first and are always mandatory + path_parameter_1: String, + path_parameter_2: u32, + // Query parameters (if any) come next and may be optional + query_parameter_1: String, + query_parameter_2: Option, + // A body parameter (if specified) comes last + body: &ThisOperationBody, + ) -> Result< + ResponseValue, + Error + > { + .. +``` + +For more info on the `ResponseValue` and `Error` types, see +[progenitor_client](./progenitor_client). + +Note that methods are `async` so must be `await`ed to get the response. + +### Dropshot Paginated Operations + +The Dropshot crate defines a mechanism for pagination. If that mechanism is +used for a particular operation, Progenitor will generate an additional method +that produces a `Stream`. Consumers can iterate over all items in the paginated +collection without manually fetching individual pages. + +Here's the signature for a typical generated method: + +```rust + pub fn operation_name_stream<'a>( + &'a self, + // Specific parameters... + limit: Option, + ) -> impl futures::Stream< + Item = Result> + > + Unpin + '_ { + .. +``` + +A typical consumer of this method might look like this: + +```rust + let mut stream = client.operation_name_stream(None); + loop { + match stream.try_next().await { + Ok(Some(item)) => println!("item {:?}", item), + Ok(None) => { + println!("done."); + break; + } + Err(_) => { + println!("error!"); + break; + } + } + } +``` diff --git a/docs/progenitor-client.md b/docs/progenitor-client.md new file mode 100644 index 0000000..87ab594 --- /dev/null +++ b/docs/progenitor-client.md @@ -0,0 +1,87 @@ +# Progenitor Client + +The `progenitor-client` crate contains types that are exported by generated +clients as well as functions that are used internally by generated clients. +Depending on how `progenitor` is being used, the crate will be included in +different ways (see ["Using Progenitor"](../README.md#using_progenitor)). + +- For macro consumers, it comes from the `progenitor` dependency. + +- For builder consumers, it must be specified under `[dependencies]` (while `progenitor` is under `[build-dependencies]`). + +- For statically generated consumers, the code is emitted into +`src/progenitor_client.rs`. + +The two types that `progenitor-client` exports are `Error` and +`ResponseValue`. A typical generated method will use these types in its +signature: + +```rust +impl Client { + pub async fn operation_name<'a>( + &'a self, + // parameters ... + ) -> Result< + ResponseValue, + Error> + { + .. +``` + +## `ResponseValue` + +OpenAPI documents defines the types that an operation returns. Generated +methods wrap these types in `ResponseValue` for two reasons: there is +additional information that may be included in a response such as the specific +status code and headers, and that information cannot simply be included in the +response type because that type may be used in other context (e.g. as a body +parameter). + +These are the relevant implementations for `ResponseValue`: + +```rust +/// Success value returned by generated client methods. +pub struct ResponseValue { .. } + +impl ResponseValue { + pub fn status(&self) -> &reqwest::StatusCode { .. } + pub fn headers(&self) -> &reqwest::header::HeaderMap { .. } + pub fn into_inner(self) -> T { .. } +} +impl std::ops::Deref for ResponseValue { + type Target = T; + .. +} +impl std::ops::DerefMut for ResponseValue { .. } + +impl std::fmt::Debug for ResponseValue { .. } +``` + +It can be used as the type `T` in most instances and extracted as a `T` using +`into_inner()`. + +## `Error` + +There are four sub-categories of error covered by the error type variants: +- A communication error + +- An expected error response, defined by the OpenAPI document with a 4xx or 5xx +status code + +- An expected status code (whose payload didn't deserialize as expected (this +could be viewed as a sub-type of communication error), but it is separately +identified as there's more information; note that this covers both success and +error status codes + +- An unexpected status code in the response + +These errors are covered by the variants of the `Error` type: + +```rust +pub enum Error { + CommunicationError(reqwest::Error), + ErrorResponse(ResponseValue), + InvalidResponsePayload(reqwest::Error), + UnexpectedResponse(reqwest::Response), +} +``` diff --git a/example-build/Cargo.toml b/example-build/Cargo.toml index 86f4f41..1fca282 100644 --- a/example-build/Cargo.toml +++ b/example-build/Cargo.toml @@ -5,9 +5,8 @@ authors = ["Adam H. Leventhal "] edition = "2018" [dependencies] -anyhow = "1.0" chrono = { version = "0.4", features = ["serde"] } -percent-encoding = "2.1" +progenitor-client = { path = "../progenitor-client" } reqwest = { version = "0.11", features = ["json", "stream"] } serde = { version = "1.0", features = ["derive"] } uuid = { version = "0.8", features = ["serde", "v4"] } diff --git a/example-build/build.rs b/example-build/build.rs index 0777bf7..b4ee9c3 100644 --- a/example-build/build.rs +++ b/example-build/build.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Oxide Computer Company +// Copyright 2022 Oxide Computer Company use std::{ env, @@ -6,12 +6,12 @@ use std::{ path::Path, }; -use progenitor::Generator; - fn main() { - let file = File::open("../sample_openapi/keeper.json").unwrap(); + let src = "../sample_openapi/keeper.json"; + println!("cargo:rerun-if-changed={}", src); + let file = File::open(src).unwrap(); let spec = serde_json::from_reader(file).unwrap(); - let mut generator = Generator::new(); + let mut generator = progenitor::Generator::new(); let content = generator.generate_text(&spec).unwrap(); diff --git a/example-macro/Cargo.toml b/example-macro/Cargo.toml index 6eb4e7e..b9209d1 100644 --- a/example-macro/Cargo.toml +++ b/example-macro/Cargo.toml @@ -5,9 +5,7 @@ authors = ["Adam H. Leventhal "] edition = "2018" [dependencies] -anyhow = "1.0" chrono = { version = "0.4", features = ["serde"] } -percent-encoding = "2.1" progenitor = { path = "../progenitor" } reqwest = { version = "0.11", features = ["json", "stream"] } schemars = "0.8" diff --git a/progenitor-client/Cargo.toml b/progenitor-client/Cargo.toml index bfd7bfd..c44f2f5 100644 --- a/progenitor-client/Cargo.toml +++ b/progenitor-client/Cargo.toml @@ -7,5 +7,7 @@ repository = "https://github.com/oxidecomputer/progenitor.git" description = "An OpenAPI client generator - client support" [dependencies] -reqwest = "0.11" +percent-encoding = "2.1" +reqwest = { version = "0.11", features = ["json"] } +serde = "1.0" serde_json = "1.0" diff --git a/progenitor-client/src/lib.rs b/progenitor-client/src/lib.rs index 636fad4..4f1c055 100644 --- a/progenitor-client/src/lib.rs +++ b/progenitor-client/src/lib.rs @@ -2,35 +2,87 @@ //! Support code for generated clients. -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; -/// Error produced by generated client methods. -pub enum Error { - /// Indicates an error from the server, with the data, or with the - /// connection. - CommunicationError(reqwest::Error), - - /// A documented error response. - ErrorResponse(ResponseValue), - - /// A response not listed in the API description. This may represent a - /// success or failure response; check `status()::is_success()`. - UnexpectedResponse(reqwest::Response), -} +use serde::de::DeserializeOwned; +/// Success value returned by generated client methods. pub struct ResponseValue { inner: T, - response: reqwest::Response, + status: reqwest::StatusCode, + headers: reqwest::header::HeaderMap, + // TODO cookies? +} + +impl ResponseValue { + #[doc(hidden)] + pub async fn from_response( + response: reqwest::Response, + ) -> Result> { + let status = response.status(); + let headers = response.headers().clone(); + let inner = response + .json() + .await + .map_err(Error::InvalidResponsePayload)?; + + Ok(Self { + inner, + status, + headers, + }) + } +} + +impl ResponseValue<()> { + #[doc(hidden)] + pub fn empty(response: reqwest::Response) -> Self { + let status = response.status(); + let headers = response.headers().clone(); + // TODO is there anything we want to do to confirm that there is no + // content? + Self { + inner: (), + status, + headers, + } + } } impl ResponseValue { - #[doc(hidden)] - pub fn new(inner: T, response: reqwest::Response) -> Self { - Self { inner, response } + /// Consumes the ResponseValue, returning the wrapped value. + pub fn into_inner(self) -> T { + self.inner } - pub fn request(&self) -> &reqwest::Response { - &self.response + /// Get the status from this response. + pub fn status(&self) -> &reqwest::StatusCode { + &self.status + } + /// Get the headers from this response. + pub fn headers(&self) -> &reqwest::header::HeaderMap { + &self.headers + } + + #[doc(hidden)] + pub fn map( + self, + f: F, + ) -> Result, E> + where + F: FnOnce(T) -> U, + { + let Self { + inner, + status, + headers, + } = self; + + Ok(ResponseValue { + inner: f(inner), + status, + headers, + }) } } @@ -41,3 +93,87 @@ impl Deref for ResponseValue { &self.inner } } + +impl DerefMut for ResponseValue { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl std::fmt::Debug for ResponseValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +/// Error produced by generated client methods. +/// +/// The type parameter may be a struct if there's a single expected error type +/// or an enum if there are multiple valid error types. It can be the unit type +/// if there are no structured returns expected. +#[derive(Debug)] +pub enum Error { + /// A server error either with the data, or with the connection. + CommunicationError(reqwest::Error), + + /// A documented, expected error response. + ErrorResponse(ResponseValue), + + /// An expected response code whose deserialization failed. + // TODO we have stuff from the response; should we include it? + InvalidResponsePayload(reqwest::Error), + + /// A response not listed in the API description. This may represent a + /// success or failure response; check `status().is_success()`. + UnexpectedResponse(reqwest::Response), +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Self::CommunicationError(e) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::CommunicationError(e) => { + write!(f, "Communication Error {}", e) + } + Error::ErrorResponse(rv) => { + write!(f, "Error Response {:?}", rv) + } + Error::InvalidResponsePayload(e) => { + write!(f, "Invalid Response Payload {}", e) + } + Error::UnexpectedResponse(r) => { + write!(f, "Unexpected Response {:?}", r) + } + } + } +} +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::CommunicationError(e) => Some(e), + Error::InvalidResponsePayload(e) => Some(e), + _ => None, + } + } +} + +const PATH_SET: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS + .add(b' ') + .add(b'"') + .add(b'#') + .add(b'<') + .add(b'>') + .add(b'?') + .add(b'`') + .add(b'{') + .add(b'}'); + +#[doc(hidden)] +pub fn encode_path(pc: &str) -> String { + percent_encoding::utf8_percent_encode(pc, PATH_SET).to_string() +} diff --git a/progenitor-impl/Cargo.toml b/progenitor-impl/Cargo.toml index f1126e9..1a11476 100644 --- a/progenitor-impl/Cargo.toml +++ b/progenitor-impl/Cargo.toml @@ -7,7 +7,6 @@ repository = "https://github.com/oxidecomputer/progenitor.git" description = "An OpenAPI client generator - core implementation" [dependencies] -anyhow = "1.0" convert_case = "0.4" getopts = "0.2" indexmap = "1.7" diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index a23646d..b413292 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2022 Oxide Computer Company -use std::{cmp::Ordering, collections::HashMap}; +use std::{ + cmp::Ordering, + collections::{BTreeSet, HashMap}, +}; use convert_case::{Case, Casing}; use indexmap::IndexMap; @@ -9,7 +12,6 @@ use openapiv3::{ StatusCode, }; use proc_macro2::TokenStream; - use quote::{format_ident, quote, ToTokens}; use template::PathTemplate; use thiserror::Error; @@ -78,11 +80,63 @@ enum OperationParameterType { } #[derive(Debug)] struct OperationResponse { - status_code: StatusCode, + status_code: OperationResponseStatus, typ: OperationResponseType, } -#[derive(Debug)] +impl Eq for OperationResponse {} +impl PartialEq for OperationResponse { + fn eq(&self, other: &Self) -> bool { + self.status_code == other.status_code + } +} +impl Ord for OperationResponse { + fn cmp(&self, other: &Self) -> Ordering { + self.status_code.cmp(&other.status_code) + } +} +impl PartialOrd for OperationResponse { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum OperationResponseStatus { + Code(u16), + Range(u16), + Default, +} + +impl OperationResponseStatus { + fn to_value(&self) -> u16 { + match self { + OperationResponseStatus::Code(code) => { + assert!(*code < 1000); + *code + } + OperationResponseStatus::Range(range) => { + assert!(*range < 10); + *range * 100 + } + OperationResponseStatus::Default => 1000, + } + } +} + +impl Ord for OperationResponseStatus { + fn cmp(&self, other: &Self) -> Ordering { + self.to_value().cmp(&other.to_value()) + } +} + +impl PartialOrd for OperationResponseStatus { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] enum OperationResponseType { Type(TypeId), None, @@ -176,28 +230,9 @@ impl Generator { }); let file = quote! { - use anyhow::Result; - - mod progenitor_support { - use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; - - #[allow(dead_code)] - const PATH_SET: &AsciiSet = &CONTROLS - .add(b' ') - .add(b'"') - .add(b'#') - .add(b'<') - .add(b'>') - .add(b'?') - .add(b'`') - .add(b'{') - .add(b'}'); - - #[allow(dead_code)] - pub(crate) fn encode_path(pc: &str) -> String { - utf8_percent_encode(pc, PATH_SET).to_string() - } - } + // Re-export ResponseValue and Error since those are used by the + // public interface of Client. + pub use progenitor_client::{Error, ResponseValue}; pub mod types { use serde::{Deserialize, Serialize}; @@ -415,7 +450,7 @@ impl Generator { OperationParameterKind::Path, ) => Ordering::Greater, - // Body params are last and should be unique + // Body params are last and should be singular. ( OperationParameterKind::Body, OperationParameterKind::Path, @@ -438,11 +473,31 @@ impl Generator { let mut responses = operation .responses - .responses + .default .iter() - .map(|(status_code, response_or_ref)| { - let response = response_or_ref.item(components)?; - + .map(|response_or_ref| { + Ok(( + OperationResponseStatus::Default, + response_or_ref.item(components)?, + )) + }) + .chain(operation.responses.responses.iter().map( + |(status_code, response_or_ref)| { + Ok(( + match status_code { + StatusCode::Code(code) => { + OperationResponseStatus::Code(*code) + } + StatusCode::Range(range) => { + OperationResponseStatus::Range(*range) + } + }, + response_or_ref.item(components)?, + )) + }, + )) + .map(|v: Result<(OperationResponseStatus, &Response)>| { + let (status_code, response) = v?; let typ = if let Some(mt) = response.content.get("application/json") { @@ -470,17 +525,17 @@ impl Generator { OperationResponseType::None }; + // See if there's a status code that covers success cases. if matches!( status_code, - StatusCode::Code(200..=299) | StatusCode::Range(2) + OperationResponseStatus::Default + | OperationResponseStatus::Code(200..=299) + | OperationResponseStatus::Range(2) ) { success = true; } - Ok(OperationResponse { - status_code: status_code.clone(), - typ, - }) + Ok(OperationResponse { status_code, typ }) }) .collect::>>()?; @@ -490,7 +545,7 @@ impl Generator { // spec. if !success { responses.push(OperationResponse { - status_code: StatusCode::Range(2), + status_code: OperationResponseStatus::Range(2), typ: OperationResponseType::Raw, }); } @@ -597,32 +652,208 @@ impl Generator { assert!(body_func.clone().count() <= 1); - let mut success_response_items = - method.responses.iter().filter(|response| { + let mut success_response_items = method + .responses + .iter() + .filter(|response| { matches!( - response.status_code, - StatusCode::Code(200..=299) | StatusCode::Range(2) + &response.status_code, + OperationResponseStatus::Default + | OperationResponseStatus::Code(200..=299) + | OperationResponseStatus::Range(2) ) - }); + }) + .collect::>(); + success_response_items.sort(); - assert_eq!(success_response_items.clone().count(), 1); + // If we have a range and a default, we can pop off the default since it will never be hit. + if let ( + Some(OperationResponse { + status_code: OperationResponseStatus::Range(2), + .. + }), + Some(OperationResponse { + status_code: OperationResponseStatus::Default, + .. + }), + ) = last_two(&success_response_items) + { + success_response_items.pop(); + } - let (response_type, decode_response) = success_response_items + let success_response_types = success_response_items + .iter() + .map(|response| response.typ.clone()) + .collect::>(); + + // TODO to deal with multiple success response types, we'll need to + // create an enum. + assert_eq!(success_response_types.len(), 1); + + let response_type = success_response_types + .iter() .next() - .map(|response| match &response.typ { - OperationResponseType::Type(type_id) => ( - self.type_space.get_type(type_id).unwrap().ident(), - quote! { res.json().await? }, - ), + .map(|typ| match typ { + OperationResponseType::Type(type_id) => { + let type_name = + self.type_space.get_type(type_id).unwrap().ident(); + quote! { ResponseValue<#type_name> } + } OperationResponseType::None => { - (quote! { reqwest::Response }, quote! { res }) - } - OperationResponseType::Raw => { - (quote! { reqwest::Response }, quote! { res }) + quote! { ResponseValue<()> } } + // TODO Maybe this should be ResponseValue? + OperationResponseType::Raw => quote! { reqwest::Response }, }) .unwrap(); + let success_response_matches = + success_response_items.iter().map(|response| { + let pat = match &response.status_code { + OperationResponseStatus::Code(code) => quote! { #code }, + OperationResponseStatus::Range(_) + | OperationResponseStatus::Default => { + quote! { 200 ..= 299 } + } + }; + + let decode = match &response.typ { + OperationResponseType::Type(_) => { + quote! { + ResponseValue::from_response(response).await + } + } + OperationResponseType::None => { + quote! { + Ok(ResponseValue::empty(response)) + } + } + OperationResponseType::Raw => quote! { Ok(response) }, + }; + + quote! { #pat => { #decode } } + }); + + // Errors... + let mut error_responses = method + .responses + .iter() + .filter(|response| { + matches!( + &response.status_code, + OperationResponseStatus::Code(400..=599) + | OperationResponseStatus::Range(4) + | OperationResponseStatus::Range(5) + ) + }) + .collect::>(); + error_responses.sort(); + + let error_response_types = error_responses + .iter() + .map(|response| response.typ.clone()) + .collect::>(); + // TODO create an enum if there are multiple error response types + assert!(error_response_types.len() <= 1); + + let error_type = error_response_types + .iter() + .next() + .map(|typ| match typ { + OperationResponseType::Type(type_id) => { + let type_name = + self.type_space.get_type(type_id).unwrap().ident(); + quote! { #type_name } + } + OperationResponseType::None => { + quote! { () } + } + // TODO Maybe this should be ResponseValue? + OperationResponseType::Raw => quote! { reqwest::Response }, + }) + .unwrap_or_else(|| quote! { () }); + + let error_response_matches = error_responses.iter().map(|response| { + let pat = match &response.status_code { + OperationResponseStatus::Code(code) => quote! { #code }, + OperationResponseStatus::Range(r) => { + let min = r * 100; + let max = min + 99; + quote! { #min ..= #max } + } + OperationResponseStatus::Default => unreachable!(), + }; + + let decode = match &response.typ { + OperationResponseType::Type(_) => { + quote! { + Err(Error::ErrorResponse( + ResponseValue::from_response(response) + .await? + )) + } + } + OperationResponseType::None => { + quote! { + Err(Error::ErrorResponse( + ResponseValue::empty(response) + )) + } + } + // TODO not sure how to handle this... + OperationResponseType::Raw => todo!(), + }; + + quote! { #pat => { #decode } } + }); + + // Generate the catch-all case for other statuses. If the operation + // specifies a default response, we've already handled the success + // status codes above, so we only need to consider error codes. + let default_response = method + .responses + .iter() + .find(|response| { + matches!( + &response.status_code, + OperationResponseStatus::Default + ) + }) + .map_or_else( + || + // With no default response, unexpected status codes produce + // and Error::UnexpectedResponse() + quote!{ + _ => Err(Error::UnexpectedResponse(response)), + }, + // If we have a structured default response, we decode it and + // return Error::ErrorResponse() + |response| { + let decode = match &response.typ { + OperationResponseType::Type(_) => { + quote! { + Err(Error::ErrorResponse( + ResponseValue::from_response(response) + .await? + )) + } + } + OperationResponseType::None => { + quote! { + Err(Error::ErrorResponse( + ResponseValue::empty(response) + )) + } + } + // TODO not sure how to handle this... maybe as a + // ResponseValue + OperationResponseType::Raw => todo!(), + }; + + quote! { _ => { #decode } } + }, + ); + // TODO document parameters let doc_comment = format!( "{}{}: {} {}", @@ -655,7 +886,10 @@ impl Generator { pub async fn #operation_id #bounds ( &'a self, #(#params),* - ) -> Result<#response_type> { + ) -> Result< + #response_type, + Error<#error_type>, + > { #url_path #query_build @@ -670,10 +904,42 @@ impl Generator { .await; #post_hook - // TODO we should do a match here for result?.status().as_u16() - let res = result?.error_for_status()?; + let response = result?; - Ok(#decode_response) + match response.status().as_u16() { + // These will be of the form... + // 201 => ResponseValue::from_response(response).await, + // 200..299 => ResponseValue::empty(response), + // TODO this isn't implemented + // ... or in the case of an operation with multiple + // successful response types... + // 200 => { + // ResponseValue::from_response() + // .await? + // .map(OperationXResponse::ResponseTypeA) + // } + // 201 => { + // ResponseValue::from_response() + // .await? + // .map(OperationXResponse::ResponseTypeB) + // } + #(#success_response_matches)* + + // This is almost identical to the success types except + // they are wrapped in Error::ErrorResponse... + // 400 => { + // Err(Error::ErrorResponse( + // ResponseValue::from_response(response.await?) + // )) + // } + #(#error_response_matches)* + + // The default response is either an Error with a known + // type if the operation defines a default (as above) or + // an Error::UnexpectedResponse... + // _ => Err(Error::UnexpectedResponse(response)), + #default_response + } } }; @@ -750,7 +1016,10 @@ impl Generator { pub fn #stream_id #bounds ( &'a self, #(#stream_params),* - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream, + >> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; @@ -761,6 +1030,7 @@ impl Generator { #(#first_params,)* ) .map_ok(move |page| { + let page = page.into_inner(); // The first page is just an iter let first = futures::stream::iter( page.items.into_iter().map(Ok) @@ -785,6 +1055,7 @@ impl Generator { #(#step_params,)* ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter( page @@ -864,7 +1135,8 @@ impl Generator { responses.iter().filter_map(|response| { match (&response.status_code, &response.typ) { ( - StatusCode::Code(200..=299) | StatusCode::Range(2), + OperationResponseStatus::Code(200..=299) + | OperationResponseStatus::Range(2), OperationResponseType::Type(type_id), ) => Some(type_id), _ => None, @@ -940,7 +1212,6 @@ impl Generator { pub fn dependencies(&self) -> Vec { let mut deps = vec![ - "anyhow = \"1.0\"", "percent-encoding = \"2.1\"", "serde = { version = \"1.0\", features = [\"derive\"] }", "reqwest = { version = \"0.11\", features = [\"json\", \"stream\"] }", @@ -968,10 +1239,16 @@ impl Generator { } } +fn last_two(items: &[T]) -> (Option<&T>, Option<&T>) { + match items.len() { + 0 => (None, None), + 1 => (Some(&items[0]), None), + n => (Some(&items[n - 2]), Some(&items[n - 1])), + } +} + /// Make the schema optional if it isn't already. -pub fn make_optional( - schema: schemars::schema::Schema, -) -> schemars::schema::Schema { +fn make_optional(schema: schemars::schema::Schema) -> schemars::schema::Schema { match &schema { // If the instance_type already includes Null then this is already // optional. diff --git a/progenitor-impl/src/template.rs b/progenitor-impl/src/template.rs index a551065..b166f5c 100644 --- a/progenitor-impl/src/template.rs +++ b/progenitor-impl/src/template.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Oxide Computer Company +// Copyright 2022 Oxide Computer Company use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -32,7 +32,7 @@ impl PathTemplate { if let Component::Parameter(n) = &component { let param = format_ident!("{}", n); Some(quote! { - progenitor_support::encode_path(&#param.to_string()) + progenitor_client::encode_path(&#param.to_string()) }) } else { None @@ -160,10 +160,9 @@ impl ToString for PathTemplate { #[cfg(test)] mod test { use super::{parse, Component, PathTemplate}; - use anyhow::{anyhow, Context, Result}; #[test] - fn basic() -> Result<()> { + fn basic() { let trials = vec![ ( "/info", @@ -193,15 +192,15 @@ mod test { ]; for (path, want) in trials.iter() { - let t = parse(path).with_context(|| anyhow!("path {}", path))?; - assert_eq!(&t, want); + match parse(path) { + Ok(t) => assert_eq!(&t, want), + Err(e) => panic!("path {} {}", path, e), + } } - - Ok(()) } #[test] - fn names() -> Result<()> { + fn names() { let trials = vec![ ("/info", vec![]), ("/measure/{number}", vec!["number".to_string()]), @@ -212,24 +211,23 @@ mod test { ]; for (path, want) in trials.iter() { - let t = parse(path).with_context(|| anyhow!("path {}", path))?; - assert_eq!(&t.names(), want); + match parse(path) { + Ok(t) => assert_eq!(&t.names(), want), + Err(e) => panic!("path {} {}", path, e), + } } - - Ok(()) } #[test] - fn compile() -> Result<()> { - let t = parse("/measure/{number}")?; + fn compile() { + let t = parse("/measure/{number}").unwrap(); let out = t.compile(); let want = quote::quote! { let url = format!("{}/measure/{}", self.baseurl, - progenitor_support::encode_path(&number.to_string()), + progenitor_client::encode_path(&number.to_string()), ); }; assert_eq!(want.to_string(), out.to_string()); - Ok(()) } } diff --git a/progenitor-impl/tests/output/buildomat.out b/progenitor-impl/tests/output/buildomat.out index 591fcc1..406b320 100644 --- a/progenitor-impl/tests/output/buildomat.out +++ b/progenitor-impl/tests/output/buildomat.out @@ -1,23 +1,4 @@ -use anyhow::Result; -mod progenitor_support { - use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; - #[allow(dead_code)] - const PATH_SET: &AsciiSet = &CONTROLS - .add(b' ') - .add(b'"') - .add(b'#') - .add(b'<') - .add(b'>') - .add(b'?') - .add(b'`') - .add(b'{') - .add(b'}'); - #[allow(dead_code)] - pub(crate) fn encode_path(pc: &str) -> String { - utf8_percent_encode(pc, PATH_SET).to_string() - } -} - +pub use progenitor_client::{Error, ResponseValue}; pub mod types { use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -182,55 +163,73 @@ impl Client { } #[doc = "control_hold: POST /v1/control/hold"] - pub async fn control_hold<'a>(&'a self) -> Result<()> { + pub async fn control_hold<'a>(&'a self) -> Result, Error<()>> { let url = format!("{}/v1/control/hold", self.baseurl,); let request = self.client.post(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "control_resume: POST /v1/control/resume"] - pub async fn control_resume<'a>(&'a self) -> Result { + pub async fn control_resume<'a>(&'a self) -> Result, Error<()>> { let url = format!("{}/v1/control/resume", self.baseurl,); let request = self.client.post(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "task_get: GET /v1/task/{task}"] - pub async fn task_get<'a>(&'a self, task: &'a str) -> Result { + pub async fn task_get<'a>( + &'a self, + task: &'a str, + ) -> Result, Error<()>> { let url = format!( "{}/v1/task/{}", self.baseurl, - progenitor_support::encode_path(&task.to_string()), + progenitor_client::encode_path(&task.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "tasks_get: GET /v1/tasks"] - pub async fn tasks_get<'a>(&'a self) -> Result> { + pub async fn tasks_get<'a>(&'a self) -> Result>, Error<()>> { let url = format!("{}/v1/tasks", self.baseurl,); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "task_submit: POST /v1/tasks"] pub async fn task_submit<'a>( &'a self, body: &'a types::TaskSubmit, - ) -> Result { + ) -> Result, Error<()>> { let url = format!("{}/v1/tasks", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "task_events_get: GET /v1/tasks/{task}/events"] @@ -238,11 +237,11 @@ impl Client { &'a self, task: &'a str, minseq: Option, - ) -> Result> { + ) -> Result>, Error<()>> { let url = format!( "{}/v1/tasks/{}/events", self.baseurl, - progenitor_support::encode_path(&task.to_string()), + progenitor_client::encode_path(&task.to_string()), ); let mut query = Vec::new(); if let Some(v) = &minseq { @@ -251,21 +250,30 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "task_outputs_get: GET /v1/tasks/{task}/outputs"] - pub async fn task_outputs_get<'a>(&'a self, task: &'a str) -> Result> { + pub async fn task_outputs_get<'a>( + &'a self, + task: &'a str, + ) -> Result>, Error<()>> { let url = format!( "{}/v1/tasks/{}/outputs", self.baseurl, - progenitor_support::encode_path(&task.to_string()), + progenitor_client::encode_path(&task.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "task_output_download: GET /v1/tasks/{task}/outputs/{output}"] @@ -273,59 +281,76 @@ impl Client { &'a self, task: &'a str, output: &'a str, - ) -> Result { + ) -> Result> { let url = format!( "{}/v1/tasks/{}/outputs/{}", self.baseurl, - progenitor_support::encode_path(&task.to_string()), - progenitor_support::encode_path(&output.to_string()), + progenitor_client::encode_path(&task.to_string()), + progenitor_client::encode_path(&output.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 200..=299 => Ok(response), + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "user_create: POST /v1/users"] pub async fn user_create<'a>( &'a self, body: &'a types::UserCreate, - ) -> Result { + ) -> Result, Error<()>> { let url = format!("{}/v1/users", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "whoami: GET /v1/whoami"] - pub async fn whoami<'a>(&'a self) -> Result { + pub async fn whoami<'a>(&'a self) -> Result, Error<()>> { let url = format!("{}/v1/whoami", self.baseurl,); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "worker_bootstrap: POST /v1/worker/bootstrap"] pub async fn worker_bootstrap<'a>( &'a self, body: &'a types::WorkerBootstrap, - ) -> Result { + ) -> Result, Error<()>> { let url = format!("{}/v1/worker/bootstrap", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "worker_ping: GET /v1/worker/ping"] - pub async fn worker_ping<'a>(&'a self) -> Result { + pub async fn worker_ping<'a>( + &'a self, + ) -> Result, Error<()>> { let url = format!("{}/v1/worker/ping", self.baseurl,); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "worker_task_append: POST /v1/worker/task/{task}/append"] @@ -333,16 +358,19 @@ impl Client { &'a self, task: &'a str, body: &'a types::WorkerAppendTask, - ) -> Result { + ) -> Result, Error<()>> { let url = format!( "{}/v1/worker/task/{}/append", self.baseurl, - progenitor_support::encode_path(&task.to_string()), + progenitor_client::encode_path(&task.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "worker_task_upload_chunk: POST /v1/worker/task/{task}/chunk"] @@ -350,16 +378,19 @@ impl Client { &'a self, task: &'a str, body: B, - ) -> Result { + ) -> Result, Error<()>> { let url = format!( "{}/v1/worker/task/{}/chunk", self.baseurl, - progenitor_support::encode_path(&task.to_string()), + progenitor_client::encode_path(&task.to_string()), ); let request = self.client.post(url).body(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "worker_task_complete: POST /v1/worker/task/{task}/complete"] @@ -367,16 +398,19 @@ impl Client { &'a self, task: &'a str, body: &'a types::WorkerCompleteTask, - ) -> Result { + ) -> Result, Error<()>> { let url = format!( "{}/v1/worker/task/{}/complete", self.baseurl, - progenitor_support::encode_path(&task.to_string()), + progenitor_client::encode_path(&task.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "worker_task_add_output: POST /v1/worker/task/{task}/output"] @@ -384,33 +418,44 @@ impl Client { &'a self, task: &'a str, body: &'a types::WorkerAddOutput, - ) -> Result { + ) -> Result, Error<()>> { let url = format!( "{}/v1/worker/task/{}/output", self.baseurl, - progenitor_support::encode_path(&task.to_string()), + progenitor_client::encode_path(&task.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "workers_list: GET /v1/workers"] - pub async fn workers_list<'a>(&'a self) -> Result { + pub async fn workers_list<'a>( + &'a self, + ) -> Result, Error<()>> { let url = format!("{}/v1/workers", self.baseurl,); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "workers_recycle: POST /v1/workers/recycle"] - pub async fn workers_recycle<'a>(&'a self) -> Result { + pub async fn workers_recycle<'a>(&'a self) -> Result, Error<()>> { let url = format!("{}/v1/workers/recycle", self.baseurl,); let request = self.client.post(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 200u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } } } diff --git a/progenitor-impl/tests/output/keeper.out b/progenitor-impl/tests/output/keeper.out index fc9d179..1fb062b 100644 --- a/progenitor-impl/tests/output/keeper.out +++ b/progenitor-impl/tests/output/keeper.out @@ -1,23 +1,4 @@ -use anyhow::Result; -mod progenitor_support { - use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; - #[allow(dead_code)] - const PATH_SET: &AsciiSet = &CONTROLS - .add(b' ') - .add(b'"') - .add(b'#') - .add(b'<') - .add(b'>') - .add(b'?') - .add(b'`') - .add(b'{') - .add(b'}'); - #[allow(dead_code)] - pub(crate) fn encode_path(pc: &str) -> String { - utf8_percent_encode(pc, PATH_SET).to_string() - } -} - +pub use progenitor_client::{Error, ResponseValue}; pub mod types { use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -123,65 +104,88 @@ impl Client { } #[doc = "enrol: POST /enrol"] - pub async fn enrol<'a>(&'a self, body: &'a types::EnrolBody) -> Result { + pub async fn enrol<'a>( + &'a self, + body: &'a types::EnrolBody, + ) -> Result, Error<()>> { let url = format!("{}/enrol", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 201u16 => Ok(ResponseValue::empty(response)), + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "global_jobs: GET /global/jobs"] - pub async fn global_jobs<'a>(&'a self) -> Result { + pub async fn global_jobs<'a>( + &'a self, + ) -> Result, Error<()>> { let url = format!("{}/global/jobs", self.baseurl,); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "ping: GET /ping"] - pub async fn ping<'a>(&'a self) -> Result { + pub async fn ping<'a>(&'a self) -> Result, Error<()>> { let url = format!("{}/ping", self.baseurl,); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "report_finish: POST /report/finish"] pub async fn report_finish<'a>( &'a self, body: &'a types::ReportFinishBody, - ) -> Result { + ) -> Result, Error<()>> { let url = format!("{}/report/finish", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "report_output: POST /report/output"] pub async fn report_output<'a>( &'a self, body: &'a types::ReportOutputBody, - ) -> Result { + ) -> Result, Error<()>> { let url = format!("{}/report/output", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "report_start: POST /report/start"] pub async fn report_start<'a>( &'a self, body: &'a types::ReportStartBody, - ) -> Result { + ) -> Result, Error<()>> { let url = format!("{}/report/start", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } } } diff --git a/progenitor-impl/tests/output/nexus.out b/progenitor-impl/tests/output/nexus.out index bd4ca74..e13731f 100644 --- a/progenitor-impl/tests/output/nexus.out +++ b/progenitor-impl/tests/output/nexus.out @@ -1,23 +1,4 @@ -use anyhow::Result; -mod progenitor_support { - use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; - #[allow(dead_code)] - const PATH_SET: &AsciiSet = &CONTROLS - .add(b' ') - .add(b'"') - .add(b'#') - .add(b'<') - .add(b'>') - .add(b'?') - .add(b'`') - .add(b'{') - .add(b'}'); - #[allow(dead_code)] - pub(crate) fn encode_path(pc: &str) -> String { - utf8_percent_encode(pc, PATH_SET).to_string() - } -} - +pub use progenitor_client::{Error, ResponseValue}; pub mod types { use serde::{Deserialize, Serialize}; #[doc = "A count of bytes, typically used either for memory or storage capacity\n\nThe maximum supported byte count is [`i64::MAX`]. This makes it somewhat inconvenient to define constructors: a u32 constructor can be infallible, but an i64 constructor can fail (if the value is negative) and a u64 constructor can fail (if the value is larger than i64::MAX). We provide all of these for consumers' convenience."] @@ -65,26 +46,18 @@ pub mod types { pub struct Disk { #[doc = "human-readable free-form text about a resource"] pub description: String, - #[serde(rename = "devicePath")] pub device_path: String, #[doc = "unique, immutable, system-controlled identifier for each resource"] pub id: uuid::Uuid, pub name: Name, - #[serde(rename = "projectId")] pub project_id: uuid::Uuid, pub size: ByteCount, - #[serde( - rename = "snapshotId", - default, - skip_serializing_if = "Option::is_none" - )] + #[serde(default, skip_serializing_if = "Option::is_none")] pub snapshot_id: Option, pub state: DiskState, #[doc = "timestamp when this resource was created"] - #[serde(rename = "timeCreated")] pub time_created: chrono::DateTime, #[doc = "timestamp when this resource was last modified"] - #[serde(rename = "timeModified")] pub time_modified: chrono::DateTime, } @@ -95,11 +68,7 @@ pub mod types { pub name: Name, pub size: ByteCount, #[doc = "id for snapshot from which the Disk should be created, if any"] - #[serde( - rename = "snapshotId", - default, - skip_serializing_if = "Option::is_none" - )] + #[serde(default, skip_serializing_if = "Option::is_none")] pub snapshot_id: Option, } @@ -138,6 +107,15 @@ pub mod types { Faulted, } + #[doc = "Body of an HTTP response for an `HttpError`. This type can be used to deserialize an HTTP response corresponding to an error in order to access the error code, message, etc."] + #[derive(Serialize, Deserialize, Debug, Clone)] + pub struct Error { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_code: Option, + pub message: String, + pub request_id: String, + } + #[doc = "The name and type information for a field of a timeseries schema."] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct FieldSchema { @@ -199,22 +177,6 @@ pub mod types { } } - #[doc = "Identity-related metadata that's included in nearly all public API objects"] - #[derive(Serialize, Deserialize, Debug, Clone)] - pub struct IdentityMetadata { - #[doc = "human-readable free-form text about a resource"] - pub description: String, - #[doc = "unique, immutable, system-controlled identifier for each resource"] - pub id: uuid::Uuid, - pub name: Name, - #[doc = "timestamp when this resource was created"] - #[serde(rename = "timeCreated")] - pub time_created: chrono::DateTime, - #[doc = "timestamp when this resource was last modified"] - #[serde(rename = "timeModified")] - pub time_modified: chrono::DateTime, - } - #[doc = "Client view of an [`Instance`]"] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Instance { @@ -228,17 +190,12 @@ pub mod types { pub name: Name, pub ncpus: InstanceCpuCount, #[doc = "id for the project containing this Instance"] - #[serde(rename = "projectId")] pub project_id: uuid::Uuid, - #[serde(rename = "runState")] pub run_state: InstanceState, #[doc = "timestamp when this resource was created"] - #[serde(rename = "timeCreated")] pub time_created: chrono::DateTime, #[doc = "timestamp when this resource was last modified"] - #[serde(rename = "timeModified")] pub time_modified: chrono::DateTime, - #[serde(rename = "timeRunStateUpdated")] pub time_run_state_updated: chrono::DateTime, } @@ -262,6 +219,12 @@ pub mod types { pub ncpus: InstanceCpuCount, } + #[doc = "Migration parameters for an [`Instance`]"] + #[derive(Serialize, Deserialize, Debug, Clone)] + pub struct InstanceMigrate { + pub dst_sled_uuid: uuid::Uuid, + } + #[doc = "A single page of results"] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct InstanceResultsPage { @@ -287,6 +250,8 @@ pub mod types { Stopped, #[serde(rename = "rebooting")] Rebooting, + #[serde(rename = "migrating")] + Migrating, #[serde(rename = "repairing")] Repairing, #[serde(rename = "failed")] @@ -304,6 +269,7 @@ pub mod types { InstanceState::Stopping => "stopping".to_string(), InstanceState::Stopped => "stopped".to_string(), InstanceState::Rebooting => "rebooting".to_string(), + InstanceState::Migrating => "migrating".to_string(), InstanceState::Repairing => "repairing".to_string(), InstanceState::Failed => "failed".to_string(), InstanceState::Destroyed => "destroyed".to_string(), @@ -405,14 +371,22 @@ pub mod types { #[doc = "A `NetworkInterface` represents a virtual network interface device."] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct NetworkInterface { - pub identity: IdentityMetadata, + #[doc = "human-readable free-form text about a resource"] + pub description: String, + #[doc = "unique, immutable, system-controlled identifier for each resource"] + pub id: uuid::Uuid, #[doc = "The Instance to which the interface belongs."] pub instance_id: uuid::Uuid, #[doc = "The IP address assigned to this interface."] pub ip: String, pub mac: MacAddr, + pub name: Name, #[doc = "The subnet to which the interface belongs."] pub subnet_id: uuid::Uuid, + #[doc = "timestamp when this resource was created"] + pub time_created: chrono::DateTime, + #[doc = "timestamp when this resource was last modified"] + pub time_modified: chrono::DateTime, #[doc = "The VPC to which the interface belongs."] pub vpc_id: uuid::Uuid, } @@ -436,10 +410,8 @@ pub mod types { pub id: uuid::Uuid, pub name: Name, #[doc = "timestamp when this resource was created"] - #[serde(rename = "timeCreated")] pub time_created: chrono::DateTime, #[doc = "timestamp when this resource was last modified"] - #[serde(rename = "timeModified")] pub time_modified: chrono::DateTime, } @@ -477,13 +449,10 @@ pub mod types { #[doc = "unique, immutable, system-controlled identifier for each resource"] pub id: uuid::Uuid, pub name: Name, - #[serde(rename = "organizationId")] pub organization_id: uuid::Uuid, #[doc = "timestamp when this resource was created"] - #[serde(rename = "timeCreated")] pub time_created: chrono::DateTime, #[doc = "timestamp when this resource was last modified"] - #[serde(rename = "timeModified")] pub time_modified: chrono::DateTime, } @@ -516,7 +485,15 @@ pub mod types { #[doc = "Client view of an [`Rack`]"] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Rack { - pub identity: IdentityMetadata, + #[doc = "human-readable free-form text about a resource"] + pub description: String, + #[doc = "unique, immutable, system-controlled identifier for each resource"] + pub id: uuid::Uuid, + pub name: Name, + #[doc = "timestamp when this resource was created"] + pub time_created: chrono::DateTime, + #[doc = "timestamp when this resource was last modified"] + pub time_modified: chrono::DateTime, } #[doc = "A single page of results"] @@ -578,19 +555,27 @@ pub mod types { Subnet(Name), #[serde(rename = "instance")] Instance(Name), - #[serde(rename = "internetGateway")] + #[serde(rename = "internet_gateway")] InternetGateway(Name), } #[doc = "A route defines a rule that governs where traffic should be sent based on its destination."] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RouterRoute { + #[doc = "human-readable free-form text about a resource"] + pub description: String, pub destination: RouteDestination, - pub identity: IdentityMetadata, + #[doc = "unique, immutable, system-controlled identifier for each resource"] + pub id: uuid::Uuid, pub kind: RouterRouteKind, + pub name: Name, #[doc = "The VPC Router to which the route belongs."] pub router_id: uuid::Uuid, pub target: RouteTarget, + #[doc = "timestamp when this resource was created"] + pub time_created: chrono::DateTime, + #[doc = "timestamp when this resource was last modified"] + pub time_modified: chrono::DateTime, } #[doc = "Create-time parameters for a [`RouterRoute`]"] @@ -605,19 +590,23 @@ pub mod types { #[doc = "The classification of a [`RouterRoute`] as defined by the system. The kind determines certain attributes such as if the route is modifiable and describes how or where the route was created.\n\nSee [RFD-21](https://rfd.shared.oxide.computer/rfd/0021#concept-router) for more context"] #[derive(Serialize, Deserialize, Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] pub enum RouterRouteKind { + #[serde(rename = "default")] Default, + #[serde(rename = "vpc_subnet")] VpcSubnet, + #[serde(rename = "vpc_peering")] VpcPeering, + #[serde(rename = "custom")] Custom, } impl ToString for RouterRouteKind { fn to_string(&self) -> String { match self { - RouterRouteKind::Default => "Default".to_string(), - RouterRouteKind::VpcSubnet => "VpcSubnet".to_string(), - RouterRouteKind::VpcPeering => "VpcPeering".to_string(), - RouterRouteKind::Custom => "Custom".to_string(), + RouterRouteKind::Default => "default".to_string(), + RouterRouteKind::VpcSubnet => "vpc_subnet".to_string(), + RouterRouteKind::VpcPeering => "vpc_peering".to_string(), + RouterRouteKind::Custom => "custom".to_string(), } } } @@ -652,15 +641,15 @@ pub mod types { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "error")] pub enum SagaErrorInfo { - #[serde(rename = "actionFailed")] + #[serde(rename = "action_failed")] ActionFailed { source_error: serde_json::Value }, - #[serde(rename = "deserializeFailed")] + #[serde(rename = "deserialize_failed")] DeserializeFailed { message: String }, - #[serde(rename = "injectedError")] + #[serde(rename = "injected_error")] InjectedError, - #[serde(rename = "serializeFailed")] + #[serde(rename = "serialize_failed")] SerializeFailed { message: String }, - #[serde(rename = "subsagaCreateFailed")] + #[serde(rename = "subsaga_create_failed")] SubsagaCreateFailed { message: String }, } @@ -688,6 +677,12 @@ pub mod types { }, } + #[doc = "Client view of currently authed user."] + #[derive(Serialize, Deserialize, Debug, Clone)] + pub struct SessionUser { + pub id: uuid::Uuid, + } + #[doc = "Client view of an [`Sled`]"] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Sled { @@ -696,13 +691,10 @@ pub mod types { #[doc = "unique, immutable, system-controlled identifier for each resource"] pub id: uuid::Uuid, pub name: Name, - #[serde(rename = "serviceAddress")] pub service_address: String, #[doc = "timestamp when this resource was created"] - #[serde(rename = "timeCreated")] pub time_created: chrono::DateTime, #[doc = "timestamp when this resource was last modified"] - #[serde(rename = "timeModified")] pub time_modified: chrono::DateTime, } @@ -754,10 +746,8 @@ pub mod types { pub id: uuid::Uuid, pub name: Name, #[doc = "timestamp when this resource was created"] - #[serde(rename = "timeCreated")] pub time_created: chrono::DateTime, #[doc = "timestamp when this resource was last modified"] - #[serde(rename = "timeModified")] pub time_modified: chrono::DateTime, } @@ -776,22 +766,17 @@ pub mod types { pub struct Vpc { #[doc = "human-readable free-form text about a resource"] pub description: String, - #[serde(rename = "dnsName")] pub dns_name: Name, #[doc = "unique, immutable, system-controlled identifier for each resource"] pub id: uuid::Uuid, pub name: Name, #[doc = "id for the project containing this VPC"] - #[serde(rename = "projectId")] pub project_id: uuid::Uuid, #[doc = "id for the system router where subnet default routes are registered"] - #[serde(rename = "systemRouterId")] pub system_router_id: uuid::Uuid, #[doc = "timestamp when this resource was created"] - #[serde(rename = "timeCreated")] pub time_created: chrono::DateTime, #[doc = "timestamp when this resource was last modified"] - #[serde(rename = "timeModified")] pub time_modified: chrono::DateTime, } @@ -799,7 +784,6 @@ pub mod types { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VpcCreate { pub description: String, - #[serde(rename = "dnsName")] pub dns_name: Name, pub name: Name, } @@ -808,14 +792,22 @@ pub mod types { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VpcFirewallRule { pub action: VpcFirewallRuleAction, + #[doc = "human-readable free-form text about a resource"] + pub description: String, pub direction: VpcFirewallRuleDirection, pub filters: VpcFirewallRuleFilter, - pub identity: IdentityMetadata, + #[doc = "unique, immutable, system-controlled identifier for each resource"] + pub id: uuid::Uuid, + pub name: Name, #[doc = "the relative priority of this rule"] pub priority: u16, pub status: VpcFirewallRuleStatus, #[doc = "list of sets of instances that the rule applies to"] pub targets: Vec, + #[doc = "timestamp when this resource was created"] + pub time_created: chrono::DateTime, + #[doc = "timestamp when this resource was last modified"] + pub time_modified: chrono::DateTime, } #[derive(Serialize, Deserialize, Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] @@ -877,7 +869,7 @@ pub mod types { Instance(Name), #[serde(rename = "ip")] Ip(String), - #[serde(rename = "internetGateway")] + #[serde(rename = "internet_gateway")] InternetGateway(Name), } @@ -988,8 +980,16 @@ pub mod types { #[doc = "A VPC router defines a series of rules that indicate where traffic should be sent depending on its destination."] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VpcRouter { - pub identity: IdentityMetadata, + #[doc = "human-readable free-form text about a resource"] + pub description: String, + #[doc = "unique, immutable, system-controlled identifier for each resource"] + pub id: uuid::Uuid, pub kind: VpcRouterKind, + pub name: Name, + #[doc = "timestamp when this resource was created"] + pub time_created: chrono::DateTime, + #[doc = "timestamp when this resource was last modified"] + pub time_modified: chrono::DateTime, #[doc = "The VPC to which the router belongs."] pub vpc_id: uuid::Uuid, } @@ -1040,7 +1040,10 @@ pub mod types { #[doc = "A VPC subnet represents a logical grouping for instances that allows network traffic between them, within a IPv4 subnetwork or optionall an IPv6 subnetwork."] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VpcSubnet { - pub identity: IdentityMetadata, + #[doc = "human-readable free-form text about a resource"] + pub description: String, + #[doc = "unique, immutable, system-controlled identifier for each resource"] + pub id: uuid::Uuid, #[serde( rename = "ipv4_block", default, @@ -1053,6 +1056,11 @@ pub mod types { skip_serializing_if = "Option::is_none" )] pub ipv_6_block: Option, + pub name: Name, + #[doc = "timestamp when this resource was created"] + pub time_created: chrono::DateTime, + #[doc = "timestamp when this resource was last modified"] + pub time_modified: chrono::DateTime, #[doc = "The VPC to which the subnet belongs."] pub vpc_id: uuid::Uuid, } @@ -1061,9 +1069,17 @@ pub mod types { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VpcSubnetCreate { pub description: String, - #[serde(rename = "ipv4Block", default, skip_serializing_if = "Option::is_none")] + #[serde( + rename = "ipv4_block", + default, + skip_serializing_if = "Option::is_none" + )] pub ipv_4_block: Option, - #[serde(rename = "ipv6Block", default, skip_serializing_if = "Option::is_none")] + #[serde( + rename = "ipv6_block", + default, + skip_serializing_if = "Option::is_none" + )] pub ipv_6_block: Option, pub name: Name, } @@ -1083,9 +1099,17 @@ pub mod types { pub struct VpcSubnetUpdate { #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, - #[serde(rename = "ipv4Block", default, skip_serializing_if = "Option::is_none")] + #[serde( + rename = "ipv4_block", + default, + skip_serializing_if = "Option::is_none" + )] pub ipv_4_block: Option, - #[serde(rename = "ipv6Block", default, skip_serializing_if = "Option::is_none")] + #[serde( + rename = "ipv6_block", + default, + skip_serializing_if = "Option::is_none" + )] pub ipv_6_block: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option, @@ -1096,7 +1120,7 @@ pub mod types { pub struct VpcUpdate { #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, - #[serde(rename = "dnsName", default, skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub dns_name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option, @@ -1135,13 +1159,13 @@ impl Client { &self.client } - #[doc = "List racks in the system.\n\nhardware_racks_get: GET /hardware/racks"] + #[doc = "\n\nhardware_racks_get: GET /hardware/racks"] pub async fn hardware_racks_get<'a>( &'a self, limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!("{}/hardware/racks", self.baseurl,); let mut query = Vec::new(); if let Some(v) = &limit { @@ -1158,21 +1182,31 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List racks in the system.\n\nreturns a Stream by making successive calls to hardware_racks_get"] + #[doc = "\n\nreturns a Stream by making successive calls to hardware_racks_get"] pub fn hardware_racks_get_stream<'a>( &'a self, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.hardware_racks_get(limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -1180,6 +1214,7 @@ impl Client { } else { self.hardware_racks_get(None, state.as_deref(), None) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -1195,29 +1230,38 @@ impl Client { .boxed() } - #[doc = "Fetch information about a particular rack.\n\nhardware_racks_get_rack: GET /hardware/racks/{rack_id}"] + #[doc = "\n\nhardware_racks_get_rack: GET /hardware/racks/{rack_id}"] pub async fn hardware_racks_get_rack<'a>( &'a self, rack_id: &'a uuid::Uuid, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/hardware/racks/{}", self.baseurl, - progenitor_support::encode_path(&rack_id.to_string()), + progenitor_client::encode_path(&rack_id.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List sleds in the system.\n\nhardware_sleds_get: GET /hardware/sleds"] + #[doc = "\n\nhardware_sleds_get: GET /hardware/sleds"] pub async fn hardware_sleds_get<'a>( &'a self, limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!("{}/hardware/sleds", self.baseurl,); let mut query = Vec::new(); if let Some(v) = &limit { @@ -1234,21 +1278,31 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List sleds in the system.\n\nreturns a Stream by making successive calls to hardware_sleds_get"] + #[doc = "\n\nreturns a Stream by making successive calls to hardware_sleds_get"] pub fn hardware_sleds_get_stream<'a>( &'a self, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.hardware_sleds_get(limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -1256,6 +1310,7 @@ impl Client { } else { self.hardware_sleds_get(None, state.as_deref(), None) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -1271,50 +1326,65 @@ impl Client { .boxed() } - #[doc = "Fetch information about a sled in the system.\n\nhardware_sleds_get_sled: GET /hardware/sleds/{sled_id}"] + #[doc = "\n\nhardware_sleds_get_sled: GET /hardware/sleds/{sled_id}"] pub async fn hardware_sleds_get_sled<'a>( &'a self, sled_id: &'a uuid::Uuid, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/hardware/sleds/{}", self.baseurl, - progenitor_support::encode_path(&sled_id.to_string()), + progenitor_client::encode_path(&sled_id.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "spoof_login: POST /login"] pub async fn spoof_login<'a>( &'a self, body: &'a types::LoginParams, - ) -> Result { + ) -> Result, Error<()>> { let url = format!("{}/login", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 200..=299 => Ok(ResponseValue::empty(response)), + _ => Err(Error::ErrorResponse(ResponseValue::empty(response))), + } } #[doc = "logout: POST /logout"] - pub async fn logout<'a>(&'a self) -> Result { + pub async fn logout<'a>(&'a self) -> Result, Error<()>> { let url = format!("{}/logout", self.baseurl,); let request = self.client.post(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 200..=299 => Ok(ResponseValue::empty(response)), + _ => Err(Error::ErrorResponse(ResponseValue::empty(response))), + } } - #[doc = "List all organizations.\n\norganizations_get: GET /organizations"] + #[doc = "\n\norganizations_get: GET /organizations"] pub async fn organizations_get<'a>( &'a self, limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!("{}/organizations", self.baseurl,); let mut query = Vec::new(); if let Some(v) = &limit { @@ -1331,21 +1401,32 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List all organizations.\n\nreturns a Stream by making successive calls to organizations_get"] + #[doc = "\n\nreturns a Stream by making successive calls to organizations_get"] pub fn organizations_get_stream<'a>( &'a self, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ + { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.organizations_get(limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -1353,6 +1434,7 @@ impl Client { } else { self.organizations_get(None, state.as_deref(), None) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -1368,79 +1450,115 @@ impl Client { .boxed() } - #[doc = "Create a new organization.\n\norganizations_post: POST /organizations"] + #[doc = "\n\norganizations_post: POST /organizations"] pub async fn organizations_post<'a>( &'a self, body: &'a types::OrganizationCreate, - ) -> Result { + ) -> Result, Error> { let url = format!("{}/organizations", self.baseurl,); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Fetch a specific organization\n\norganizations_get_organization: GET /organizations/{organization_name}"] + #[doc = "\n\norganizations_get_organization: GET /organizations/{organization_name}"] pub async fn organizations_get_organization<'a>( &'a self, organization_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Update a specific organization.\n * TODO-correctness: Is it valid for PUT to accept application/json that's a subset of what the resource actually represents? If not, is that a problem? (HTTP may require that this be idempotent.) If so, can we get around that having this be a slightly different content-type (e.g., \"application/json-patch\")? We should see what other APIs do.\n\norganizations_put_organization: PUT /organizations/{organization_name}"] + #[doc = "\n\norganizations_put_organization: PUT /organizations/{organization_name}"] pub async fn organizations_put_organization<'a>( &'a self, organization_name: &'a types::Name, body: &'a types::OrganizationUpdate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), ); let request = self.client.put(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Delete a specific organization.\n\norganizations_delete_organization: DELETE /organizations/{organization_name}"] + #[doc = "\n\norganizations_delete_organization: DELETE /organizations/{organization_name}"] pub async fn organizations_delete_organization<'a>( &'a self, organization_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), ); let request = self.client.delete(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List all projects.\n\norganization_projects_get: GET /organizations/{organization_name}/projects"] + #[doc = "\n\norganization_projects_get: GET /organizations/{organization_name}/projects"] pub async fn organization_projects_get<'a>( &'a self, organization_name: &'a types::Name, limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -1457,22 +1575,32 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List all projects.\n\nreturns a Stream by making successive calls to organization_projects_get"] + #[doc = "\n\nreturns a Stream by making successive calls to organization_projects_get"] pub fn organization_projects_get_stream<'a>( &'a self, organization_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.organization_projects_get(organization_name, limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -1485,6 +1613,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -1500,79 +1629,115 @@ impl Client { .boxed() } - #[doc = "Create a new project.\n\norganization_projects_post: POST /organizations/{organization_name}/projects"] + #[doc = "\n\norganization_projects_post: POST /organizations/{organization_name}/projects"] pub async fn organization_projects_post<'a>( &'a self, organization_name: &'a types::Name, body: &'a types::ProjectCreate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Fetch a specific project\n\norganization_projects_get_project: GET /organizations/{organization_name}/projects/{project_name}"] + #[doc = "\n\norganization_projects_get_project: GET /organizations/{organization_name}/projects/{project_name}"] pub async fn organization_projects_get_project<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Update a specific project.\n * TODO-correctness: Is it valid for PUT to accept application/json that's a subset of what the resource actually represents? If not, is that a problem? (HTTP may require that this be idempotent.) If so, can we get around that having this be a slightly different content-type (e.g., \"application/json-patch\")? We should see what other APIs do.\n\norganization_projects_put_project: PUT /organizations/{organization_name}/projects/{project_name}"] + #[doc = "\n\norganization_projects_put_project: PUT /organizations/{organization_name}/projects/{project_name}"] pub async fn organization_projects_put_project<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, body: &'a types::ProjectUpdate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let request = self.client.put(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Delete a specific project.\n\norganization_projects_delete_project: DELETE /organizations/{organization_name}/projects/{project_name}"] + #[doc = "\n\norganization_projects_delete_project: DELETE /organizations/{organization_name}/projects/{project_name}"] pub async fn organization_projects_delete_project<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let request = self.client.delete(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List disks in a project.\n\nproject_disks_get: GET /organizations/{organization_name}/projects/{project_name}/disks"] + #[doc = "\n\nproject_disks_get: GET /organizations/{organization_name}/projects/{project_name}/disks"] pub async fn project_disks_get<'a>( &'a self, organization_name: &'a types::Name, @@ -1580,12 +1745,12 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/disks", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -1602,23 +1767,33 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List disks in a project.\n\nreturns a Stream by making successive calls to project_disks_get"] + #[doc = "\n\nreturns a Stream by making successive calls to project_disks_get"] pub fn project_disks_get_stream<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.project_disks_get(organization_name, project_name, limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -1632,6 +1807,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -1647,66 +1823,93 @@ impl Client { .boxed() } - #[doc = "Create a disk in a project.\n * TODO-correctness See note about instance create. This should be async.\n\nproject_disks_post: POST /organizations/{organization_name}/projects/{project_name}/disks"] + #[doc = "\n\nproject_disks_post: POST /organizations/{organization_name}/projects/{project_name}/disks"] pub async fn project_disks_post<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, body: &'a types::DiskCreate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/disks", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Fetch a single disk in a project.\n\nproject_disks_get_disk: GET /organizations/{organization_name}/projects/{project_name}/disks/{disk_name}"] + #[doc = "\n\nproject_disks_get_disk: GET /organizations/{organization_name}/projects/{project_name}/disks/{disk_name}"] pub async fn project_disks_get_disk<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, disk_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/disks/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&disk_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&disk_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Delete a disk from a project.\n\nproject_disks_delete_disk: DELETE /organizations/{organization_name}/projects/{project_name}/disks/{disk_name}"] + #[doc = "\n\nproject_disks_delete_disk: DELETE /organizations/{organization_name}/projects/{project_name}/disks/{disk_name}"] pub async fn project_disks_delete_disk<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, disk_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/disks/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&disk_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&disk_name.to_string()), ); let request = self.client.delete(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List instances in a project.\n\nproject_instances_get: GET /organizations/{organization_name}/projects/{project_name}/instances"] + #[doc = "\n\nproject_instances_get: GET /organizations/{organization_name}/projects/{project_name}/instances"] pub async fn project_instances_get<'a>( &'a self, organization_name: &'a types::Name, @@ -1714,12 +1917,12 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -1736,23 +1939,34 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List instances in a project.\n\nreturns a Stream by making successive calls to project_instances_get"] + #[doc = "\n\nreturns a Stream by making successive calls to project_instances_get"] pub fn project_instances_get_stream<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ + { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.project_instances_get(organization_name, project_name, limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -1766,6 +1980,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -1781,66 +1996,93 @@ impl Client { .boxed() } - #[doc = "Create an instance in a project.\n * TODO-correctness This is supposed to be async. Is that right? We can create the instance immediately -- it's just not booted yet. Maybe the boot operation is what's a separate operation_id. What about the response code (201 Created vs 202 Accepted)? Is that orthogonal? Things can return a useful response, including an operation id, with either response code. Maybe a \"reboot\" operation would return a 202 Accepted because there's no actual resource created?\n\nproject_instances_post: POST /organizations/{organization_name}/projects/{project_name}/instances"] + #[doc = "\n\nproject_instances_post: POST /organizations/{organization_name}/projects/{project_name}/instances"] pub async fn project_instances_post<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, body: &'a types::InstanceCreate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Get an instance in a project.\n\nproject_instances_get_instance: GET /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}"] + #[doc = "\n\nproject_instances_get_instance: GET /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}"] pub async fn project_instances_get_instance<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, instance_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&instance_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Delete an instance from a project.\n\nproject_instances_delete_instance: DELETE /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}"] + #[doc = "\n\nproject_instances_delete_instance: DELETE /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}"] pub async fn project_instances_delete_instance<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, instance_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&instance_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), ); let request = self.client.delete(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List disks attached to this instance.\n\ninstance_disks_get: GET /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks"] + #[doc = "\n\ninstance_disks_get: GET /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks"] pub async fn instance_disks_get<'a>( &'a self, organization_name: &'a types::Name, @@ -1849,13 +2091,13 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances/{}/disks", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&instance_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -1872,11 +2114,20 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List disks attached to this instance.\n\nreturns a Stream by making successive calls to instance_disks_get"] + #[doc = "\n\nreturns a Stream by making successive calls to instance_disks_get"] pub fn instance_disks_get_stream<'a>( &'a self, organization_name: &'a types::Name, @@ -1884,7 +2135,7 @@ impl Client { instance_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; @@ -1897,6 +2148,7 @@ impl Client { sort_by, ) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -1911,6 +2163,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -1933,18 +2186,27 @@ impl Client { project_name: &'a types::Name, instance_name: &'a types::Name, body: &'a types::DiskIdentifier, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances/{}/disks/attach", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&instance_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 202u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } #[doc = "instance_disks_detach: POST /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/detach"] @@ -1954,81 +2216,147 @@ impl Client { project_name: &'a types::Name, instance_name: &'a types::Name, body: &'a types::DiskIdentifier, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances/{}/disks/detach", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&instance_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 202u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Reboot an instance.\n\nproject_instances_instance_reboot: POST /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/reboot"] + #[doc = "\n\nproject_instances_migrate_instance: POST /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/migrate"] + pub async fn project_instances_migrate_instance<'a>( + &'a self, + organization_name: &'a types::Name, + project_name: &'a types::Name, + instance_name: &'a types::Name, + body: &'a types::InstanceMigrate, + ) -> Result, Error> { + let url = format!( + "{}/organizations/{}/projects/{}/instances/{}/migrate", + self.baseurl, + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), + ); + let request = self.client.post(url).json(body).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + + #[doc = "\n\nproject_instances_instance_reboot: POST /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/reboot"] pub async fn project_instances_instance_reboot<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, instance_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances/{}/reboot", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&instance_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), ); let request = self.client.post(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 202u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Boot an instance.\n\nproject_instances_instance_start: POST /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/start"] + #[doc = "\n\nproject_instances_instance_start: POST /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/start"] pub async fn project_instances_instance_start<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, instance_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances/{}/start", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&instance_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), ); let request = self.client.post(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 202u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Halt an instance.\n\nproject_instances_instance_stop: POST /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop"] + #[doc = "\n\nproject_instances_instance_stop: POST /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop"] pub async fn project_instances_instance_stop<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, instance_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/instances/{}/stop", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&instance_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&instance_name.to_string()), ); let request = self.client.post(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 202u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List VPCs in a project.\n\nproject_vpcs_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs"] + #[doc = "\n\nproject_vpcs_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs"] pub async fn project_vpcs_get<'a>( &'a self, organization_name: &'a types::Name, @@ -2036,12 +2364,12 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -2058,23 +2386,33 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List VPCs in a project.\n\nreturns a Stream by making successive calls to project_vpcs_get"] + #[doc = "\n\nreturns a Stream by making successive calls to project_vpcs_get"] pub fn project_vpcs_get_stream<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.project_vpcs_get(organization_name, project_name, limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -2088,6 +2426,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -2103,87 +2442,123 @@ impl Client { .boxed() } - #[doc = "Create a VPC in a project.\n\nproject_vpcs_post: POST /organizations/{organization_name}/projects/{project_name}/vpcs"] + #[doc = "\n\nproject_vpcs_post: POST /organizations/{organization_name}/projects/{project_name}/vpcs"] pub async fn project_vpcs_post<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, body: &'a types::VpcCreate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Get a VPC in a project.\n\nproject_vpcs_get_vpc: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}"] + #[doc = "\n\nproject_vpcs_get_vpc: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}"] pub async fn project_vpcs_get_vpc<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Update a VPC.\n\nproject_vpcs_put_vpc: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}"] + #[doc = "\n\nproject_vpcs_put_vpc: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}"] pub async fn project_vpcs_put_vpc<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, body: &'a types::VpcUpdate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let request = self.client.put(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Delete a vpc from a project.\n\nproject_vpcs_delete_vpc: DELETE /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}"] + #[doc = "\n\nproject_vpcs_delete_vpc: DELETE /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}"] pub async fn project_vpcs_delete_vpc<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let request = self.client.delete(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List firewall rules for a VPC.\n\nvpc_firewall_rules_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/firewall/rules"] + #[doc = "\n\nvpc_firewall_rules_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/firewall/rules"] pub async fn vpc_firewall_rules_get<'a>( &'a self, organization_name: &'a types::Name, @@ -2192,13 +2567,13 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/firewall/rules", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -2215,11 +2590,20 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List firewall rules for a VPC.\n\nreturns a Stream by making successive calls to vpc_firewall_rules_get"] + #[doc = "\n\nreturns a Stream by making successive calls to vpc_firewall_rules_get"] pub fn vpc_firewall_rules_get_stream<'a>( &'a self, organization_name: &'a types::Name, @@ -2227,7 +2611,8 @@ impl Client { vpc_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ + { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; @@ -2240,6 +2625,7 @@ impl Client { sort_by, ) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -2254,6 +2640,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -2269,28 +2656,37 @@ impl Client { .boxed() } - #[doc = "Replace the firewall rules for a VPC\n\nvpc_firewall_rules_put: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/firewall/rules"] + #[doc = "\n\nvpc_firewall_rules_put: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/firewall/rules"] pub async fn vpc_firewall_rules_put<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, body: &'a types::VpcFirewallRuleUpdateParams, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/firewall/rules", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let request = self.client.put(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List VPC Custom and System Routers\n\nvpc_routers_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers"] + #[doc = "\n\nvpc_routers_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers"] pub async fn vpc_routers_get<'a>( &'a self, organization_name: &'a types::Name, @@ -2299,13 +2695,13 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -2322,11 +2718,20 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List VPC Custom and System Routers\n\nreturns a Stream by making successive calls to vpc_routers_get"] + #[doc = "\n\nreturns a Stream by making successive calls to vpc_routers_get"] pub fn vpc_routers_get_stream<'a>( &'a self, organization_name: &'a types::Name, @@ -2334,7 +2739,8 @@ impl Client { vpc_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ + { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; @@ -2347,6 +2753,7 @@ impl Client { sort_by, ) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -2361,6 +2768,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -2376,50 +2784,68 @@ impl Client { .boxed() } - #[doc = "Create a VPC Router\n\nvpc_routers_post: POST /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers"] + #[doc = "\n\nvpc_routers_post: POST /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers"] pub async fn vpc_routers_post<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, body: &'a types::VpcRouterCreate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Get a VPC Router\n\nvpc_routers_get_router: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}"] + #[doc = "\n\nvpc_routers_get_router: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}"] pub async fn vpc_routers_get_router<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, router_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&router_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&router_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Update a VPC Router\n\nvpc_routers_put_router: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}"] + #[doc = "\n\nvpc_routers_put_router: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}"] pub async fn vpc_routers_put_router<'a>( &'a self, organization_name: &'a types::Name, @@ -2427,44 +2853,62 @@ impl Client { vpc_name: &'a types::Name, router_name: &'a types::Name, body: &'a types::VpcRouterUpdate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&router_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&router_name.to_string()), ); let request = self.client.put(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Delete a router from its VPC\n\nvpc_routers_delete_router: DELETE /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}"] + #[doc = "\n\nvpc_routers_delete_router: DELETE /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}"] pub async fn vpc_routers_delete_router<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, router_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&router_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&router_name.to_string()), ); let request = self.client.delete(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List a Router's routes\n\nrouters_routes_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes"] + #[doc = "\n\nrouters_routes_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes"] pub async fn routers_routes_get<'a>( &'a self, organization_name: &'a types::Name, @@ -2474,14 +2918,14 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers/{}/routes", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&router_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&router_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -2498,11 +2942,20 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List a Router's routes\n\nreturns a Stream by making successive calls to routers_routes_get"] + #[doc = "\n\nreturns a Stream by making successive calls to routers_routes_get"] pub fn routers_routes_get_stream<'a>( &'a self, organization_name: &'a types::Name, @@ -2511,7 +2964,8 @@ impl Client { router_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ + { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; @@ -2525,6 +2979,7 @@ impl Client { sort_by, ) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -2540,6 +2995,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -2555,7 +3011,7 @@ impl Client { .boxed() } - #[doc = "Create a VPC Router\n\nrouters_routes_post: POST /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes"] + #[doc = "\n\nrouters_routes_post: POST /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes"] pub async fn routers_routes_post<'a>( &'a self, organization_name: &'a types::Name, @@ -2563,22 +3019,31 @@ impl Client { vpc_name: &'a types::Name, router_name: &'a types::Name, body: &'a types::RouterRouteCreateParams, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers/{}/routes", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&router_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&router_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Get a VPC Router route\n\nrouters_routes_get_route: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}"] + #[doc = "\n\nrouters_routes_get_route: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}"] pub async fn routers_routes_get_route<'a>( &'a self, organization_name: &'a types::Name, @@ -2586,23 +3051,32 @@ impl Client { vpc_name: &'a types::Name, router_name: &'a types::Name, route_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers/{}/routes/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&router_name.to_string()), - progenitor_support::encode_path(&route_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&router_name.to_string()), + progenitor_client::encode_path(&route_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Update a Router route\n\nrouters_routes_put_route: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}"] + #[doc = "\n\nrouters_routes_put_route: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}"] pub async fn routers_routes_put_route<'a>( &'a self, organization_name: &'a types::Name, @@ -2611,23 +3085,32 @@ impl Client { router_name: &'a types::Name, route_name: &'a types::Name, body: &'a types::RouterRouteUpdateParams, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers/{}/routes/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&router_name.to_string()), - progenitor_support::encode_path(&route_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&router_name.to_string()), + progenitor_client::encode_path(&route_name.to_string()), ); let request = self.client.put(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Delete a route from its router\n\nrouters_routes_delete_route: DELETE /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}"] + #[doc = "\n\nrouters_routes_delete_route: DELETE /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}"] pub async fn routers_routes_delete_route<'a>( &'a self, organization_name: &'a types::Name, @@ -2635,23 +3118,32 @@ impl Client { vpc_name: &'a types::Name, router_name: &'a types::Name, route_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/routers/{}/routes/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&router_name.to_string()), - progenitor_support::encode_path(&route_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&router_name.to_string()), + progenitor_client::encode_path(&route_name.to_string()), ); let request = self.client.delete(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List subnets in a VPC.\n\nvpc_subnets_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets"] + #[doc = "\n\nvpc_subnets_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets"] pub async fn vpc_subnets_get<'a>( &'a self, organization_name: &'a types::Name, @@ -2660,13 +3152,13 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/subnets", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -2683,11 +3175,20 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List subnets in a VPC.\n\nreturns a Stream by making successive calls to vpc_subnets_get"] + #[doc = "\n\nreturns a Stream by making successive calls to vpc_subnets_get"] pub fn vpc_subnets_get_stream<'a>( &'a self, organization_name: &'a types::Name, @@ -2695,7 +3196,8 @@ impl Client { vpc_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ + { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; @@ -2708,6 +3210,7 @@ impl Client { sort_by, ) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -2722,6 +3225,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -2737,50 +3241,68 @@ impl Client { .boxed() } - #[doc = "Create a subnet in a VPC.\n\nvpc_subnets_post: POST /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets"] + #[doc = "\n\nvpc_subnets_post: POST /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets"] pub async fn vpc_subnets_post<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, body: &'a types::VpcSubnetCreate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/subnets", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), ); let request = self.client.post(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Get subnet in a VPC.\n\nvpc_subnets_get_subnet: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}"] + #[doc = "\n\nvpc_subnets_get_subnet: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}"] pub async fn vpc_subnets_get_subnet<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, subnet_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/subnets/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&subnet_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&subnet_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Update a VPC Subnet.\n\nvpc_subnets_put_subnet: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}"] + #[doc = "\n\nvpc_subnets_put_subnet: PUT /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}"] pub async fn vpc_subnets_put_subnet<'a>( &'a self, organization_name: &'a types::Name, @@ -2788,44 +3310,62 @@ impl Client { vpc_name: &'a types::Name, subnet_name: &'a types::Name, body: &'a types::VpcSubnetUpdate, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/subnets/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&subnet_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&subnet_name.to_string()), ); let request = self.client.put(url).json(body).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "Delete a subnet from a VPC.\n\nvpc_subnets_delete_subnet: DELETE /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}"] + #[doc = "\n\nvpc_subnets_delete_subnet: DELETE /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}"] pub async fn vpc_subnets_delete_subnet<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, vpc_name: &'a types::Name, subnet_name: &'a types::Name, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/subnets/{}", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&subnet_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&subnet_name.to_string()), ); let request = self.client.delete(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res) + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List IP addresses on a VPC subnet.\n\nsubnets_ips_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}/ips"] + #[doc = "\n\nsubnets_ips_get: GET /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}/ips"] pub async fn subnets_ips_get<'a>( &'a self, organization_name: &'a types::Name, @@ -2835,14 +3375,14 @@ impl Client { limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!( "{}/organizations/{}/projects/{}/vpcs/{}/subnets/{}/ips", self.baseurl, - progenitor_support::encode_path(&organization_name.to_string()), - progenitor_support::encode_path(&project_name.to_string()), - progenitor_support::encode_path(&vpc_name.to_string()), - progenitor_support::encode_path(&subnet_name.to_string()), + progenitor_client::encode_path(&organization_name.to_string()), + progenitor_client::encode_path(&project_name.to_string()), + progenitor_client::encode_path(&vpc_name.to_string()), + progenitor_client::encode_path(&subnet_name.to_string()), ); let mut query = Vec::new(); if let Some(v) = &limit { @@ -2859,11 +3399,20 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List IP addresses on a VPC subnet.\n\nreturns a Stream by making successive calls to subnets_ips_get"] + #[doc = "\n\nreturns a Stream by making successive calls to subnets_ips_get"] pub fn subnets_ips_get_stream<'a>( &'a self, organization_name: &'a types::Name, @@ -2872,7 +3421,8 @@ impl Client { subnet_name: &'a types::Name, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ + { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; @@ -2886,6 +3436,7 @@ impl Client { sort_by, ) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -2901,6 +3452,7 @@ impl Client { None, ) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -2916,12 +3468,12 @@ impl Client { .boxed() } - #[doc = "List the built-in roles\n\nroles_get: GET /roles"] + #[doc = "\n\nroles_get: GET /roles"] pub async fn roles_get<'a>( &'a self, limit: Option, page_token: Option<&'a str>, - ) -> Result { + ) -> Result, Error> { let url = format!("{}/roles", self.baseurl,); let mut query = Vec::new(); if let Some(v) = &limit { @@ -2934,20 +3486,30 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List the built-in roles\n\nreturns a Stream by making successive calls to roles_get"] + #[doc = "\n\nreturns a Stream by making successive calls to roles_get"] pub fn roles_get_stream<'a>( &'a self, limit: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.roles_get(limit, None) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -2955,6 +3517,7 @@ impl Client { } else { self.roles_get(None, state.as_deref()) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -2970,26 +3533,38 @@ impl Client { .boxed() } - #[doc = "Fetch a specific built-in role\n\nroles_get_role: GET /roles/{role_name}"] - pub async fn roles_get_role<'a>(&'a self, role_name: &'a str) -> Result { + #[doc = "\n\nroles_get_role: GET /roles/{role_name}"] + pub async fn roles_get_role<'a>( + &'a self, + role_name: &'a str, + ) -> Result, Error> { let url = format!( "{}/roles/{}", self.baseurl, - progenitor_support::encode_path(&role_name.to_string()), + progenitor_client::encode_path(&role_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List all sagas (for debugging)\n\nsagas_get: GET /sagas"] + #[doc = "\n\nsagas_get: GET /sagas"] pub async fn sagas_get<'a>( &'a self, limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!("{}/sagas", self.baseurl,); let mut query = Vec::new(); if let Some(v) = &limit { @@ -3006,21 +3581,31 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List all sagas (for debugging)\n\nreturns a Stream by making successive calls to sagas_get"] + #[doc = "\n\nreturns a Stream by making successive calls to sagas_get"] pub fn sagas_get_stream<'a>( &'a self, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.sagas_get(limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -3028,6 +3613,7 @@ impl Client { } else { self.sagas_get(None, state.as_deref(), None) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -3043,25 +3629,57 @@ impl Client { .boxed() } - #[doc = "Fetch information about a single saga (for debugging)\n\nsagas_get_saga: GET /sagas/{saga_id}"] - pub async fn sagas_get_saga<'a>(&'a self, saga_id: &'a uuid::Uuid) -> Result { + #[doc = "\n\nsagas_get_saga: GET /sagas/{saga_id}"] + pub async fn sagas_get_saga<'a>( + &'a self, + saga_id: &'a uuid::Uuid, + ) -> Result, Error> { let url = format!( "{}/sagas/{}", self.baseurl, - progenitor_support::encode_path(&saga_id.to_string()), + progenitor_client::encode_path(&saga_id.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List all timeseries schema\n\ntimeseries_schema_get: GET /timeseries/schema"] + #[doc = "session_me: GET /session/me"] + pub async fn session_me<'a>( + &'a self, + ) -> Result, Error> { + let url = format!("{}/session/me", self.baseurl,); + let request = self.client.get(url).build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + + #[doc = "\n\ntimeseries_schema_get: GET /timeseries/schema"] pub async fn timeseries_schema_get<'a>( &'a self, limit: Option, page_token: Option<&'a str>, - ) -> Result { + ) -> Result, Error> { let url = format!("{}/timeseries/schema", self.baseurl,); let mut query = Vec::new(); if let Some(v) = &limit { @@ -3074,20 +3692,31 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List all timeseries schema\n\nreturns a Stream by making successive calls to timeseries_schema_get"] + #[doc = "\n\nreturns a Stream by making successive calls to timeseries_schema_get"] pub fn timeseries_schema_get_stream<'a>( &'a self, limit: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ + { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.timeseries_schema_get(limit, None) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -3095,6 +3724,7 @@ impl Client { } else { self.timeseries_schema_get(None, state.as_deref()) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -3110,13 +3740,13 @@ impl Client { .boxed() } - #[doc = "List the built-in system users\n\nusers_get: GET /users"] + #[doc = "\n\nusers_get: GET /users"] pub async fn users_get<'a>( &'a self, limit: Option, page_token: Option<&'a str>, sort_by: Option, - ) -> Result { + ) -> Result, Error> { let url = format!("{}/users", self.baseurl,); let mut query = Vec::new(); if let Some(v) = &limit { @@ -3133,21 +3763,31 @@ impl Client { let request = self.client.get(url).query(&query).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } - #[doc = "List the built-in system users\n\nreturns a Stream by making successive calls to users_get"] + #[doc = "\n\nreturns a Stream by making successive calls to users_get"] pub fn users_get_stream<'a>( &'a self, limit: Option, sort_by: Option, - ) -> impl futures::Stream> + Unpin + '_ { + ) -> impl futures::Stream>> + Unpin + '_ { use futures::StreamExt; use futures::TryFutureExt; use futures::TryStreamExt; self.users_get(limit, None, sort_by) .map_ok(move |page| { + let page = page.into_inner(); let first = futures::stream::iter(page.items.into_iter().map(Ok)); let rest = futures::stream::try_unfold(page.next_page, move |state| async move { if state.is_none() { @@ -3155,6 +3795,7 @@ impl Client { } else { self.users_get(None, state.as_deref(), None) .map_ok(|page| { + let page = page.into_inner(); Some(( futures::stream::iter(page.items.into_iter().map(Ok)), page.next_page, @@ -3170,16 +3811,28 @@ impl Client { .boxed() } - #[doc = "Fetch a specific built-in system user\n\nusers_get_user: GET /users/{user_name}"] - pub async fn users_get_user<'a>(&'a self, user_name: &'a types::Name) -> Result { + #[doc = "\n\nusers_get_user: GET /users/{user_name}"] + pub async fn users_get_user<'a>( + &'a self, + user_name: &'a types::Name, + ) -> Result, Error> { let url = format!( "{}/users/{}", self.baseurl, - progenitor_support::encode_path(&user_name.to_string()), + progenitor_client::encode_path(&user_name.to_string()), ); let request = self.client.get(url).build()?; let result = self.client.execute(request).await; - let res = result?.error_for_status()?; - Ok(res.json().await?) + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } } } diff --git a/progenitor-macro/src/lib.rs b/progenitor-macro/src/lib.rs index 099308a..2ffdfe3 100644 --- a/progenitor-macro/src/lib.rs +++ b/progenitor-macro/src/lib.rs @@ -10,6 +10,45 @@ use serde::Deserialize; use serde_tokenstream::ParseWrapper; use syn::LitStr; +/// Generates a client from the given OpenAPI document +/// +/// `generate_api!` can be invoked in two ways. The simple form, takes a path +/// to the OpenAPI document: +/// ```ignore +/// generate_api!("path/to/spec.json"); +/// ``` +/// +/// The more complex form accepts the following key-value pairs in any order: +/// ```ignore +/// generate_api!( +/// spec = "path/to/spec.json" +/// [ inner_type = path::to:Type, ] +/// [ pre_hook = closure::or::path::to::function, ] +/// [ post_hook = closure::or::path::to::function, ] +/// [ derives = [ path::to::DeriveMacro ], ] +/// ); +/// ``` +/// +/// The `spec` key is required; it is the OpenAPI document from which the +/// client is derived. +/// +/// The optional `inner_type` is for ancillary data, stored with the generated +/// client that can be usd by the pre and post hooks. +/// +/// The optional `pre_hook` is either a closure (that must be within +/// parentheses: `(fn |inner, request| { .. })`) or a path to a function. The +/// closure or function must take two parameters: the inner type and a +/// `&reqwest::Request`. This allows clients to examine requests before they're +/// sent to the server, for example to log them. +/// +/// The optional `post_hook` is either a closure (that must be within +/// parentheses: `(fn |inner, result| { .. })`) or a path to a function. The +/// closure or function must take two parameters: the inner type and a +/// `&Result`. This allows clients to +/// examine responses, for example to log them. +/// +/// The optional `derives` array allows consumers to specify additional derive +/// macros to apply to generated types. #[proc_macro] pub fn generate_api(item: TokenStream) -> TokenStream { match do_generate_api(item) { @@ -112,6 +151,10 @@ fn do_generate_api(item: TokenStream) -> Result { })?; let output = quote! { + // The progenitor_client is tautologically visible from macro + // consumers. + use progenitor::progenitor_client; + #code // Force a rebuild when the given file is modified. diff --git a/progenitor/Cargo.toml b/progenitor/Cargo.toml index 97e55fc..0c74985 100644 --- a/progenitor/Cargo.toml +++ b/progenitor/Cargo.toml @@ -7,8 +7,9 @@ repository = "https://github.com/oxidecomputer/progenitor.git" description = "An OpenAPI client generator" [dependencies] -progenitor-macro = { path = "../progenitor-macro" } +progenitor-client = { path = "../progenitor-client" } progenitor-impl = { path = "../progenitor-impl" } +progenitor-macro = { path = "../progenitor-macro" } anyhow = "1.0" getopts = "0.2" openapiv3 = "1.0.0" diff --git a/progenitor/src/lib.rs b/progenitor/src/lib.rs index c3a9adb..1c8d642 100644 --- a/progenitor/src/lib.rs +++ b/progenitor/src/lib.rs @@ -1,7 +1,16 @@ -// Copyright 2021 Oxide Computer Company +// Copyright 2022 Oxide Computer Company -//! TODO This crate does stuff! +//! Progenitor is a Rust crate for generating opinionated clients from API +//! descriptions specified in the OpenAPI 3.0.x format. It makes use of Rust +//! futures for async API calls and `Streams` for paginated interfaces. +//! +//! It generates a type called `Client` with methods that correspond to the +//! operations specified in the OpenAPI document. +//! +//! For details see the [repo +//! README](https://github.com/oxidecomputer/progenitor/README.md) +pub use progenitor_client; pub use progenitor_impl::Error; pub use progenitor_impl::Generator; pub use progenitor_macro::generate_api; diff --git a/progenitor/src/main.rs b/progenitor/src/main.rs index 781fb47..a291638 100644 --- a/progenitor/src/main.rs +++ b/progenitor/src/main.rs @@ -53,8 +53,8 @@ fn main() -> Result<()> { let mut builder = Generator::new(); - let fail = match builder.generate_text(&api) { - Ok(out) => { + match builder.generate_text(&api) { + Ok(api_code) => { let type_space = builder.get_type_space(); println!("-----------------------------------------------------"); @@ -89,7 +89,9 @@ fn main() -> Result<()> { edition = \"2018\"\n\ \n\ [dependencies]\n\ - {}", + {}\n\ + \n\ + [workspace]\n", name, version, builder.dependencies().join("\n"), @@ -107,19 +109,25 @@ fn main() -> Result<()> { /* * Create the Rust source file containing the generated client: */ - let mut librs = src; + let lib_code = format!("mod progenitor_client;\n\n{}", api_code); + let mut librs = src.clone(); librs.push("lib.rs"); - save(librs, out.as_str())?; - false + save(librs, lib_code.as_str())?; + + /* + * Create the Rust source file containing the support code: + */ + let progenitor_client_code = + include_str!("../../progenitor-client/src/lib.rs"); + let mut clientrs = src; + clientrs.push("progenitor_client.rs"); + save(clientrs, progenitor_client_code)?; } + Err(e) => { println!("gen fail: {:?}", e); - true + bail!("generation experienced errors"); } - }; - - if fail { - bail!("generation experienced errors"); } Ok(()) @@ -135,6 +143,7 @@ where Ok(serde_json::from_reader(f)?) } +// TODO some or all of this validation should be in progenitor-impl pub fn load_api

(p: P) -> Result where P: AsRef, @@ -161,45 +170,6 @@ where bail!("tags not presently supported"); } - if let Some(components) = api.components.as_ref() { - if !components.security_schemes.is_empty() { - bail!("component security schemes not supported"); - } - - if !components.responses.is_empty() { - bail!("component responses not supported"); - } - - if !components.parameters.is_empty() { - bail!("component parameters not supported"); - } - - if !components.request_bodies.is_empty() { - bail!("component request bodies not supported"); - } - - if !components.headers.is_empty() { - bail!("component headers not supported"); - } - - if !components.links.is_empty() { - bail!("component links not supported"); - } - - if !components.callbacks.is_empty() { - bail!("component callbacks not supported"); - } - - /* - * XXX Ignoring "examples" and "extensions" for now. - * Explicitly allowing "schemas" through. - */ - } - - /* - * XXX Ignoring "external_docs" and "extensions" for now, as they seem not - * to immediately affect our code generation. - */ let mut opids = HashSet::new(); for p in api.paths.paths.iter() { match p.1 { @@ -217,10 +187,6 @@ where bail!("duplicate operation ID: {}", oid); } - if !o.tags.is_empty() { - bail!("op {}: tags, unsupported", oid); - } - if !o.servers.is_empty() { bail!("op {}: servers, unsupported", oid); } @@ -228,10 +194,6 @@ where if o.security.is_some() { bail!("op {}: security, unsupported", oid); } - - if o.responses.default.is_some() { - bail!("op {}: has response default", oid); - } } else { bail!("path {} is missing operation ID", p.0); } diff --git a/sample_openapi/nexus.json b/sample_openapi/nexus.json index fe6af15..85acae5 100644 --- a/sample_openapi/nexus.json +++ b/sample_openapi/nexus.json @@ -12,7 +12,11 @@ "paths": { "/hardware/racks": { "get": { - "description": "List racks in the system.", + "tags": [ + "racks" + ], + "summary": "List racks in the system.", + "description": "", "operationId": "hardware_racks_get", "parameters": [ { @@ -56,6 +60,12 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true @@ -63,7 +73,11 @@ }, "/hardware/racks/{rack_id}": { "get": { - "description": "Fetch information about a particular rack.", + "tags": [ + "racks" + ], + "summary": "Fetch information about a particular rack.", + "description": "", "operationId": "hardware_racks_get_rack", "parameters": [ { @@ -88,13 +102,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/hardware/sleds": { "get": { - "description": "List sleds in the system.", + "tags": [ + "sleds" + ], + "summary": "List sleds in the system.", + "description": "", "operationId": "hardware_sleds_get", "parameters": [ { @@ -138,6 +162,12 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true @@ -145,7 +175,11 @@ }, "/hardware/sleds/{sled_id}": { "get": { - "description": "Fetch information about a sled in the system.", + "tags": [ + "sleds" + ], + "summary": "Fetch information about a sled in the system.", + "description": "", "operationId": "hardware_sleds_get_sled", "parameters": [ { @@ -170,12 +204,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/login": { "post": { + "tags": [ + "hidden" + ], "operationId": "spoof_login", "requestBody": { "content": { @@ -196,6 +239,9 @@ }, "/logout": { "post": { + "tags": [ + "hidden" + ], "operationId": "logout", "responses": { "default": { @@ -206,7 +252,11 @@ }, "/organizations": { "get": { - "description": "List all organizations.", + "tags": [ + "organizations" + ], + "summary": "List all organizations.", + "description": "", "operationId": "organizations_get", "parameters": [ { @@ -250,12 +300,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "post": { - "description": "Create a new organization.", + "tags": [ + "organizations" + ], + "summary": "Create a new organization.", + "description": "", "operationId": "organizations_post", "requestBody": { "content": { @@ -277,13 +337,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}": { "get": { - "description": "Fetch a specific organization", + "tags": [ + "organizations" + ], + "summary": "Fetch a specific organization", + "description": "", "operationId": "organizations_get_organization", "parameters": [ { @@ -306,11 +376,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "put": { - "description": "Update a specific organization.\n * TODO-correctness: Is it valid for PUT to accept application/json that's a subset of what the resource actually represents? If not, is that a problem? (HTTP may require that this be idempotent.) If so, can we get around that having this be a slightly different content-type (e.g., \"application/json-patch\")? We should see what other APIs do.", + "tags": [ + "organizations" + ], + "summary": "Update a specific organization.", + "description": "", "operationId": "organizations_put_organization", "parameters": [ { @@ -343,11 +423,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "delete": { - "description": "Delete a specific organization.", + "tags": [ + "organizations" + ], + "summary": "Delete a specific organization.", + "description": "", "operationId": "organizations_delete_organization", "parameters": [ { @@ -363,13 +453,23 @@ "responses": { "204": { "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects": { "get": { - "description": "List all projects.", + "tags": [ + "projects" + ], + "summary": "List all projects.", + "description": "", "operationId": "organization_projects_get", "parameters": [ { @@ -422,12 +522,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "post": { - "description": "Create a new project.", + "tags": [ + "projects" + ], + "summary": "Create a new project.", + "description": "", "operationId": "organization_projects_post", "parameters": [ { @@ -460,13 +570,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}": { "get": { - "description": "Fetch a specific project", + "tags": [ + "projects" + ], + "summary": "Fetch a specific project", + "description": "", "operationId": "organization_projects_get_project", "parameters": [ { @@ -498,11 +618,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "put": { - "description": "Update a specific project.\n * TODO-correctness: Is it valid for PUT to accept application/json that's a subset of what the resource actually represents? If not, is that a problem? (HTTP may require that this be idempotent.) If so, can we get around that having this be a slightly different content-type (e.g., \"application/json-patch\")? We should see what other APIs do.", + "tags": [ + "projects" + ], + "summary": "Update a specific project.", + "description": "", "operationId": "organization_projects_put_project", "parameters": [ { @@ -544,11 +674,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "delete": { - "description": "Delete a specific project.", + "tags": [ + "projects" + ], + "summary": "Delete a specific project.", + "description": "", "operationId": "organization_projects_delete_project", "parameters": [ { @@ -573,13 +713,23 @@ "responses": { "204": { "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/disks": { "get": { - "description": "List disks in a project.", + "tags": [ + "disks" + ], + "summary": "List disks in a project.", + "description": "", "operationId": "project_disks_get", "parameters": [ { @@ -641,12 +791,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "post": { - "description": "Create a disk in a project.\n * TODO-correctness See note about instance create. This should be async.", + "tags": [ + "disks" + ], + "summary": "Create a disk in a project.", + "description": "", "operationId": "project_disks_post", "parameters": [ { @@ -688,13 +848,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/disks/{disk_name}": { "get": { - "description": "Fetch a single disk in a project.", + "tags": [ + "disks" + ], + "summary": "Fetch a single disk in a project.", + "description": "", "operationId": "project_disks_get_disk", "parameters": [ { @@ -735,11 +905,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "delete": { - "description": "Delete a disk from a project.", + "tags": [ + "disks" + ], + "summary": "Delete a disk from a project.", + "description": "", "operationId": "project_disks_delete_disk", "parameters": [ { @@ -773,13 +953,23 @@ "responses": { "204": { "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/instances": { "get": { - "description": "List instances in a project.", + "tags": [ + "instances" + ], + "summary": "List instances in a project.", + "description": "", "operationId": "project_instances_get", "parameters": [ { @@ -841,12 +1031,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "post": { - "description": "Create an instance in a project.\n * TODO-correctness This is supposed to be async. Is that right? We can create the instance immediately -- it's just not booted yet. Maybe the boot operation is what's a separate operation_id. What about the response code (201 Created vs 202 Accepted)? Is that orthogonal? Things can return a useful response, including an operation id, with either response code. Maybe a \"reboot\" operation would return a 202 Accepted because there's no actual resource created?", + "tags": [ + "instances" + ], + "summary": "Create an instance in a project.", + "description": "", "operationId": "project_instances_post", "parameters": [ { @@ -888,13 +1088,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}": { "get": { - "description": "Get an instance in a project.", + "tags": [ + "instances" + ], + "summary": "Get an instance in a project.", + "description": "", "operationId": "project_instances_get_instance", "parameters": [ { @@ -935,11 +1145,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "delete": { - "description": "Delete an instance from a project.", + "tags": [ + "instances" + ], + "summary": "Delete an instance from a project.", + "description": "", "operationId": "project_instances_delete_instance", "parameters": [ { @@ -973,13 +1193,23 @@ "responses": { "204": { "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks": { "get": { - "description": "List disks attached to this instance.", + "tags": [ + "instances" + ], + "summary": "List disks attached to this instance.", + "description": "", "operationId": "instance_disks_get", "parameters": [ { @@ -1050,6 +1280,12 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true @@ -1057,6 +1293,9 @@ }, "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/attach": { "post": { + "tags": [ + "instances" + ], "operationId": "instance_disks_attach", "parameters": [ { @@ -1107,12 +1346,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/detach": { "post": { + "tags": [ + "instances" + ], "operationId": "instance_disks_detach", "parameters": [ { @@ -1163,13 +1411,90 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/migrate": { + "post": { + "tags": [ + "instances" + ], + "summary": "Migrate an instance to a different propolis-server, possibly on a different sled.", + "description": "", + "operationId": "project_instances_migrate_instance", + "parameters": [ + { + "in": "path", + "name": "instance_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "organization_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + }, + { + "in": "path", + "name": "project_name", + "required": true, + "schema": { + "$ref": "#/components/schemas/Name" + }, + "style": "simple" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceMigrate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/reboot": { "post": { - "description": "Reboot an instance.", + "tags": [ + "instances" + ], + "summary": "Reboot an instance.", + "description": "", "operationId": "project_instances_instance_reboot", "parameters": [ { @@ -1210,13 +1535,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/start": { "post": { - "description": "Boot an instance.", + "tags": [ + "instances" + ], + "summary": "Boot an instance.", + "description": "", "operationId": "project_instances_instance_start", "parameters": [ { @@ -1257,13 +1592,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop": { "post": { - "description": "Halt an instance.", + "tags": [ + "instances" + ], + "summary": "Halt an instance.", + "description": "", "operationId": "project_instances_instance_stop", "parameters": [ { @@ -1304,13 +1649,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs": { "get": { - "description": "List VPCs in a project.", + "tags": [ + "vpcs" + ], + "summary": "List VPCs in a project.", + "description": "", "operationId": "project_vpcs_get", "parameters": [ { @@ -1372,12 +1727,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "post": { - "description": "Create a VPC in a project.", + "tags": [ + "vpcs" + ], + "summary": "Create a VPC in a project.", + "description": "", "operationId": "project_vpcs_post", "parameters": [ { @@ -1419,13 +1784,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}": { "get": { - "description": "Get a VPC in a project.", + "tags": [ + "vpcs" + ], + "summary": "Get a VPC in a project.", + "description": "", "operationId": "project_vpcs_get_vpc", "parameters": [ { @@ -1466,11 +1841,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "put": { - "description": "Update a VPC.", + "tags": [ + "vpcs" + ], + "summary": "Update a VPC.", + "description": "", "operationId": "project_vpcs_put_vpc", "parameters": [ { @@ -1514,11 +1899,21 @@ "responses": { "204": { "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "delete": { - "description": "Delete a vpc from a project.", + "tags": [ + "vpcs" + ], + "summary": "Delete a vpc from a project.", + "description": "", "operationId": "project_vpcs_delete_vpc", "parameters": [ { @@ -1552,13 +1947,23 @@ "responses": { "204": { "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/firewall/rules": { "get": { - "description": "List firewall rules for a VPC.", + "tags": [ + "firewall" + ], + "summary": "List firewall rules for a VPC.", + "description": "", "operationId": "vpc_firewall_rules_get", "parameters": [ { @@ -1629,12 +2034,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "put": { - "description": "Replace the firewall rules for a VPC", + "tags": [ + "firewall" + ], + "summary": "Replace the firewall rules for a VPC", + "description": "", "operationId": "vpc_firewall_rules_put", "parameters": [ { @@ -1685,13 +2100,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers": { "get": { - "description": "List VPC Custom and System Routers", + "tags": [ + "routers" + ], + "summary": "List VPC Custom and System Routers", + "description": "", "operationId": "vpc_routers_get", "parameters": [ { @@ -1762,12 +2187,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "post": { - "description": "Create a VPC Router", + "tags": [ + "routers" + ], + "summary": "Create a VPC Router", + "description": "", "operationId": "vpc_routers_post", "parameters": [ { @@ -1818,13 +2253,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}": { "get": { - "description": "Get a VPC Router", + "tags": [ + "routers" + ], + "summary": "Get a VPC Router", + "description": "", "operationId": "vpc_routers_get_router", "parameters": [ { @@ -1874,11 +2319,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "put": { - "description": "Update a VPC Router", + "tags": [ + "routers" + ], + "summary": "Update a VPC Router", + "description": "", "operationId": "vpc_routers_put_router", "parameters": [ { @@ -1931,11 +2386,21 @@ "responses": { "204": { "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "delete": { - "description": "Delete a router from its VPC", + "tags": [ + "routers" + ], + "summary": "Delete a router from its VPC", + "description": "", "operationId": "vpc_routers_delete_router", "parameters": [ { @@ -1978,13 +2443,23 @@ "responses": { "204": { "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes": { "get": { - "description": "List a Router's routes", + "tags": [ + "routes" + ], + "summary": "List a Router's routes", + "description": "", "operationId": "routers_routes_get", "parameters": [ { @@ -2064,12 +2539,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "post": { - "description": "Create a VPC Router", + "tags": [ + "routes" + ], + "summary": "Create a VPC Router", + "description": "", "operationId": "routers_routes_post", "parameters": [ { @@ -2129,13 +2614,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}": { "get": { - "description": "Get a VPC Router route", + "tags": [ + "routes" + ], + "summary": "Get a VPC Router route", + "description": "", "operationId": "routers_routes_get_route", "parameters": [ { @@ -2194,11 +2689,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "put": { - "description": "Update a Router route", + "tags": [ + "routes" + ], + "summary": "Update a Router route", + "description": "", "operationId": "routers_routes_put_route", "parameters": [ { @@ -2260,11 +2765,21 @@ "responses": { "204": { "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "delete": { - "description": "Delete a route from its router", + "tags": [ + "routes" + ], + "summary": "Delete a route from its router", + "description": "", "operationId": "routers_routes_delete_route", "parameters": [ { @@ -2316,13 +2831,23 @@ "responses": { "204": { "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets": { "get": { - "description": "List subnets in a VPC.", + "tags": [ + "subnets" + ], + "summary": "List subnets in a VPC.", + "description": "", "operationId": "vpc_subnets_get", "parameters": [ { @@ -2393,12 +2918,22 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true }, "post": { - "description": "Create a subnet in a VPC.", + "tags": [ + "subnets" + ], + "summary": "Create a subnet in a VPC.", + "description": "", "operationId": "vpc_subnets_post", "parameters": [ { @@ -2449,13 +2984,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}": { "get": { - "description": "Get subnet in a VPC.", + "tags": [ + "subnets" + ], + "summary": "Get subnet in a VPC.", + "description": "", "operationId": "vpc_subnets_get_subnet", "parameters": [ { @@ -2505,11 +3050,21 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "put": { - "description": "Update a VPC Subnet.", + "tags": [ + "subnets" + ], + "summary": "Update a VPC Subnet.", + "description": "", "operationId": "vpc_subnets_put_subnet", "parameters": [ { @@ -2562,11 +3117,21 @@ "responses": { "204": { "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } }, "delete": { - "description": "Delete a subnet from a VPC.", + "tags": [ + "subnets" + ], + "summary": "Delete a subnet from a VPC.", + "description": "", "operationId": "vpc_subnets_delete_subnet", "parameters": [ { @@ -2609,13 +3174,23 @@ "responses": { "204": { "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}/ips": { "get": { - "description": "List IP addresses on a VPC subnet.", + "tags": [ + "subnets" + ], + "summary": "List IP addresses on a VPC subnet.", + "description": "", "operationId": "subnets_ips_get", "parameters": [ { @@ -2695,6 +3270,12 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true @@ -2702,7 +3283,11 @@ }, "/roles": { "get": { - "description": "List the built-in roles", + "tags": [ + "roles" + ], + "summary": "List the built-in roles", + "description": "", "operationId": "roles_get", "parameters": [ { @@ -2738,6 +3323,12 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true @@ -2745,7 +3336,11 @@ }, "/roles/{role_name}": { "get": { - "description": "Fetch a specific built-in role", + "tags": [ + "roles" + ], + "summary": "Fetch a specific built-in role", + "description": "", "operationId": "roles_get_role", "parameters": [ { @@ -2769,13 +3364,23 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/sagas": { "get": { - "description": "List all sagas (for debugging)", + "tags": [ + "sagas" + ], + "summary": "List all sagas (for debugging)", + "description": "", "operationId": "sagas_get", "parameters": [ { @@ -2819,6 +3424,12 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true @@ -2826,7 +3437,11 @@ }, "/sagas/{saga_id}": { "get": { - "description": "Fetch information about a single saga (for debugging)", + "tags": [ + "sagas" + ], + "summary": "Fetch information about a single saga (for debugging)", + "description": "", "operationId": "sagas_get_saga", "parameters": [ { @@ -2850,13 +3465,50 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/session/me": { + "get": { + "tags": [ + "hidden" + ], + "summary": "Fetch the user associated with the current session", + "operationId": "session_me", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionUser" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } }, "/timeseries/schema": { "get": { - "description": "List all timeseries schema", + "tags": [ + "metrics" + ], + "summary": "List all timeseries schema", + "description": "", "operationId": "timeseries_schema_get", "parameters": [ { @@ -2892,6 +3544,12 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true @@ -2899,7 +3557,11 @@ }, "/users": { "get": { - "description": "List the built-in system users", + "tags": [ + "users" + ], + "summary": "List the built-in system users", + "description": "", "operationId": "users_get", "parameters": [ { @@ -2943,6 +3605,12 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-pagination": true @@ -2950,7 +3618,11 @@ }, "/users/{user_name}": { "get": { - "description": "Fetch a specific built-in system user", + "tags": [ + "users" + ], + "summary": "Fetch a specific built-in system user", + "description": "", "operationId": "users_get_user", "parameters": [ { @@ -2973,12 +3645,30 @@ } } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } } } } }, "components": { + "responses": { + "Error": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, "schemas": { "ByteCount": { "description": "A count of bytes, typically used either for memory or storage capacity\n\nThe maximum supported byte count is [`i64::MAX`]. This makes it somewhat inconvenient to define constructors: a u32 constructor can be infallible, but an i64 constructor can fail (if the value is negative) and a u64 constructor can fail (if the value is larger than i64::MAX). We provide all of these for consumers' convenience.", @@ -3009,7 +3699,7 @@ "description": "human-readable free-form text about a resource", "type": "string" }, - "devicePath": { + "device_path": { "type": "string" }, "id": { @@ -3025,14 +3715,14 @@ } ] }, - "projectId": { + "project_id": { "type": "string", "format": "uuid" }, "size": { "$ref": "#/components/schemas/ByteCount" }, - "snapshotId": { + "snapshot_id": { "nullable": true, "type": "string", "format": "uuid" @@ -3040,12 +3730,12 @@ "state": { "$ref": "#/components/schemas/DiskState" }, - "timeCreated": { + "time_created": { "description": "timestamp when this resource was created", "type": "string", "format": "date-time" }, - "timeModified": { + "time_modified": { "description": "timestamp when this resource was last modified", "type": "string", "format": "date-time" @@ -3053,14 +3743,14 @@ }, "required": [ "description", - "devicePath", + "device_path", "id", "name", - "projectId", + "project_id", "size", "state", - "timeCreated", - "timeModified" + "time_created", + "time_modified" ] }, "DiskCreate": { @@ -3081,7 +3771,7 @@ } ] }, - "snapshotId": { + "snapshot_id": { "nullable": true, "description": "id for snapshot from which the Disk should be created, if any", "type": "string", @@ -3252,6 +3942,25 @@ } ] }, + "Error": { + "description": "Body of an HTTP response for an `HttpError`. This type can be used to deserialize an HTTP response corresponding to an error in order to access the error code, message, etc.", + "type": "object", + "properties": { + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "message", + "request_id" + ] + }, "FieldSchema": { "description": "The name and type information for a field of a timeseries schema.", "type": "object", @@ -3291,46 +4000,6 @@ "Bool" ] }, - "IdentityMetadata": { - "description": "Identity-related metadata that's included in nearly all public API objects", - "type": "object", - "properties": { - "description": { - "description": "human-readable free-form text about a resource", - "type": "string" - }, - "id": { - "description": "unique, immutable, system-controlled identifier for each resource", - "type": "string", - "format": "uuid" - }, - "name": { - "description": "unique, mutable, user-controlled identifier for each resource", - "allOf": [ - { - "$ref": "#/components/schemas/Name" - } - ] - }, - "timeCreated": { - "description": "timestamp when this resource was created", - "type": "string", - "format": "date-time" - }, - "timeModified": { - "description": "timestamp when this resource was last modified", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "description", - "id", - "name", - "timeCreated", - "timeModified" - ] - }, "Instance": { "description": "Client view of an [`Instance`]", "type": "object", @@ -3372,25 +4041,25 @@ } ] }, - "projectId": { + "project_id": { "description": "id for the project containing this Instance", "type": "string", "format": "uuid" }, - "runState": { + "run_state": { "$ref": "#/components/schemas/InstanceState" }, - "timeCreated": { + "time_created": { "description": "timestamp when this resource was created", "type": "string", "format": "date-time" }, - "timeModified": { + "time_modified": { "description": "timestamp when this resource was last modified", "type": "string", "format": "date-time" }, - "timeRunStateUpdated": { + "time_run_state_updated": { "type": "string", "format": "date-time" } @@ -3402,11 +4071,11 @@ "memory", "name", "ncpus", - "projectId", - "runState", - "timeCreated", - "timeModified", - "timeRunStateUpdated" + "project_id", + "run_state", + "time_created", + "time_modified", + "time_run_state_updated" ] }, "InstanceCpuCount": { @@ -3443,6 +4112,19 @@ "ncpus" ] }, + "InstanceMigrate": { + "description": "Migration parameters for an [`Instance`]", + "type": "object", + "properties": { + "dst_sled_uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "dst_sled_uuid" + ] + }, "InstanceResultsPage": { "description": "A single page of results", "type": "object", @@ -3474,6 +4156,7 @@ "stopping", "stopped", "rebooting", + "migrating", "repairing", "failed", "destroyed" @@ -3531,13 +4214,14 @@ "description": "A `NetworkInterface` represents a virtual network interface device.", "type": "object", "properties": { - "identity": { - "description": "common identifying metadata", - "allOf": [ - { - "$ref": "#/components/schemas/IdentityMetadata" - } - ] + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" }, "instance_id": { "description": "The Instance to which the interface belongs.", @@ -3557,11 +4241,29 @@ } ] }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, "subnet_id": { "description": "The subnet to which the interface belongs.", "type": "string", "format": "uuid" }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + }, "vpc_id": { "description": "The VPC to which the interface belongs.", "type": "string", @@ -3569,11 +4271,15 @@ } }, "required": [ - "identity", + "description", + "id", "instance_id", "ip", "mac", + "name", "subnet_id", + "time_created", + "time_modified", "vpc_id" ] }, @@ -3619,12 +4325,12 @@ } ] }, - "timeCreated": { + "time_created": { "description": "timestamp when this resource was created", "type": "string", "format": "date-time" }, - "timeModified": { + "time_modified": { "description": "timestamp when this resource was last modified", "type": "string", "format": "date-time" @@ -3634,8 +4340,8 @@ "description", "id", "name", - "timeCreated", - "timeModified" + "time_created", + "time_modified" ] }, "OrganizationCreate": { @@ -3714,16 +4420,16 @@ } ] }, - "organizationId": { + "organization_id": { "type": "string", "format": "uuid" }, - "timeCreated": { + "time_created": { "description": "timestamp when this resource was created", "type": "string", "format": "date-time" }, - "timeModified": { + "time_modified": { "description": "timestamp when this resource was last modified", "type": "string", "format": "date-time" @@ -3733,9 +4439,9 @@ "description", "id", "name", - "organizationId", - "timeCreated", - "timeModified" + "organization_id", + "time_created", + "time_modified" ] }, "ProjectCreate": { @@ -3797,12 +4503,40 @@ "description": "Client view of an [`Rack`]", "type": "object", "properties": { - "identity": { - "$ref": "#/components/schemas/IdentityMetadata" + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" + }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" } }, "required": [ - "identity" + "description", + "id", + "name", + "time_created", + "time_modified" ] }, "RackResultsPage": { @@ -4012,7 +4746,7 @@ "type": { "type": "string", "enum": [ - "internetGateway" + "internet_gateway" ] }, "value": { @@ -4030,16 +4764,17 @@ "description": "A route defines a rule that governs where traffic should be sent based on its destination.", "type": "object", "properties": { + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, "destination": { "$ref": "#/components/schemas/RouteDestination" }, - "identity": { - "description": "common identifying metadata", - "allOf": [ - { - "$ref": "#/components/schemas/IdentityMetadata" - } - ] + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" }, "kind": { "description": "Describes the kind of router. Set at creation. `read-only`", @@ -4049,6 +4784,14 @@ } ] }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, "router_id": { "description": "The VPC Router to which the route belongs.", "type": "string", @@ -4056,14 +4799,28 @@ }, "target": { "$ref": "#/components/schemas/RouteTarget" + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" } }, "required": [ + "description", "destination", - "identity", + "id", "kind", + "name", "router_id", - "target" + "target", + "time_created", + "time_modified" ] }, "RouterRouteCreateParams": { @@ -4094,10 +4851,10 @@ "description": "The classification of a [`RouterRoute`] as defined by the system. The kind determines certain attributes such as if the route is modifiable and describes how or where the route was created.\n\nSee [RFD-21](https://rfd.shared.oxide.computer/rfd/0021#concept-router) for more context", "type": "string", "enum": [ - "Default", - "VpcSubnet", - "VpcPeering", - "Custom" + "default", + "vpc_subnet", + "vpc_peering", + "custom" ] }, "RouterRouteResultsPage": { @@ -4173,7 +4930,7 @@ "error": { "type": "string", "enum": [ - "actionFailed" + "action_failed" ] }, "source_error": {} @@ -4189,7 +4946,7 @@ "error": { "type": "string", "enum": [ - "deserializeFailed" + "deserialize_failed" ] }, "message": { @@ -4207,7 +4964,7 @@ "error": { "type": "string", "enum": [ - "injectedError" + "injected_error" ] } }, @@ -4221,7 +4978,7 @@ "error": { "type": "string", "enum": [ - "serializeFailed" + "serialize_failed" ] }, "message": { @@ -4239,7 +4996,7 @@ "error": { "type": "string", "enum": [ - "subsagaCreateFailed" + "subsaga_create_failed" ] }, "message": { @@ -4328,6 +5085,19 @@ } ] }, + "SessionUser": { + "description": "Client view of currently authed user.", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "id" + ] + }, "Sled": { "description": "Client view of an [`Sled`]", "type": "object", @@ -4349,15 +5119,15 @@ } ] }, - "serviceAddress": { + "service_address": { "type": "string" }, - "timeCreated": { + "time_created": { "description": "timestamp when this resource was created", "type": "string", "format": "date-time" }, - "timeModified": { + "time_modified": { "description": "timestamp when this resource was last modified", "type": "string", "format": "date-time" @@ -4367,9 +5137,9 @@ "description", "id", "name", - "serviceAddress", - "timeCreated", - "timeModified" + "service_address", + "time_created", + "time_modified" ] }, "SledResultsPage": { @@ -4469,12 +5239,12 @@ } ] }, - "timeCreated": { + "time_created": { "description": "timestamp when this resource was created", "type": "string", "format": "date-time" }, - "timeModified": { + "time_modified": { "description": "timestamp when this resource was last modified", "type": "string", "format": "date-time" @@ -4484,8 +5254,8 @@ "description", "id", "name", - "timeCreated", - "timeModified" + "time_created", + "time_modified" ] }, "UserResultsPage": { @@ -4517,7 +5287,7 @@ "description": "human-readable free-form text about a resource", "type": "string" }, - "dnsName": { + "dns_name": { "description": "The name used for the VPC in DNS.", "allOf": [ { @@ -4538,22 +5308,22 @@ } ] }, - "projectId": { + "project_id": { "description": "id for the project containing this VPC", "type": "string", "format": "uuid" }, - "systemRouterId": { + "system_router_id": { "description": "id for the system router where subnet default routes are registered", "type": "string", "format": "uuid" }, - "timeCreated": { + "time_created": { "description": "timestamp when this resource was created", "type": "string", "format": "date-time" }, - "timeModified": { + "time_modified": { "description": "timestamp when this resource was last modified", "type": "string", "format": "date-time" @@ -4561,13 +5331,13 @@ }, "required": [ "description", - "dnsName", + "dns_name", "id", "name", - "projectId", - "systemRouterId", - "timeCreated", - "timeModified" + "project_id", + "system_router_id", + "time_created", + "time_modified" ] }, "VpcCreate": { @@ -4577,7 +5347,7 @@ "description": { "type": "string" }, - "dnsName": { + "dns_name": { "$ref": "#/components/schemas/Name" }, "name": { @@ -4586,7 +5356,7 @@ }, "required": [ "description", - "dnsName", + "dns_name", "name" ] }, @@ -4602,6 +5372,10 @@ } ] }, + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, "direction": { "description": "whether this rule is for incoming or outgoing traffic", "allOf": [ @@ -4618,11 +5392,16 @@ } ] }, - "identity": { - "description": "common identifying metadata", + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" + }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", "allOf": [ { - "$ref": "#/components/schemas/IdentityMetadata" + "$ref": "#/components/schemas/Name" } ] }, @@ -4646,16 +5425,30 @@ "items": { "$ref": "#/components/schemas/VpcFirewallRuleTarget" } + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" } }, "required": [ "action", + "description", "direction", "filters", - "identity", + "id", + "name", "priority", "status", - "targets" + "targets", + "time_created", + "time_modified" ] }, "VpcFirewallRuleAction": { @@ -4784,7 +5577,7 @@ "type": { "type": "string", "enum": [ - "internetGateway" + "internet_gateway" ] }, "value": { @@ -4997,17 +5790,36 @@ "description": "A VPC router defines a series of rules that indicate where traffic should be sent depending on its destination.", "type": "object", "properties": { - "identity": { - "description": "common identifying metadata", - "allOf": [ - { - "$ref": "#/components/schemas/IdentityMetadata" - } - ] + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" }, "kind": { "$ref": "#/components/schemas/VpcRouterKind" }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + }, "vpc_id": { "description": "The VPC to which the router belongs.", "type": "string", @@ -5015,8 +5827,12 @@ } }, "required": [ - "identity", + "description", + "id", "kind", + "name", + "time_created", + "time_modified", "vpc_id" ] }, @@ -5086,13 +5902,14 @@ "description": "A VPC subnet represents a logical grouping for instances that allows network traffic between them, within a IPv4 subnetwork or optionall an IPv6 subnetwork.", "type": "object", "properties": { - "identity": { - "description": "common identifying metadata", - "allOf": [ - { - "$ref": "#/components/schemas/IdentityMetadata" - } - ] + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" }, "ipv4_block": { "nullable": true, @@ -5112,6 +5929,24 @@ } ] }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + }, "vpc_id": { "description": "The VPC to which the subnet belongs.", "type": "string", @@ -5119,7 +5954,11 @@ } }, "required": [ - "identity", + "description", + "id", + "name", + "time_created", + "time_modified", "vpc_id" ] }, @@ -5130,7 +5969,7 @@ "description": { "type": "string" }, - "ipv4Block": { + "ipv4_block": { "nullable": true, "allOf": [ { @@ -5138,7 +5977,7 @@ } ] }, - "ipv6Block": { + "ipv6_block": { "nullable": true, "allOf": [ { @@ -5184,7 +6023,7 @@ "nullable": true, "type": "string" }, - "ipv4Block": { + "ipv4_block": { "nullable": true, "allOf": [ { @@ -5192,7 +6031,7 @@ } ] }, - "ipv6Block": { + "ipv6_block": { "nullable": true, "allOf": [ { @@ -5218,7 +6057,7 @@ "nullable": true, "type": "string" }, - "dnsName": { + "dns_name": { "nullable": true, "allOf": [ {