From 4e2dcc5365ea4282af7b1851c5ae96305cb51d9a Mon Sep 17 00:00:00 2001 From: liffy <629075+lifning@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:40:07 -0700 Subject: [PATCH] Generate a Client method for Dropshot websocket channels (#183) Generated methods return `ResponseValue --- CHANGELOG.adoc | 1 + Cargo.lock | 40 + README.md | 11 +- example-build/Cargo.toml | 4 +- example-macro/Cargo.toml | 2 +- progenitor-client/Cargo.toml | 2 +- progenitor-client/src/progenitor_client.rs | 24 + progenitor-impl/src/lib.rs | 11 +- progenitor-impl/src/method.rs | 65 +- .../output/propolis-server-builder-tagged.out | 1968 ++++++++++++++++ .../tests/output/propolis-server-builder.out | 1974 +++++++++++++++++ .../output/propolis-server-positional.out | 535 +++++ progenitor-impl/tests/test_output.rs | 5 + progenitor/Cargo.toml | 6 +- progenitor/tests/build_propolis.rs | 23 + sample_openapi/propolis-server.json | 850 +++++++ 16 files changed, 5510 insertions(+), 11 deletions(-) create mode 100644 progenitor-impl/tests/output/propolis-server-builder-tagged.out create mode 100644 progenitor-impl/tests/output/propolis-server-builder.out create mode 100644 progenitor-impl/tests/output/propolis-server-positional.out create mode 100644 progenitor/tests/build_propolis.rs create mode 100644 sample_openapi/propolis-server.json diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index d9a1880..f6e323e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -24,6 +24,7 @@ https://github.com/oxidecomputer/progenitor/compare/v0.1.1\...v0.2.0[Full list o * Derive `Debug` for `Client` and builders for the various operations (#145) * Builders for `struct` types (#171) * Add a prelude that include the `Client` and any extension traits (#176) +* Add support for upgrading connections, which requires a version bump to reqwest. (#183) == 0.1.1 (released 2022-05-13) diff --git a/Cargo.lock b/Cargo.lock index ba6e1dc..fed94e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,9 +352,11 @@ dependencies = [ name = "example-build" version = "0.0.1" dependencies = [ + "base64", "chrono", "progenitor", "progenitor-client", + "rand", "reqwest", "serde", "serde_json", @@ -986,6 +988,12 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1024,6 +1032,7 @@ name = "progenitor" version = "0.2.1-dev" dependencies = [ "anyhow", + "base64", "chrono", "clap", "futures", @@ -1032,6 +1041,7 @@ dependencies = [ "progenitor-client", "progenitor-impl", "progenitor-macro", + "rand", "regress", "reqwest", "schemars", @@ -1101,6 +1111,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.10" diff --git a/README.md b/README.md index 80aab72..dbe2352 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,13 @@ Similarly if there is a `format` field set to `uuid`: +uuid = { version = "1.0.0", features = ["serde", "v4"] } ``` +And if there are any websocket channel endpoints: +```diff +[dependencies] ++base64 = "0.13" ++rand = "0.8" +``` + The macro has some additional fancy options to control the generated code: ```rust @@ -116,7 +123,7 @@ You'll need to add add the following to `Cargo.toml`: +serde_json = "1.0" ``` -(`chrono` and `uuid` as above) +(`chrono`, `uuid`, `base64`, and `rand` as above) Note that `progenitor` is used by `build.rs`, but the generated code required `progenitor-client`. @@ -290,4 +297,4 @@ let result = client ``` Consumers do not need to specify parameters and struct properties that are not -required or for which the API specifies defaults. Neat! \ No newline at end of file +required or for which the API specifies defaults. Neat! diff --git a/example-build/Cargo.toml b/example-build/Cargo.toml index 17427ba..a6e1010 100644 --- a/example-build/Cargo.toml +++ b/example-build/Cargo.toml @@ -7,7 +7,9 @@ edition = "2021" [dependencies] chrono = { version = "0.4", features = ["serde"] } progenitor-client = { path = "../progenitor-client" } -reqwest = { version = "0.11", features = ["json", "stream"] } +reqwest = { version = "0.11.12", features = ["json", "stream"] } +base64 = "0.13" +rand = "0.8" serde = { version = "1.0", features = ["derive"] } uuid = { version = "1.0", features = ["serde", "v4"] } diff --git a/example-macro/Cargo.toml b/example-macro/Cargo.toml index 68f0d47..9fee4e9 100644 --- a/example-macro/Cargo.toml +++ b/example-macro/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] chrono = { version = "0.4", features = ["serde"] } progenitor = { path = "../progenitor" } -reqwest = { version = "0.11", features = ["json", "stream"] } +reqwest = { version = "0.11.12", features = ["json", "stream"] } schemars = { version = "0.8.10", features = ["uuid1"] } serde = { version = "1.0", features = ["derive"] } uuid = { version = "1.0", features = ["serde", "v4"] } diff --git a/progenitor-client/Cargo.toml b/progenitor-client/Cargo.toml index d62aa73..7ce851b 100644 --- a/progenitor-client/Cargo.toml +++ b/progenitor-client/Cargo.toml @@ -10,7 +10,7 @@ description = "An OpenAPI client generator - client support" bytes = "1.2.1" futures-core = "0.3.24" percent-encoding = "2.2" -reqwest = { version = "0.11", default-features = false, features = ["json", "stream"] } +reqwest = { version = "0.11.12", default-features = false, features = ["json", "stream"] } serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7.1" diff --git a/progenitor-client/src/progenitor_client.rs b/progenitor-client/src/progenitor_client.rs index 7096294..6efa63f 100644 --- a/progenitor-client/src/progenitor_client.rs +++ b/progenitor-client/src/progenitor_client.rs @@ -76,6 +76,30 @@ impl ResponseValue { } } +impl ResponseValue { + #[doc(hidden)] + pub async fn upgrade( + response: reqwest::Response, + ) -> Result> { + let status = response.status(); + let headers = response.headers().clone(); + if status == reqwest::StatusCode::SWITCHING_PROTOCOLS { + let inner = response + .upgrade() + .await + .map_err(Error::InvalidResponsePayload)?; + + Ok(Self { + inner, + status, + headers, + }) + } else { + Err(Error::UnexpectedResponse(response)) + } + } +} + impl ResponseValue { #[doc(hidden)] pub fn stream(response: reqwest::Response) -> Self { diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index cb47fc3..10d7f9e 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -26,6 +26,8 @@ pub enum Error { UnexpectedFormat(String), #[error("invalid operation path {0}")] InvalidPath(String), + #[error("invalid dropshot extension use: {0}")] + InvalidExtension(String), #[error("internal error {0}")] InternalError(String), } @@ -36,6 +38,7 @@ pub struct Generator { type_space: TypeSpace, settings: GenerationSettings, uses_futures: bool, + uses_websockets: bool, } #[derive(Default, Clone)] @@ -116,6 +119,7 @@ impl Default for Generator { ), settings: Default::default(), uses_futures: Default::default(), + uses_websockets: Default::default(), } } } @@ -133,6 +137,7 @@ impl Generator { type_space: TypeSpace::new(&type_settings), settings: settings.clone(), uses_futures: false, + uses_websockets: false, } } @@ -426,7 +431,7 @@ impl Generator { "bytes = \"1.1\"", "futures-core = \"0.3\"", "percent-encoding = \"2.1\"", - "reqwest = { version = \"0.11\", features = [\"json\", \"stream\"] }", + "reqwest = { version = \"0.11.12\", features = [\"json\", \"stream\"] }", "serde = { version = \"1.0\", features = [\"derive\"] }", "serde_urlencoded = \"0.7\"", ]; @@ -444,6 +449,10 @@ impl Generator { if self.uses_futures { deps.push("futures = \"0.3\"") } + if self.uses_websockets { + deps.push("base64 = \"0.13\""); + deps.push("rand = \"0.8\""); + } if self.type_space.uses_serde_json() { deps.push("serde_json = \"1.0\"") } diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 1c6a3c9..63e8db1 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -29,6 +29,7 @@ pub(crate) struct OperationMethod { params: Vec, responses: Vec, dropshot_paginated: Option, + dropshot_websocket: bool, } enum HttpMethod { @@ -189,6 +190,7 @@ impl OperationResponseStatus { matches!( self, OperationResponseStatus::Default + | OperationResponseStatus::Code(101) | OperationResponseStatus::Code(200..=299) | OperationResponseStatus::Range(2) ) @@ -225,6 +227,7 @@ enum OperationResponseType { Type(TypeId), None, Raw, + Upgrade, } impl Generator { @@ -338,6 +341,12 @@ impl Generator { }) .collect::>>()?; + let dropshot_websocket = + operation.extensions.get("x-dropshot-websocket").is_some(); + if dropshot_websocket { + self.uses_websockets = true; + } + if let Some(body_param) = self.get_body_param(operation, components)? { params.push(body_param); } @@ -378,9 +387,10 @@ impl Generator { let (status_code, response) = v?; // We categorize responses as "typed" based on the - // "application/json" content type, "raw" if there's any other - // response content type (we don't investigate further), or - // "none" if there is no content. + // "application/json" content type, "upgrade" if it's a + // websocket channel without a meaningful content-type, + // "raw" if there's any other response content type (we don't + // investigate further), or "none" if there is no content. // TODO if there are multiple response content types we could // treat those like different response types and create an // enum; the generated client method would check for the @@ -407,6 +417,8 @@ impl Generator { }; OperationResponseType::Type(typ) + } else if dropshot_websocket { + OperationResponseType::Upgrade } else if response.content.first().is_some() { OperationResponseType::Raw } else { @@ -449,9 +461,25 @@ impl Generator { }); } + // Must accept HTTP 101 Switching Protocols + if dropshot_websocket { + responses.push(OperationResponse { + status_code: OperationResponseStatus::Code(101), + typ: OperationResponseType::Upgrade, + description: None, + }) + } + let dropshot_paginated = self.dropshot_pagination_data(operation, ¶ms, &responses); + if dropshot_websocket && dropshot_paginated.is_some() { + return Err(Error::InvalidExtension(format!( + "conflicting extensions in {:?}", + operation_id + ))); + } + Ok(OperationMethod { operation_id: sanitize(operation_id, Case::Snake), tags: operation.tags.clone(), @@ -465,6 +493,7 @@ impl Generator { params, responses, dropshot_paginated, + dropshot_websocket, }) } @@ -705,6 +734,20 @@ impl Generator { (query_build, query_use) }; + let websock_hdrs = if method.dropshot_websocket { + quote! { + .header(reqwest::header::CONNECTION, "Upgrade") + .header(reqwest::header::UPGRADE, "websocket") + .header(reqwest::header::SEC_WEBSOCKET_VERSION, "13") + .header( + reqwest::header::SEC_WEBSOCKET_KEY, + base64::encode(rand::random::<[u8; 16]>()), + ) + } + } else { + quote! {} + }; + // Generate the path rename map; then use it to generate code for // assigning the path parameters to the `url` variable. let url_renames = method @@ -791,6 +834,11 @@ impl Generator { Ok(ResponseValue::stream(response)) } } + OperationResponseType::Upgrade => { + quote! { + ResponseValue::upgrade(response).await + } + } }; quote! { #pat => { #decode } } @@ -842,6 +890,13 @@ impl Generator { )) } } + OperationResponseType::Upgrade => { + if response.status_code == OperationResponseStatus::Default { + return quote! { } // catch-all handled below + } else { + todo!("non-default error response handling for upgrade requests is not yet implemented"); + } + } }; quote! { #pat => { #decode } } @@ -879,6 +934,7 @@ impl Generator { . #method_func (url) #(#body_func)* #query_use + #websock_hdrs .build()?; #pre_hook let result = #client.client @@ -988,6 +1044,9 @@ impl Generator { OperationResponseType::Raw => { quote! { ByteStream } } + OperationResponseType::Upgrade => { + quote! { reqwest::Upgraded } + } }) // TODO should this be a bytestream? .unwrap_or_else(|| quote! { () }); diff --git a/progenitor-impl/tests/output/propolis-server-builder-tagged.out b/progenitor-impl/tests/output/propolis-server-builder-tagged.out new file mode 100644 index 0000000..8e06105 --- /dev/null +++ b/progenitor-impl/tests/output/propolis-server-builder-tagged.out @@ -0,0 +1,1968 @@ +#[allow(unused_imports)] +use progenitor_client::{encode_path, RequestBuilderExt}; +pub use progenitor_client::{ByteStream, Error, ResponseValue}; +pub mod types { + use serde::{Deserialize, Serialize}; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CrucibleOpts { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cert_pem: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub control: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flush_timeout: Option, + pub id: uuid::Uuid, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key_pem: Option, + pub lossy: bool, + pub read_only: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub root_cert_pem: Option, + pub target: Vec, + } + + impl CrucibleOpts { + pub fn builder() -> builder::CrucibleOpts { + builder::CrucibleOpts::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DiskAttachment { + pub disk_id: uuid::Uuid, + pub generation_id: u64, + pub state: DiskAttachmentState, + } + + impl DiskAttachment { + pub fn builder() -> builder::DiskAttachment { + builder::DiskAttachment::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum DiskAttachmentState { + Detached, + Destroyed, + Faulted, + Attached(uuid::Uuid), + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DiskRequest { + pub device: String, + pub gen: u64, + pub name: String, + pub read_only: bool, + pub slot: Slot, + pub volume_construction_request: VolumeConstructionRequest, + } + + impl DiskRequest { + pub fn builder() -> builder::DiskRequest { + builder::DiskRequest::default() + } + } + + ///Error information from a response. + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Error { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_code: Option, + pub message: String, + pub request_id: String, + } + + impl Error { + pub fn builder() -> builder::Error { + builder::Error::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Instance { + pub disks: Vec, + pub nics: Vec, + pub properties: InstanceProperties, + pub state: InstanceState, + } + + impl Instance { + pub fn builder() -> builder::Instance { + builder::Instance::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceEnsureRequest { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cloud_init_bytes: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub disks: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub migrate: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub nics: Vec, + pub properties: InstanceProperties, + } + + impl InstanceEnsureRequest { + pub fn builder() -> builder::InstanceEnsureRequest { + builder::InstanceEnsureRequest::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceEnsureResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub migrate: Option, + } + + impl InstanceEnsureResponse { + pub fn builder() -> builder::InstanceEnsureResponse { + builder::InstanceEnsureResponse::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceGetResponse { + pub instance: Instance, + } + + impl InstanceGetResponse { + pub fn builder() -> builder::InstanceGetResponse { + builder::InstanceGetResponse::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceMigrateInitiateRequest { + pub migration_id: uuid::Uuid, + pub src_addr: String, + pub src_uuid: uuid::Uuid, + } + + impl InstanceMigrateInitiateRequest { + pub fn builder() -> builder::InstanceMigrateInitiateRequest { + builder::InstanceMigrateInitiateRequest::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceMigrateInitiateResponse { + pub migration_id: uuid::Uuid, + } + + impl InstanceMigrateInitiateResponse { + pub fn builder() -> builder::InstanceMigrateInitiateResponse { + builder::InstanceMigrateInitiateResponse::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceMigrateStatusRequest { + pub migration_id: uuid::Uuid, + } + + impl InstanceMigrateStatusRequest { + pub fn builder() -> builder::InstanceMigrateStatusRequest { + builder::InstanceMigrateStatusRequest::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceMigrateStatusResponse { + pub state: MigrationState, + } + + impl InstanceMigrateStatusResponse { + pub fn builder() -> builder::InstanceMigrateStatusResponse { + builder::InstanceMigrateStatusResponse::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceProperties { + ///ID of the bootrom used to initialize this Instance. + pub bootrom_id: uuid::Uuid, + ///Free-form text description of an Instance. + pub description: String, + ///Unique identifier for this Instance. + pub id: uuid::Uuid, + ///ID of the image used to initialize this Instance. + pub image_id: uuid::Uuid, + ///Size of memory allocated to the Instance, in MiB. + pub memory: u64, + ///Human-readable name of the Instance. + pub name: String, + ///Number of vCPUs to be allocated to the Instance. + pub vcpus: u8, + } + + impl InstanceProperties { + pub fn builder() -> builder::InstanceProperties { + builder::InstanceProperties::default() + } + } + + ///Current state of an Instance. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum InstanceState { + Creating, + Starting, + Running, + Stopping, + Stopped, + Rebooting, + Migrating, + Repairing, + Failed, + Destroyed, + } + + impl ToString for InstanceState { + fn to_string(&self) -> String { + match *self { + Self::Creating => "Creating".to_string(), + Self::Starting => "Starting".to_string(), + Self::Running => "Running".to_string(), + Self::Stopping => "Stopping".to_string(), + Self::Stopped => "Stopped".to_string(), + Self::Rebooting => "Rebooting".to_string(), + Self::Migrating => "Migrating".to_string(), + Self::Repairing => "Repairing".to_string(), + Self::Failed => "Failed".to_string(), + Self::Destroyed => "Destroyed".to_string(), + } + } + } + + impl std::str::FromStr for InstanceState { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Creating" => Ok(Self::Creating), + "Starting" => Ok(Self::Starting), + "Running" => Ok(Self::Running), + "Stopping" => Ok(Self::Stopping), + "Stopped" => Ok(Self::Stopped), + "Rebooting" => Ok(Self::Rebooting), + "Migrating" => Ok(Self::Migrating), + "Repairing" => Ok(Self::Repairing), + "Failed" => Ok(Self::Failed), + "Destroyed" => Ok(Self::Destroyed), + _ => Err("invalid value"), + } + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceStateMonitorRequest { + pub gen: u64, + } + + impl InstanceStateMonitorRequest { + pub fn builder() -> builder::InstanceStateMonitorRequest { + builder::InstanceStateMonitorRequest::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceStateMonitorResponse { + pub gen: u64, + pub state: InstanceState, + } + + impl InstanceStateMonitorResponse { + pub fn builder() -> builder::InstanceStateMonitorResponse { + builder::InstanceStateMonitorResponse::default() + } + } + + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum InstanceStateRequested { + Run, + Stop, + Reboot, + MigrateStart, + } + + impl ToString for InstanceStateRequested { + fn to_string(&self) -> String { + match *self { + Self::Run => "Run".to_string(), + Self::Stop => "Stop".to_string(), + Self::Reboot => "Reboot".to_string(), + Self::MigrateStart => "MigrateStart".to_string(), + } + } + } + + impl std::str::FromStr for InstanceStateRequested { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Run" => Ok(Self::Run), + "Stop" => Ok(Self::Stop), + "Reboot" => Ok(Self::Reboot), + "MigrateStart" => Ok(Self::MigrateStart), + _ => Err("invalid value"), + } + } + } + + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum MigrationState { + Sync, + RamPush, + Pause, + RamPushDirty, + Device, + Arch, + Resume, + RamPull, + Finish, + Error, + } + + impl ToString for MigrationState { + fn to_string(&self) -> String { + match *self { + Self::Sync => "Sync".to_string(), + Self::RamPush => "RamPush".to_string(), + Self::Pause => "Pause".to_string(), + Self::RamPushDirty => "RamPushDirty".to_string(), + Self::Device => "Device".to_string(), + Self::Arch => "Arch".to_string(), + Self::Resume => "Resume".to_string(), + Self::RamPull => "RamPull".to_string(), + Self::Finish => "Finish".to_string(), + Self::Error => "Error".to_string(), + } + } + } + + impl std::str::FromStr for MigrationState { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Sync" => Ok(Self::Sync), + "RamPush" => Ok(Self::RamPush), + "Pause" => Ok(Self::Pause), + "RamPushDirty" => Ok(Self::RamPushDirty), + "Device" => Ok(Self::Device), + "Arch" => Ok(Self::Arch), + "Resume" => Ok(Self::Resume), + "RamPull" => Ok(Self::RamPull), + "Finish" => Ok(Self::Finish), + "Error" => Ok(Self::Error), + _ => Err("invalid value"), + } + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NetworkInterface { + pub attachment: NetworkInterfaceAttachmentState, + pub name: String, + } + + impl NetworkInterface { + pub fn builder() -> builder::NetworkInterface { + builder::NetworkInterface::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum NetworkInterfaceAttachmentState { + Detached, + Faulted, + Attached(Slot), + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NetworkInterfaceRequest { + pub name: String, + pub slot: Slot, + } + + impl NetworkInterfaceRequest { + pub fn builder() -> builder::NetworkInterfaceRequest { + builder::NetworkInterfaceRequest::default() + } + } + + ///A stable index which is translated by Propolis into a PCI BDF, visible + /// to the guest. + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Slot(pub u8); + impl std::ops::Deref for Slot { + type Target = u8; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(tag = "type")] + pub enum VolumeConstructionRequest { + #[serde(rename = "volume")] + Volume { + block_size: u64, + id: uuid::Uuid, + #[serde(default, skip_serializing_if = "Option::is_none")] + read_only_parent: Option>, + sub_volumes: Vec, + }, + #[serde(rename = "url")] + Url { + block_size: u64, + id: uuid::Uuid, + url: String, + }, + #[serde(rename = "region")] + Region { + block_size: u64, + gen: u64, + opts: CrucibleOpts, + }, + #[serde(rename = "file")] + File { + block_size: u64, + id: uuid::Uuid, + path: String, + }, + } + + mod builder { + pub struct CrucibleOpts { + cert_pem: Result, String>, + control: Result, String>, + flush_timeout: Result, String>, + id: Result, + key: Result, String>, + key_pem: Result, String>, + lossy: Result, + read_only: Result, + root_cert_pem: Result, String>, + target: Result, String>, + } + + impl Default for CrucibleOpts { + fn default() -> Self { + Self { + cert_pem: Ok(Default::default()), + control: Ok(Default::default()), + flush_timeout: Ok(Default::default()), + id: Err("no value supplied for id".to_string()), + key: Ok(Default::default()), + key_pem: Ok(Default::default()), + lossy: Err("no value supplied for lossy".to_string()), + read_only: Err("no value supplied for read_only".to_string()), + root_cert_pem: Ok(Default::default()), + target: Err("no value supplied for target".to_string()), + } + } + } + + impl CrucibleOpts { + pub fn cert_pem(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.cert_pem = value + .try_into() + .map_err(|e| format!("error converting supplied value for cert_pem: {}", e)); + self + } + pub fn control(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.control = value + .try_into() + .map_err(|e| format!("error converting supplied value for control: {}", e)); + self + } + pub fn flush_timeout(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.flush_timeout = value.try_into().map_err(|e| { + format!("error converting supplied value for flush_timeout: {}", e) + }); + self + } + pub fn id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.id = value + .try_into() + .map_err(|e| format!("error converting supplied value for id: {}", e)); + self + } + pub fn key(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.key = value + .try_into() + .map_err(|e| format!("error converting supplied value for key: {}", e)); + self + } + pub fn key_pem(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.key_pem = value + .try_into() + .map_err(|e| format!("error converting supplied value for key_pem: {}", e)); + self + } + pub fn lossy(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.lossy = value + .try_into() + .map_err(|e| format!("error converting supplied value for lossy: {}", e)); + self + } + pub fn read_only(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.read_only = value + .try_into() + .map_err(|e| format!("error converting supplied value for read_only: {}", e)); + self + } + pub fn root_cert_pem(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.root_cert_pem = value.try_into().map_err(|e| { + format!("error converting supplied value for root_cert_pem: {}", e) + }); + self + } + pub fn target(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.target = value + .try_into() + .map_err(|e| format!("error converting supplied value for target: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CrucibleOpts { + type Error = String; + fn try_from(value: CrucibleOpts) -> Result { + Ok(Self { + cert_pem: value.cert_pem?, + control: value.control?, + flush_timeout: value.flush_timeout?, + id: value.id?, + key: value.key?, + key_pem: value.key_pem?, + lossy: value.lossy?, + read_only: value.read_only?, + root_cert_pem: value.root_cert_pem?, + target: value.target?, + }) + } + } + + pub struct DiskAttachment { + disk_id: Result, + generation_id: Result, + state: Result, + } + + impl Default for DiskAttachment { + fn default() -> Self { + Self { + disk_id: Err("no value supplied for disk_id".to_string()), + generation_id: Err("no value supplied for generation_id".to_string()), + state: Err("no value supplied for state".to_string()), + } + } + } + + impl DiskAttachment { + pub fn disk_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.disk_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for disk_id: {}", e)); + self + } + pub fn generation_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.generation_id = value.try_into().map_err(|e| { + format!("error converting supplied value for generation_id: {}", e) + }); + self + } + pub fn state(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.state = value + .try_into() + .map_err(|e| format!("error converting supplied value for state: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::DiskAttachment { + type Error = String; + fn try_from(value: DiskAttachment) -> Result { + Ok(Self { + disk_id: value.disk_id?, + generation_id: value.generation_id?, + state: value.state?, + }) + } + } + + pub struct DiskRequest { + device: Result, + gen: Result, + name: Result, + read_only: Result, + slot: Result, + volume_construction_request: Result, + } + + impl Default for DiskRequest { + fn default() -> Self { + Self { + device: Err("no value supplied for device".to_string()), + gen: Err("no value supplied for gen".to_string()), + name: Err("no value supplied for name".to_string()), + read_only: Err("no value supplied for read_only".to_string()), + slot: Err("no value supplied for slot".to_string()), + volume_construction_request: Err( + "no value supplied for volume_construction_request".to_string(), + ), + } + } + } + + impl DiskRequest { + pub fn device(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.device = value + .try_into() + .map_err(|e| format!("error converting supplied value for device: {}", e)); + self + } + pub fn gen(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.gen = value + .try_into() + .map_err(|e| format!("error converting supplied value for gen: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn read_only(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.read_only = value + .try_into() + .map_err(|e| format!("error converting supplied value for read_only: {}", e)); + self + } + pub fn slot(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.slot = value + .try_into() + .map_err(|e| format!("error converting supplied value for slot: {}", e)); + self + } + pub fn volume_construction_request(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.volume_construction_request = value.try_into().map_err(|e| { + format!( + "error converting supplied value for volume_construction_request: {}", + e + ) + }); + self + } + } + + impl std::convert::TryFrom for super::DiskRequest { + type Error = String; + fn try_from(value: DiskRequest) -> Result { + Ok(Self { + device: value.device?, + gen: value.gen?, + name: value.name?, + read_only: value.read_only?, + slot: value.slot?, + volume_construction_request: value.volume_construction_request?, + }) + } + } + + pub struct Error { + error_code: Result, String>, + message: Result, + request_id: Result, + } + + impl Default for Error { + fn default() -> Self { + Self { + error_code: Ok(Default::default()), + message: Err("no value supplied for message".to_string()), + request_id: Err("no value supplied for request_id".to_string()), + } + } + } + + impl Error { + pub fn error_code(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.error_code = value + .try_into() + .map_err(|e| format!("error converting supplied value for error_code: {}", e)); + self + } + pub fn message(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.message = value + .try_into() + .map_err(|e| format!("error converting supplied value for message: {}", e)); + self + } + pub fn request_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.request_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for request_id: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::Error { + type Error = String; + fn try_from(value: Error) -> Result { + Ok(Self { + error_code: value.error_code?, + message: value.message?, + request_id: value.request_id?, + }) + } + } + + pub struct Instance { + disks: Result, String>, + nics: Result, String>, + properties: Result, + state: Result, + } + + impl Default for Instance { + fn default() -> Self { + Self { + disks: Err("no value supplied for disks".to_string()), + nics: Err("no value supplied for nics".to_string()), + properties: Err("no value supplied for properties".to_string()), + state: Err("no value supplied for state".to_string()), + } + } + } + + impl Instance { + pub fn disks(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.disks = value + .try_into() + .map_err(|e| format!("error converting supplied value for disks: {}", e)); + self + } + pub fn nics(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.nics = value + .try_into() + .map_err(|e| format!("error converting supplied value for nics: {}", e)); + self + } + pub fn properties(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.properties = value + .try_into() + .map_err(|e| format!("error converting supplied value for properties: {}", e)); + self + } + pub fn state(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.state = value + .try_into() + .map_err(|e| format!("error converting supplied value for state: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::Instance { + type Error = String; + fn try_from(value: Instance) -> Result { + Ok(Self { + disks: value.disks?, + nics: value.nics?, + properties: value.properties?, + state: value.state?, + }) + } + } + + pub struct InstanceEnsureRequest { + cloud_init_bytes: Result, String>, + disks: Result, String>, + migrate: Result, String>, + nics: Result, String>, + properties: Result, + } + + impl Default for InstanceEnsureRequest { + fn default() -> Self { + Self { + cloud_init_bytes: Ok(Default::default()), + disks: Ok(Default::default()), + migrate: Ok(Default::default()), + nics: Ok(Default::default()), + properties: Err("no value supplied for properties".to_string()), + } + } + } + + impl InstanceEnsureRequest { + pub fn cloud_init_bytes(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.cloud_init_bytes = value.try_into().map_err(|e| { + format!( + "error converting supplied value for cloud_init_bytes: {}", + e + ) + }); + self + } + pub fn disks(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.disks = value + .try_into() + .map_err(|e| format!("error converting supplied value for disks: {}", e)); + self + } + pub fn migrate(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.migrate = value + .try_into() + .map_err(|e| format!("error converting supplied value for migrate: {}", e)); + self + } + pub fn nics(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.nics = value + .try_into() + .map_err(|e| format!("error converting supplied value for nics: {}", e)); + self + } + pub fn properties(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.properties = value + .try_into() + .map_err(|e| format!("error converting supplied value for properties: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceEnsureRequest { + type Error = String; + fn try_from(value: InstanceEnsureRequest) -> Result { + Ok(Self { + cloud_init_bytes: value.cloud_init_bytes?, + disks: value.disks?, + migrate: value.migrate?, + nics: value.nics?, + properties: value.properties?, + }) + } + } + + pub struct InstanceEnsureResponse { + migrate: Result, String>, + } + + impl Default for InstanceEnsureResponse { + fn default() -> Self { + Self { + migrate: Ok(Default::default()), + } + } + } + + impl InstanceEnsureResponse { + pub fn migrate(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.migrate = value + .try_into() + .map_err(|e| format!("error converting supplied value for migrate: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceEnsureResponse { + type Error = String; + fn try_from(value: InstanceEnsureResponse) -> Result { + Ok(Self { + migrate: value.migrate?, + }) + } + } + + pub struct InstanceGetResponse { + instance: Result, + } + + impl Default for InstanceGetResponse { + fn default() -> Self { + Self { + instance: Err("no value supplied for instance".to_string()), + } + } + } + + impl InstanceGetResponse { + pub fn instance(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.instance = value + .try_into() + .map_err(|e| format!("error converting supplied value for instance: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceGetResponse { + type Error = String; + fn try_from(value: InstanceGetResponse) -> Result { + Ok(Self { + instance: value.instance?, + }) + } + } + + pub struct InstanceMigrateInitiateRequest { + migration_id: Result, + src_addr: Result, + src_uuid: Result, + } + + impl Default for InstanceMigrateInitiateRequest { + fn default() -> Self { + Self { + migration_id: Err("no value supplied for migration_id".to_string()), + src_addr: Err("no value supplied for src_addr".to_string()), + src_uuid: Err("no value supplied for src_uuid".to_string()), + } + } + } + + impl InstanceMigrateInitiateRequest { + pub fn migration_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.migration_id = value.try_into().map_err(|e| { + format!("error converting supplied value for migration_id: {}", e) + }); + self + } + pub fn src_addr(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.src_addr = value + .try_into() + .map_err(|e| format!("error converting supplied value for src_addr: {}", e)); + self + } + pub fn src_uuid(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.src_uuid = value + .try_into() + .map_err(|e| format!("error converting supplied value for src_uuid: {}", e)); + self + } + } + + impl std::convert::TryFrom + for super::InstanceMigrateInitiateRequest + { + type Error = String; + fn try_from(value: InstanceMigrateInitiateRequest) -> Result { + Ok(Self { + migration_id: value.migration_id?, + src_addr: value.src_addr?, + src_uuid: value.src_uuid?, + }) + } + } + + pub struct InstanceMigrateInitiateResponse { + migration_id: Result, + } + + impl Default for InstanceMigrateInitiateResponse { + fn default() -> Self { + Self { + migration_id: Err("no value supplied for migration_id".to_string()), + } + } + } + + impl InstanceMigrateInitiateResponse { + pub fn migration_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.migration_id = value.try_into().map_err(|e| { + format!("error converting supplied value for migration_id: {}", e) + }); + self + } + } + + impl std::convert::TryFrom + for super::InstanceMigrateInitiateResponse + { + type Error = String; + fn try_from(value: InstanceMigrateInitiateResponse) -> Result { + Ok(Self { + migration_id: value.migration_id?, + }) + } + } + + pub struct InstanceMigrateStatusRequest { + migration_id: Result, + } + + impl Default for InstanceMigrateStatusRequest { + fn default() -> Self { + Self { + migration_id: Err("no value supplied for migration_id".to_string()), + } + } + } + + impl InstanceMigrateStatusRequest { + pub fn migration_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.migration_id = value.try_into().map_err(|e| { + format!("error converting supplied value for migration_id: {}", e) + }); + self + } + } + + impl std::convert::TryFrom for super::InstanceMigrateStatusRequest { + type Error = String; + fn try_from(value: InstanceMigrateStatusRequest) -> Result { + Ok(Self { + migration_id: value.migration_id?, + }) + } + } + + pub struct InstanceMigrateStatusResponse { + state: Result, + } + + impl Default for InstanceMigrateStatusResponse { + fn default() -> Self { + Self { + state: Err("no value supplied for state".to_string()), + } + } + } + + impl InstanceMigrateStatusResponse { + pub fn state(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.state = value + .try_into() + .map_err(|e| format!("error converting supplied value for state: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceMigrateStatusResponse { + type Error = String; + fn try_from(value: InstanceMigrateStatusResponse) -> Result { + Ok(Self { + state: value.state?, + }) + } + } + + pub struct InstanceProperties { + bootrom_id: Result, + description: Result, + id: Result, + image_id: Result, + memory: Result, + name: Result, + vcpus: Result, + } + + impl Default for InstanceProperties { + fn default() -> Self { + Self { + bootrom_id: Err("no value supplied for bootrom_id".to_string()), + description: Err("no value supplied for description".to_string()), + id: Err("no value supplied for id".to_string()), + image_id: Err("no value supplied for image_id".to_string()), + memory: Err("no value supplied for memory".to_string()), + name: Err("no value supplied for name".to_string()), + vcpus: Err("no value supplied for vcpus".to_string()), + } + } + } + + impl InstanceProperties { + pub fn bootrom_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.bootrom_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for bootrom_id: {}", e)); + self + } + pub fn description(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.description = value + .try_into() + .map_err(|e| format!("error converting supplied value for description: {}", e)); + self + } + pub fn id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.id = value + .try_into() + .map_err(|e| format!("error converting supplied value for id: {}", e)); + self + } + pub fn image_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.image_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for image_id: {}", e)); + self + } + pub fn memory(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.memory = value + .try_into() + .map_err(|e| format!("error converting supplied value for memory: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn vcpus(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.vcpus = value + .try_into() + .map_err(|e| format!("error converting supplied value for vcpus: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceProperties { + type Error = String; + fn try_from(value: InstanceProperties) -> Result { + Ok(Self { + bootrom_id: value.bootrom_id?, + description: value.description?, + id: value.id?, + image_id: value.image_id?, + memory: value.memory?, + name: value.name?, + vcpus: value.vcpus?, + }) + } + } + + pub struct InstanceStateMonitorRequest { + gen: Result, + } + + impl Default for InstanceStateMonitorRequest { + fn default() -> Self { + Self { + gen: Err("no value supplied for gen".to_string()), + } + } + } + + impl InstanceStateMonitorRequest { + pub fn gen(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.gen = value + .try_into() + .map_err(|e| format!("error converting supplied value for gen: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceStateMonitorRequest { + type Error = String; + fn try_from(value: InstanceStateMonitorRequest) -> Result { + Ok(Self { gen: value.gen? }) + } + } + + pub struct InstanceStateMonitorResponse { + gen: Result, + state: Result, + } + + impl Default for InstanceStateMonitorResponse { + fn default() -> Self { + Self { + gen: Err("no value supplied for gen".to_string()), + state: Err("no value supplied for state".to_string()), + } + } + } + + impl InstanceStateMonitorResponse { + pub fn gen(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.gen = value + .try_into() + .map_err(|e| format!("error converting supplied value for gen: {}", e)); + self + } + pub fn state(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.state = value + .try_into() + .map_err(|e| format!("error converting supplied value for state: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceStateMonitorResponse { + type Error = String; + fn try_from(value: InstanceStateMonitorResponse) -> Result { + Ok(Self { + gen: value.gen?, + state: value.state?, + }) + } + } + + pub struct NetworkInterface { + attachment: Result, + name: Result, + } + + impl Default for NetworkInterface { + fn default() -> Self { + Self { + attachment: Err("no value supplied for attachment".to_string()), + name: Err("no value supplied for name".to_string()), + } + } + } + + impl NetworkInterface { + pub fn attachment(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.attachment = value + .try_into() + .map_err(|e| format!("error converting supplied value for attachment: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::NetworkInterface { + type Error = String; + fn try_from(value: NetworkInterface) -> Result { + Ok(Self { + attachment: value.attachment?, + name: value.name?, + }) + } + } + + pub struct NetworkInterfaceRequest { + name: Result, + slot: Result, + } + + impl Default for NetworkInterfaceRequest { + fn default() -> Self { + Self { + name: Err("no value supplied for name".to_string()), + slot: Err("no value supplied for slot".to_string()), + } + } + } + + impl NetworkInterfaceRequest { + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn slot(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.slot = value + .try_into() + .map_err(|e| format!("error converting supplied value for slot: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::NetworkInterfaceRequest { + type Error = String; + fn try_from(value: NetworkInterfaceRequest) -> Result { + Ok(Self { + name: value.name?, + slot: value.slot?, + }) + } + } + } +} + +#[derive(Clone, Debug)] +pub struct Client { + pub(crate) baseurl: String, + pub(crate) client: reqwest::Client, +} + +impl Client { + pub fn new(baseurl: &str) -> Self { + let dur = std::time::Duration::from_secs(15); + let client = reqwest::ClientBuilder::new() + .connect_timeout(dur) + .timeout(dur) + .build() + .unwrap(); + Self::new_with_client(baseurl, client) + } + + pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { + Self { + baseurl: baseurl.to_string(), + client, + } + } + + pub fn baseurl(&self) -> &String { + &self.baseurl + } + + pub fn client(&self) -> &reqwest::Client { + &self.client + } +} + +impl Client { + ///Sends a `GET` request to `/instance` + ///```ignore + /// let response = client.instance_get() + /// .send() + /// .await; + /// ``` + pub fn instance_get(&self) -> builder::InstanceGet { + builder::InstanceGet::new(self) + } + + ///Sends a `PUT` request to `/instance` + ///```ignore + /// let response = client.instance_ensure() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn instance_ensure(&self) -> builder::InstanceEnsure { + builder::InstanceEnsure::new(self) + } + + ///Issue a snapshot request to a crucible backend + /// + ///Sends a `POST` request to `/instance/disk/{id}/snapshot/{snapshot_id}` + ///```ignore + /// let response = client.instance_issue_crucible_snapshot_request() + /// .id(id) + /// .snapshot_id(snapshot_id) + /// .send() + /// .await; + /// ``` + pub fn instance_issue_crucible_snapshot_request( + &self, + ) -> builder::InstanceIssueCrucibleSnapshotRequest { + builder::InstanceIssueCrucibleSnapshotRequest::new(self) + } + + ///Sends a `GET` request to `/instance/migrate/status` + ///```ignore + /// let response = client.instance_migrate_status() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn instance_migrate_status(&self) -> builder::InstanceMigrateStatus { + builder::InstanceMigrateStatus::new(self) + } + + ///Sends a `GET` request to `/instance/serial` + ///```ignore + /// let response = client.instance_serial() + /// .send() + /// .await; + /// ``` + pub fn instance_serial(&self) -> builder::InstanceSerial { + builder::InstanceSerial::new(self) + } + + ///Sends a `PUT` request to `/instance/state` + ///```ignore + /// let response = client.instance_state_put() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn instance_state_put(&self) -> builder::InstanceStatePut { + builder::InstanceStatePut::new(self) + } + + ///Sends a `GET` request to `/instance/state-monitor` + ///```ignore + /// let response = client.instance_state_monitor() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn instance_state_monitor(&self) -> builder::InstanceStateMonitor { + builder::InstanceStateMonitor::new(self) + } +} + +pub mod builder { + use super::types; + #[allow(unused_imports)] + use super::{encode_path, ByteStream, Error, RequestBuilderExt, ResponseValue}; + ///Builder for [`Client::instance_get`] + /// + ///[`Client::instance_get`]: super::Client::instance_get + #[derive(Debug, Clone)] + pub struct InstanceGet<'a> { + client: &'a super::Client, + } + + impl<'a> InstanceGet<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { client } + } + + ///Sends a `GET` request to `/instance` + pub async fn send( + self, + ) -> Result, Error> { + let Self { client } = self; + let url = format!("{}/instance", client.baseurl,); + let request = client.client.get(url).build()?; + let result = client.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)), + } + } + } + + ///Builder for [`Client::instance_ensure`] + /// + ///[`Client::instance_ensure`]: super::Client::instance_ensure + #[derive(Debug, Clone)] + pub struct InstanceEnsure<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> InstanceEnsure<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + body: Err("body was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value + .try_into() + .map_err(|_| "conversion to `InstanceEnsureRequest` for body failed".to_string()); + self + } + + ///Sends a `PUT` request to `/instance` + pub async fn send( + self, + ) -> Result, Error> { + let Self { client, body } = self; + let body = body.map_err(Error::InvalidRequest)?; + let url = format!("{}/instance", client.baseurl,); + let request = client.client.put(url).json(&body).build()?; + let result = client.client.execute(request).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)), + } + } + } + + ///Builder for [`Client::instance_issue_crucible_snapshot_request`] + /// + ///[`Client::instance_issue_crucible_snapshot_request`]: super::Client::instance_issue_crucible_snapshot_request + #[derive(Debug, Clone)] + pub struct InstanceIssueCrucibleSnapshotRequest<'a> { + client: &'a super::Client, + id: Result, + snapshot_id: Result, + } + + impl<'a> InstanceIssueCrucibleSnapshotRequest<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + id: Err("id was not initialized".to_string()), + snapshot_id: Err("snapshot_id was not initialized".to_string()), + } + } + + pub fn id(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.id = value + .try_into() + .map_err(|_| "conversion to `uuid :: Uuid` for id failed".to_string()); + self + } + + pub fn snapshot_id(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.snapshot_id = value + .try_into() + .map_err(|_| "conversion to `uuid :: Uuid` for snapshot_id failed".to_string()); + self + } + + ///Sends a `POST` request to + /// `/instance/disk/{id}/snapshot/{snapshot_id}` + pub async fn send(self) -> Result, Error> { + let Self { + client, + id, + snapshot_id, + } = self; + let id = id.map_err(Error::InvalidRequest)?; + let snapshot_id = snapshot_id.map_err(Error::InvalidRequest)?; + let url = format!( + "{}/instance/disk/{}/snapshot/{}", + client.baseurl, + encode_path(&id.to_string()), + encode_path(&snapshot_id.to_string()), + ); + let request = client.client.post(url).build()?; + let result = client.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)), + } + } + } + + ///Builder for [`Client::instance_migrate_status`] + /// + ///[`Client::instance_migrate_status`]: super::Client::instance_migrate_status + #[derive(Debug, Clone)] + pub struct InstanceMigrateStatus<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> InstanceMigrateStatus<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + body: Err("body was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value.try_into().map_err(|_| { + "conversion to `InstanceMigrateStatusRequest` for body failed".to_string() + }); + self + } + + ///Sends a `GET` request to `/instance/migrate/status` + pub async fn send( + self, + ) -> Result, Error> + { + let Self { client, body } = self; + let body = body.map_err(Error::InvalidRequest)?; + let url = format!("{}/instance/migrate/status", client.baseurl,); + let request = client.client.get(url).json(&body).build()?; + let result = client.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)), + } + } + } + + ///Builder for [`Client::instance_serial`] + /// + ///[`Client::instance_serial`]: super::Client::instance_serial + #[derive(Debug, Clone)] + pub struct InstanceSerial<'a> { + client: &'a super::Client, + } + + impl<'a> InstanceSerial<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { client } + } + + ///Sends a `GET` request to `/instance/serial` + pub async fn send( + self, + ) -> Result, Error> { + let Self { client } = self; + let url = format!("{}/instance/serial", client.baseurl,); + let request = client + .client + .get(url) + .header(reqwest::header::CONNECTION, "Upgrade") + .header(reqwest::header::UPGRADE, "websocket") + .header(reqwest::header::SEC_WEBSOCKET_VERSION, "13") + .header( + reqwest::header::SEC_WEBSOCKET_KEY, + base64::encode(rand::random::<[u8; 16]>()), + ) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 101u16 => ResponseValue::upgrade(response).await, + 200..=299 => ResponseValue::upgrade(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + ///Builder for [`Client::instance_state_put`] + /// + ///[`Client::instance_state_put`]: super::Client::instance_state_put + #[derive(Debug, Clone)] + pub struct InstanceStatePut<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> InstanceStatePut<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + body: Err("body was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value + .try_into() + .map_err(|_| "conversion to `InstanceStateRequested` for body failed".to_string()); + self + } + + ///Sends a `PUT` request to `/instance/state` + pub async fn send(self) -> Result, Error> { + let Self { client, body } = self; + let body = body.map_err(Error::InvalidRequest)?; + let url = format!("{}/instance/state", client.baseurl,); + let request = client.client.put(url).json(&body).build()?; + let result = client.client.execute(request).await; + 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)), + } + } + } + + ///Builder for [`Client::instance_state_monitor`] + /// + ///[`Client::instance_state_monitor`]: super::Client::instance_state_monitor + #[derive(Debug, Clone)] + pub struct InstanceStateMonitor<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> InstanceStateMonitor<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + body: Err("body was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value.try_into().map_err(|_| { + "conversion to `InstanceStateMonitorRequest` for body failed".to_string() + }); + self + } + + ///Sends a `GET` request to `/instance/state-monitor` + pub async fn send( + self, + ) -> Result, Error> + { + let Self { client, body } = self; + let body = body.map_err(Error::InvalidRequest)?; + let url = format!("{}/instance/state-monitor", client.baseurl,); + let request = client.client.get(url).json(&body).build()?; + let result = client.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)), + } + } + } +} + +pub mod prelude { + pub use super::Client; +} diff --git a/progenitor-impl/tests/output/propolis-server-builder.out b/progenitor-impl/tests/output/propolis-server-builder.out new file mode 100644 index 0000000..aed4196 --- /dev/null +++ b/progenitor-impl/tests/output/propolis-server-builder.out @@ -0,0 +1,1974 @@ +#[allow(unused_imports)] +use progenitor_client::{encode_path, RequestBuilderExt}; +pub use progenitor_client::{ByteStream, Error, ResponseValue}; +pub mod types { + use serde::{Deserialize, Serialize}; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct CrucibleOpts { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cert_pem: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub control: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flush_timeout: Option, + pub id: uuid::Uuid, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key_pem: Option, + pub lossy: bool, + pub read_only: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub root_cert_pem: Option, + pub target: Vec, + } + + impl CrucibleOpts { + pub fn builder() -> builder::CrucibleOpts { + builder::CrucibleOpts::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct DiskAttachment { + pub disk_id: uuid::Uuid, + pub generation_id: u64, + pub state: DiskAttachmentState, + } + + impl DiskAttachment { + pub fn builder() -> builder::DiskAttachment { + builder::DiskAttachment::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub enum DiskAttachmentState { + Detached, + Destroyed, + Faulted, + Attached(uuid::Uuid), + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct DiskRequest { + pub device: String, + pub gen: u64, + pub name: String, + pub read_only: bool, + pub slot: Slot, + pub volume_construction_request: VolumeConstructionRequest, + } + + impl DiskRequest { + pub fn builder() -> builder::DiskRequest { + builder::DiskRequest::default() + } + } + + ///Error information from a response. + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct Error { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_code: Option, + pub message: String, + pub request_id: String, + } + + impl Error { + pub fn builder() -> builder::Error { + builder::Error::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct Instance { + pub disks: Vec, + pub nics: Vec, + pub properties: InstanceProperties, + pub state: InstanceState, + } + + impl Instance { + pub fn builder() -> builder::Instance { + builder::Instance::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceEnsureRequest { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cloud_init_bytes: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub disks: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub migrate: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub nics: Vec, + pub properties: InstanceProperties, + } + + impl InstanceEnsureRequest { + pub fn builder() -> builder::InstanceEnsureRequest { + builder::InstanceEnsureRequest::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceEnsureResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub migrate: Option, + } + + impl InstanceEnsureResponse { + pub fn builder() -> builder::InstanceEnsureResponse { + builder::InstanceEnsureResponse::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceGetResponse { + pub instance: Instance, + } + + impl InstanceGetResponse { + pub fn builder() -> builder::InstanceGetResponse { + builder::InstanceGetResponse::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceMigrateInitiateRequest { + pub migration_id: uuid::Uuid, + pub src_addr: String, + pub src_uuid: uuid::Uuid, + } + + impl InstanceMigrateInitiateRequest { + pub fn builder() -> builder::InstanceMigrateInitiateRequest { + builder::InstanceMigrateInitiateRequest::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceMigrateInitiateResponse { + pub migration_id: uuid::Uuid, + } + + impl InstanceMigrateInitiateResponse { + pub fn builder() -> builder::InstanceMigrateInitiateResponse { + builder::InstanceMigrateInitiateResponse::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceMigrateStatusRequest { + pub migration_id: uuid::Uuid, + } + + impl InstanceMigrateStatusRequest { + pub fn builder() -> builder::InstanceMigrateStatusRequest { + builder::InstanceMigrateStatusRequest::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceMigrateStatusResponse { + pub state: MigrationState, + } + + impl InstanceMigrateStatusResponse { + pub fn builder() -> builder::InstanceMigrateStatusResponse { + builder::InstanceMigrateStatusResponse::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceProperties { + ///ID of the bootrom used to initialize this Instance. + pub bootrom_id: uuid::Uuid, + ///Free-form text description of an Instance. + pub description: String, + ///Unique identifier for this Instance. + pub id: uuid::Uuid, + ///ID of the image used to initialize this Instance. + pub image_id: uuid::Uuid, + ///Size of memory allocated to the Instance, in MiB. + pub memory: u64, + ///Human-readable name of the Instance. + pub name: String, + ///Number of vCPUs to be allocated to the Instance. + pub vcpus: u8, + } + + impl InstanceProperties { + pub fn builder() -> builder::InstanceProperties { + builder::InstanceProperties::default() + } + } + + ///Current state of an Instance. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, JsonSchema, + )] + pub enum InstanceState { + Creating, + Starting, + Running, + Stopping, + Stopped, + Rebooting, + Migrating, + Repairing, + Failed, + Destroyed, + } + + impl ToString for InstanceState { + fn to_string(&self) -> String { + match *self { + Self::Creating => "Creating".to_string(), + Self::Starting => "Starting".to_string(), + Self::Running => "Running".to_string(), + Self::Stopping => "Stopping".to_string(), + Self::Stopped => "Stopped".to_string(), + Self::Rebooting => "Rebooting".to_string(), + Self::Migrating => "Migrating".to_string(), + Self::Repairing => "Repairing".to_string(), + Self::Failed => "Failed".to_string(), + Self::Destroyed => "Destroyed".to_string(), + } + } + } + + impl std::str::FromStr for InstanceState { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Creating" => Ok(Self::Creating), + "Starting" => Ok(Self::Starting), + "Running" => Ok(Self::Running), + "Stopping" => Ok(Self::Stopping), + "Stopped" => Ok(Self::Stopped), + "Rebooting" => Ok(Self::Rebooting), + "Migrating" => Ok(Self::Migrating), + "Repairing" => Ok(Self::Repairing), + "Failed" => Ok(Self::Failed), + "Destroyed" => Ok(Self::Destroyed), + _ => Err("invalid value"), + } + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceStateMonitorRequest { + pub gen: u64, + } + + impl InstanceStateMonitorRequest { + pub fn builder() -> builder::InstanceStateMonitorRequest { + builder::InstanceStateMonitorRequest::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct InstanceStateMonitorResponse { + pub gen: u64, + pub state: InstanceState, + } + + impl InstanceStateMonitorResponse { + pub fn builder() -> builder::InstanceStateMonitorResponse { + builder::InstanceStateMonitorResponse::default() + } + } + + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, JsonSchema, + )] + pub enum InstanceStateRequested { + Run, + Stop, + Reboot, + MigrateStart, + } + + impl ToString for InstanceStateRequested { + fn to_string(&self) -> String { + match *self { + Self::Run => "Run".to_string(), + Self::Stop => "Stop".to_string(), + Self::Reboot => "Reboot".to_string(), + Self::MigrateStart => "MigrateStart".to_string(), + } + } + } + + impl std::str::FromStr for InstanceStateRequested { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Run" => Ok(Self::Run), + "Stop" => Ok(Self::Stop), + "Reboot" => Ok(Self::Reboot), + "MigrateStart" => Ok(Self::MigrateStart), + _ => Err("invalid value"), + } + } + } + + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, JsonSchema, + )] + pub enum MigrationState { + Sync, + RamPush, + Pause, + RamPushDirty, + Device, + Arch, + Resume, + RamPull, + Finish, + Error, + } + + impl ToString for MigrationState { + fn to_string(&self) -> String { + match *self { + Self::Sync => "Sync".to_string(), + Self::RamPush => "RamPush".to_string(), + Self::Pause => "Pause".to_string(), + Self::RamPushDirty => "RamPushDirty".to_string(), + Self::Device => "Device".to_string(), + Self::Arch => "Arch".to_string(), + Self::Resume => "Resume".to_string(), + Self::RamPull => "RamPull".to_string(), + Self::Finish => "Finish".to_string(), + Self::Error => "Error".to_string(), + } + } + } + + impl std::str::FromStr for MigrationState { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Sync" => Ok(Self::Sync), + "RamPush" => Ok(Self::RamPush), + "Pause" => Ok(Self::Pause), + "RamPushDirty" => Ok(Self::RamPushDirty), + "Device" => Ok(Self::Device), + "Arch" => Ok(Self::Arch), + "Resume" => Ok(Self::Resume), + "RamPull" => Ok(Self::RamPull), + "Finish" => Ok(Self::Finish), + "Error" => Ok(Self::Error), + _ => Err("invalid value"), + } + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct NetworkInterface { + pub attachment: NetworkInterfaceAttachmentState, + pub name: String, + } + + impl NetworkInterface { + pub fn builder() -> builder::NetworkInterface { + builder::NetworkInterface::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub enum NetworkInterfaceAttachmentState { + Detached, + Faulted, + Attached(Slot), + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct NetworkInterfaceRequest { + pub name: String, + pub slot: Slot, + } + + impl NetworkInterfaceRequest { + pub fn builder() -> builder::NetworkInterfaceRequest { + builder::NetworkInterfaceRequest::default() + } + } + + ///A stable index which is translated by Propolis into a PCI BDF, visible + /// to the guest. + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + pub struct Slot(pub u8); + impl std::ops::Deref for Slot { + type Target = u8; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] + #[serde(tag = "type")] + pub enum VolumeConstructionRequest { + #[serde(rename = "volume")] + Volume { + block_size: u64, + id: uuid::Uuid, + #[serde(default, skip_serializing_if = "Option::is_none")] + read_only_parent: Option>, + sub_volumes: Vec, + }, + #[serde(rename = "url")] + Url { + block_size: u64, + id: uuid::Uuid, + url: String, + }, + #[serde(rename = "region")] + Region { + block_size: u64, + gen: u64, + opts: CrucibleOpts, + }, + #[serde(rename = "file")] + File { + block_size: u64, + id: uuid::Uuid, + path: String, + }, + } + + mod builder { + pub struct CrucibleOpts { + cert_pem: Result, String>, + control: Result, String>, + flush_timeout: Result, String>, + id: Result, + key: Result, String>, + key_pem: Result, String>, + lossy: Result, + read_only: Result, + root_cert_pem: Result, String>, + target: Result, String>, + } + + impl Default for CrucibleOpts { + fn default() -> Self { + Self { + cert_pem: Ok(Default::default()), + control: Ok(Default::default()), + flush_timeout: Ok(Default::default()), + id: Err("no value supplied for id".to_string()), + key: Ok(Default::default()), + key_pem: Ok(Default::default()), + lossy: Err("no value supplied for lossy".to_string()), + read_only: Err("no value supplied for read_only".to_string()), + root_cert_pem: Ok(Default::default()), + target: Err("no value supplied for target".to_string()), + } + } + } + + impl CrucibleOpts { + pub fn cert_pem(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.cert_pem = value + .try_into() + .map_err(|e| format!("error converting supplied value for cert_pem: {}", e)); + self + } + pub fn control(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.control = value + .try_into() + .map_err(|e| format!("error converting supplied value for control: {}", e)); + self + } + pub fn flush_timeout(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.flush_timeout = value.try_into().map_err(|e| { + format!("error converting supplied value for flush_timeout: {}", e) + }); + self + } + pub fn id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.id = value + .try_into() + .map_err(|e| format!("error converting supplied value for id: {}", e)); + self + } + pub fn key(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.key = value + .try_into() + .map_err(|e| format!("error converting supplied value for key: {}", e)); + self + } + pub fn key_pem(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.key_pem = value + .try_into() + .map_err(|e| format!("error converting supplied value for key_pem: {}", e)); + self + } + pub fn lossy(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.lossy = value + .try_into() + .map_err(|e| format!("error converting supplied value for lossy: {}", e)); + self + } + pub fn read_only(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.read_only = value + .try_into() + .map_err(|e| format!("error converting supplied value for read_only: {}", e)); + self + } + pub fn root_cert_pem(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.root_cert_pem = value.try_into().map_err(|e| { + format!("error converting supplied value for root_cert_pem: {}", e) + }); + self + } + pub fn target(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.target = value + .try_into() + .map_err(|e| format!("error converting supplied value for target: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CrucibleOpts { + type Error = String; + fn try_from(value: CrucibleOpts) -> Result { + Ok(Self { + cert_pem: value.cert_pem?, + control: value.control?, + flush_timeout: value.flush_timeout?, + id: value.id?, + key: value.key?, + key_pem: value.key_pem?, + lossy: value.lossy?, + read_only: value.read_only?, + root_cert_pem: value.root_cert_pem?, + target: value.target?, + }) + } + } + + pub struct DiskAttachment { + disk_id: Result, + generation_id: Result, + state: Result, + } + + impl Default for DiskAttachment { + fn default() -> Self { + Self { + disk_id: Err("no value supplied for disk_id".to_string()), + generation_id: Err("no value supplied for generation_id".to_string()), + state: Err("no value supplied for state".to_string()), + } + } + } + + impl DiskAttachment { + pub fn disk_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.disk_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for disk_id: {}", e)); + self + } + pub fn generation_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.generation_id = value.try_into().map_err(|e| { + format!("error converting supplied value for generation_id: {}", e) + }); + self + } + pub fn state(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.state = value + .try_into() + .map_err(|e| format!("error converting supplied value for state: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::DiskAttachment { + type Error = String; + fn try_from(value: DiskAttachment) -> Result { + Ok(Self { + disk_id: value.disk_id?, + generation_id: value.generation_id?, + state: value.state?, + }) + } + } + + pub struct DiskRequest { + device: Result, + gen: Result, + name: Result, + read_only: Result, + slot: Result, + volume_construction_request: Result, + } + + impl Default for DiskRequest { + fn default() -> Self { + Self { + device: Err("no value supplied for device".to_string()), + gen: Err("no value supplied for gen".to_string()), + name: Err("no value supplied for name".to_string()), + read_only: Err("no value supplied for read_only".to_string()), + slot: Err("no value supplied for slot".to_string()), + volume_construction_request: Err( + "no value supplied for volume_construction_request".to_string(), + ), + } + } + } + + impl DiskRequest { + pub fn device(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.device = value + .try_into() + .map_err(|e| format!("error converting supplied value for device: {}", e)); + self + } + pub fn gen(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.gen = value + .try_into() + .map_err(|e| format!("error converting supplied value for gen: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn read_only(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.read_only = value + .try_into() + .map_err(|e| format!("error converting supplied value for read_only: {}", e)); + self + } + pub fn slot(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.slot = value + .try_into() + .map_err(|e| format!("error converting supplied value for slot: {}", e)); + self + } + pub fn volume_construction_request(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.volume_construction_request = value.try_into().map_err(|e| { + format!( + "error converting supplied value for volume_construction_request: {}", + e + ) + }); + self + } + } + + impl std::convert::TryFrom for super::DiskRequest { + type Error = String; + fn try_from(value: DiskRequest) -> Result { + Ok(Self { + device: value.device?, + gen: value.gen?, + name: value.name?, + read_only: value.read_only?, + slot: value.slot?, + volume_construction_request: value.volume_construction_request?, + }) + } + } + + pub struct Error { + error_code: Result, String>, + message: Result, + request_id: Result, + } + + impl Default for Error { + fn default() -> Self { + Self { + error_code: Ok(Default::default()), + message: Err("no value supplied for message".to_string()), + request_id: Err("no value supplied for request_id".to_string()), + } + } + } + + impl Error { + pub fn error_code(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.error_code = value + .try_into() + .map_err(|e| format!("error converting supplied value for error_code: {}", e)); + self + } + pub fn message(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.message = value + .try_into() + .map_err(|e| format!("error converting supplied value for message: {}", e)); + self + } + pub fn request_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.request_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for request_id: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::Error { + type Error = String; + fn try_from(value: Error) -> Result { + Ok(Self { + error_code: value.error_code?, + message: value.message?, + request_id: value.request_id?, + }) + } + } + + pub struct Instance { + disks: Result, String>, + nics: Result, String>, + properties: Result, + state: Result, + } + + impl Default for Instance { + fn default() -> Self { + Self { + disks: Err("no value supplied for disks".to_string()), + nics: Err("no value supplied for nics".to_string()), + properties: Err("no value supplied for properties".to_string()), + state: Err("no value supplied for state".to_string()), + } + } + } + + impl Instance { + pub fn disks(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.disks = value + .try_into() + .map_err(|e| format!("error converting supplied value for disks: {}", e)); + self + } + pub fn nics(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.nics = value + .try_into() + .map_err(|e| format!("error converting supplied value for nics: {}", e)); + self + } + pub fn properties(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.properties = value + .try_into() + .map_err(|e| format!("error converting supplied value for properties: {}", e)); + self + } + pub fn state(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.state = value + .try_into() + .map_err(|e| format!("error converting supplied value for state: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::Instance { + type Error = String; + fn try_from(value: Instance) -> Result { + Ok(Self { + disks: value.disks?, + nics: value.nics?, + properties: value.properties?, + state: value.state?, + }) + } + } + + pub struct InstanceEnsureRequest { + cloud_init_bytes: Result, String>, + disks: Result, String>, + migrate: Result, String>, + nics: Result, String>, + properties: Result, + } + + impl Default for InstanceEnsureRequest { + fn default() -> Self { + Self { + cloud_init_bytes: Ok(Default::default()), + disks: Ok(Default::default()), + migrate: Ok(Default::default()), + nics: Ok(Default::default()), + properties: Err("no value supplied for properties".to_string()), + } + } + } + + impl InstanceEnsureRequest { + pub fn cloud_init_bytes(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.cloud_init_bytes = value.try_into().map_err(|e| { + format!( + "error converting supplied value for cloud_init_bytes: {}", + e + ) + }); + self + } + pub fn disks(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.disks = value + .try_into() + .map_err(|e| format!("error converting supplied value for disks: {}", e)); + self + } + pub fn migrate(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.migrate = value + .try_into() + .map_err(|e| format!("error converting supplied value for migrate: {}", e)); + self + } + pub fn nics(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.nics = value + .try_into() + .map_err(|e| format!("error converting supplied value for nics: {}", e)); + self + } + pub fn properties(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.properties = value + .try_into() + .map_err(|e| format!("error converting supplied value for properties: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceEnsureRequest { + type Error = String; + fn try_from(value: InstanceEnsureRequest) -> Result { + Ok(Self { + cloud_init_bytes: value.cloud_init_bytes?, + disks: value.disks?, + migrate: value.migrate?, + nics: value.nics?, + properties: value.properties?, + }) + } + } + + pub struct InstanceEnsureResponse { + migrate: Result, String>, + } + + impl Default for InstanceEnsureResponse { + fn default() -> Self { + Self { + migrate: Ok(Default::default()), + } + } + } + + impl InstanceEnsureResponse { + pub fn migrate(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.migrate = value + .try_into() + .map_err(|e| format!("error converting supplied value for migrate: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceEnsureResponse { + type Error = String; + fn try_from(value: InstanceEnsureResponse) -> Result { + Ok(Self { + migrate: value.migrate?, + }) + } + } + + pub struct InstanceGetResponse { + instance: Result, + } + + impl Default for InstanceGetResponse { + fn default() -> Self { + Self { + instance: Err("no value supplied for instance".to_string()), + } + } + } + + impl InstanceGetResponse { + pub fn instance(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.instance = value + .try_into() + .map_err(|e| format!("error converting supplied value for instance: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceGetResponse { + type Error = String; + fn try_from(value: InstanceGetResponse) -> Result { + Ok(Self { + instance: value.instance?, + }) + } + } + + pub struct InstanceMigrateInitiateRequest { + migration_id: Result, + src_addr: Result, + src_uuid: Result, + } + + impl Default for InstanceMigrateInitiateRequest { + fn default() -> Self { + Self { + migration_id: Err("no value supplied for migration_id".to_string()), + src_addr: Err("no value supplied for src_addr".to_string()), + src_uuid: Err("no value supplied for src_uuid".to_string()), + } + } + } + + impl InstanceMigrateInitiateRequest { + pub fn migration_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.migration_id = value.try_into().map_err(|e| { + format!("error converting supplied value for migration_id: {}", e) + }); + self + } + pub fn src_addr(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.src_addr = value + .try_into() + .map_err(|e| format!("error converting supplied value for src_addr: {}", e)); + self + } + pub fn src_uuid(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.src_uuid = value + .try_into() + .map_err(|e| format!("error converting supplied value for src_uuid: {}", e)); + self + } + } + + impl std::convert::TryFrom + for super::InstanceMigrateInitiateRequest + { + type Error = String; + fn try_from(value: InstanceMigrateInitiateRequest) -> Result { + Ok(Self { + migration_id: value.migration_id?, + src_addr: value.src_addr?, + src_uuid: value.src_uuid?, + }) + } + } + + pub struct InstanceMigrateInitiateResponse { + migration_id: Result, + } + + impl Default for InstanceMigrateInitiateResponse { + fn default() -> Self { + Self { + migration_id: Err("no value supplied for migration_id".to_string()), + } + } + } + + impl InstanceMigrateInitiateResponse { + pub fn migration_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.migration_id = value.try_into().map_err(|e| { + format!("error converting supplied value for migration_id: {}", e) + }); + self + } + } + + impl std::convert::TryFrom + for super::InstanceMigrateInitiateResponse + { + type Error = String; + fn try_from(value: InstanceMigrateInitiateResponse) -> Result { + Ok(Self { + migration_id: value.migration_id?, + }) + } + } + + pub struct InstanceMigrateStatusRequest { + migration_id: Result, + } + + impl Default for InstanceMigrateStatusRequest { + fn default() -> Self { + Self { + migration_id: Err("no value supplied for migration_id".to_string()), + } + } + } + + impl InstanceMigrateStatusRequest { + pub fn migration_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.migration_id = value.try_into().map_err(|e| { + format!("error converting supplied value for migration_id: {}", e) + }); + self + } + } + + impl std::convert::TryFrom for super::InstanceMigrateStatusRequest { + type Error = String; + fn try_from(value: InstanceMigrateStatusRequest) -> Result { + Ok(Self { + migration_id: value.migration_id?, + }) + } + } + + pub struct InstanceMigrateStatusResponse { + state: Result, + } + + impl Default for InstanceMigrateStatusResponse { + fn default() -> Self { + Self { + state: Err("no value supplied for state".to_string()), + } + } + } + + impl InstanceMigrateStatusResponse { + pub fn state(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.state = value + .try_into() + .map_err(|e| format!("error converting supplied value for state: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceMigrateStatusResponse { + type Error = String; + fn try_from(value: InstanceMigrateStatusResponse) -> Result { + Ok(Self { + state: value.state?, + }) + } + } + + pub struct InstanceProperties { + bootrom_id: Result, + description: Result, + id: Result, + image_id: Result, + memory: Result, + name: Result, + vcpus: Result, + } + + impl Default for InstanceProperties { + fn default() -> Self { + Self { + bootrom_id: Err("no value supplied for bootrom_id".to_string()), + description: Err("no value supplied for description".to_string()), + id: Err("no value supplied for id".to_string()), + image_id: Err("no value supplied for image_id".to_string()), + memory: Err("no value supplied for memory".to_string()), + name: Err("no value supplied for name".to_string()), + vcpus: Err("no value supplied for vcpus".to_string()), + } + } + } + + impl InstanceProperties { + pub fn bootrom_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.bootrom_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for bootrom_id: {}", e)); + self + } + pub fn description(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.description = value + .try_into() + .map_err(|e| format!("error converting supplied value for description: {}", e)); + self + } + pub fn id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.id = value + .try_into() + .map_err(|e| format!("error converting supplied value for id: {}", e)); + self + } + pub fn image_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.image_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for image_id: {}", e)); + self + } + pub fn memory(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.memory = value + .try_into() + .map_err(|e| format!("error converting supplied value for memory: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn vcpus(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.vcpus = value + .try_into() + .map_err(|e| format!("error converting supplied value for vcpus: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceProperties { + type Error = String; + fn try_from(value: InstanceProperties) -> Result { + Ok(Self { + bootrom_id: value.bootrom_id?, + description: value.description?, + id: value.id?, + image_id: value.image_id?, + memory: value.memory?, + name: value.name?, + vcpus: value.vcpus?, + }) + } + } + + pub struct InstanceStateMonitorRequest { + gen: Result, + } + + impl Default for InstanceStateMonitorRequest { + fn default() -> Self { + Self { + gen: Err("no value supplied for gen".to_string()), + } + } + } + + impl InstanceStateMonitorRequest { + pub fn gen(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.gen = value + .try_into() + .map_err(|e| format!("error converting supplied value for gen: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceStateMonitorRequest { + type Error = String; + fn try_from(value: InstanceStateMonitorRequest) -> Result { + Ok(Self { gen: value.gen? }) + } + } + + pub struct InstanceStateMonitorResponse { + gen: Result, + state: Result, + } + + impl Default for InstanceStateMonitorResponse { + fn default() -> Self { + Self { + gen: Err("no value supplied for gen".to_string()), + state: Err("no value supplied for state".to_string()), + } + } + } + + impl InstanceStateMonitorResponse { + pub fn gen(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.gen = value + .try_into() + .map_err(|e| format!("error converting supplied value for gen: {}", e)); + self + } + pub fn state(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.state = value + .try_into() + .map_err(|e| format!("error converting supplied value for state: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::InstanceStateMonitorResponse { + type Error = String; + fn try_from(value: InstanceStateMonitorResponse) -> Result { + Ok(Self { + gen: value.gen?, + state: value.state?, + }) + } + } + + pub struct NetworkInterface { + attachment: Result, + name: Result, + } + + impl Default for NetworkInterface { + fn default() -> Self { + Self { + attachment: Err("no value supplied for attachment".to_string()), + name: Err("no value supplied for name".to_string()), + } + } + } + + impl NetworkInterface { + pub fn attachment(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.attachment = value + .try_into() + .map_err(|e| format!("error converting supplied value for attachment: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::NetworkInterface { + type Error = String; + fn try_from(value: NetworkInterface) -> Result { + Ok(Self { + attachment: value.attachment?, + name: value.name?, + }) + } + } + + pub struct NetworkInterfaceRequest { + name: Result, + slot: Result, + } + + impl Default for NetworkInterfaceRequest { + fn default() -> Self { + Self { + name: Err("no value supplied for name".to_string()), + slot: Err("no value supplied for slot".to_string()), + } + } + } + + impl NetworkInterfaceRequest { + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn slot(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.slot = value + .try_into() + .map_err(|e| format!("error converting supplied value for slot: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::NetworkInterfaceRequest { + type Error = String; + fn try_from(value: NetworkInterfaceRequest) -> Result { + Ok(Self { + name: value.name?, + slot: value.slot?, + }) + } + } + } +} + +#[derive(Clone, Debug)] +pub struct Client { + pub(crate) baseurl: String, + pub(crate) client: reqwest::Client, +} + +impl Client { + pub fn new(baseurl: &str) -> Self { + let dur = std::time::Duration::from_secs(15); + let client = reqwest::ClientBuilder::new() + .connect_timeout(dur) + .timeout(dur) + .build() + .unwrap(); + Self::new_with_client(baseurl, client) + } + + pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { + Self { + baseurl: baseurl.to_string(), + client, + } + } + + pub fn baseurl(&self) -> &String { + &self.baseurl + } + + pub fn client(&self) -> &reqwest::Client { + &self.client + } +} + +impl Client { + ///Sends a `GET` request to `/instance` + ///```ignore + /// let response = client.instance_get() + /// .send() + /// .await; + /// ``` + pub fn instance_get(&self) -> builder::InstanceGet { + builder::InstanceGet::new(self) + } + + ///Sends a `PUT` request to `/instance` + ///```ignore + /// let response = client.instance_ensure() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn instance_ensure(&self) -> builder::InstanceEnsure { + builder::InstanceEnsure::new(self) + } + + ///Issue a snapshot request to a crucible backend + /// + ///Sends a `POST` request to `/instance/disk/{id}/snapshot/{snapshot_id}` + ///```ignore + /// let response = client.instance_issue_crucible_snapshot_request() + /// .id(id) + /// .snapshot_id(snapshot_id) + /// .send() + /// .await; + /// ``` + pub fn instance_issue_crucible_snapshot_request( + &self, + ) -> builder::InstanceIssueCrucibleSnapshotRequest { + builder::InstanceIssueCrucibleSnapshotRequest::new(self) + } + + ///Sends a `GET` request to `/instance/migrate/status` + ///```ignore + /// let response = client.instance_migrate_status() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn instance_migrate_status(&self) -> builder::InstanceMigrateStatus { + builder::InstanceMigrateStatus::new(self) + } + + ///Sends a `GET` request to `/instance/serial` + ///```ignore + /// let response = client.instance_serial() + /// .send() + /// .await; + /// ``` + pub fn instance_serial(&self) -> builder::InstanceSerial { + builder::InstanceSerial::new(self) + } + + ///Sends a `PUT` request to `/instance/state` + ///```ignore + /// let response = client.instance_state_put() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn instance_state_put(&self) -> builder::InstanceStatePut { + builder::InstanceStatePut::new(self) + } + + ///Sends a `GET` request to `/instance/state-monitor` + ///```ignore + /// let response = client.instance_state_monitor() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn instance_state_monitor(&self) -> builder::InstanceStateMonitor { + builder::InstanceStateMonitor::new(self) + } +} + +pub mod builder { + use super::types; + #[allow(unused_imports)] + use super::{encode_path, ByteStream, Error, RequestBuilderExt, ResponseValue}; + ///Builder for [`Client::instance_get`] + /// + ///[`Client::instance_get`]: super::Client::instance_get + #[derive(Debug, Clone)] + pub struct InstanceGet<'a> { + client: &'a super::Client, + } + + impl<'a> InstanceGet<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { client } + } + + ///Sends a `GET` request to `/instance` + pub async fn send( + self, + ) -> Result, Error> { + let Self { client } = self; + let url = format!("{}/instance", client.baseurl,); + let request = client.client.get(url).build()?; + let result = client.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)), + } + } + } + + ///Builder for [`Client::instance_ensure`] + /// + ///[`Client::instance_ensure`]: super::Client::instance_ensure + #[derive(Debug, Clone)] + pub struct InstanceEnsure<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> InstanceEnsure<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + body: Err("body was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value + .try_into() + .map_err(|_| "conversion to `InstanceEnsureRequest` for body failed".to_string()); + self + } + + ///Sends a `PUT` request to `/instance` + pub async fn send( + self, + ) -> Result, Error> { + let Self { client, body } = self; + let body = body.map_err(Error::InvalidRequest)?; + let url = format!("{}/instance", client.baseurl,); + let request = client.client.put(url).json(&body).build()?; + let result = client.client.execute(request).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)), + } + } + } + + ///Builder for [`Client::instance_issue_crucible_snapshot_request`] + /// + ///[`Client::instance_issue_crucible_snapshot_request`]: super::Client::instance_issue_crucible_snapshot_request + #[derive(Debug, Clone)] + pub struct InstanceIssueCrucibleSnapshotRequest<'a> { + client: &'a super::Client, + id: Result, + snapshot_id: Result, + } + + impl<'a> InstanceIssueCrucibleSnapshotRequest<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + id: Err("id was not initialized".to_string()), + snapshot_id: Err("snapshot_id was not initialized".to_string()), + } + } + + pub fn id(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.id = value + .try_into() + .map_err(|_| "conversion to `uuid :: Uuid` for id failed".to_string()); + self + } + + pub fn snapshot_id(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.snapshot_id = value + .try_into() + .map_err(|_| "conversion to `uuid :: Uuid` for snapshot_id failed".to_string()); + self + } + + ///Sends a `POST` request to + /// `/instance/disk/{id}/snapshot/{snapshot_id}` + pub async fn send(self) -> Result, Error> { + let Self { + client, + id, + snapshot_id, + } = self; + let id = id.map_err(Error::InvalidRequest)?; + let snapshot_id = snapshot_id.map_err(Error::InvalidRequest)?; + let url = format!( + "{}/instance/disk/{}/snapshot/{}", + client.baseurl, + encode_path(&id.to_string()), + encode_path(&snapshot_id.to_string()), + ); + let request = client.client.post(url).build()?; + let result = client.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)), + } + } + } + + ///Builder for [`Client::instance_migrate_status`] + /// + ///[`Client::instance_migrate_status`]: super::Client::instance_migrate_status + #[derive(Debug, Clone)] + pub struct InstanceMigrateStatus<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> InstanceMigrateStatus<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + body: Err("body was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value.try_into().map_err(|_| { + "conversion to `InstanceMigrateStatusRequest` for body failed".to_string() + }); + self + } + + ///Sends a `GET` request to `/instance/migrate/status` + pub async fn send( + self, + ) -> Result, Error> + { + let Self { client, body } = self; + let body = body.map_err(Error::InvalidRequest)?; + let url = format!("{}/instance/migrate/status", client.baseurl,); + let request = client.client.get(url).json(&body).build()?; + let result = client.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)), + } + } + } + + ///Builder for [`Client::instance_serial`] + /// + ///[`Client::instance_serial`]: super::Client::instance_serial + #[derive(Debug, Clone)] + pub struct InstanceSerial<'a> { + client: &'a super::Client, + } + + impl<'a> InstanceSerial<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { client } + } + + ///Sends a `GET` request to `/instance/serial` + pub async fn send( + self, + ) -> Result, Error> { + let Self { client } = self; + let url = format!("{}/instance/serial", client.baseurl,); + let request = client + .client + .get(url) + .header(reqwest::header::CONNECTION, "Upgrade") + .header(reqwest::header::UPGRADE, "websocket") + .header(reqwest::header::SEC_WEBSOCKET_VERSION, "13") + .header( + reqwest::header::SEC_WEBSOCKET_KEY, + base64::encode(rand::random::<[u8; 16]>()), + ) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 101u16 => ResponseValue::upgrade(response).await, + 200..=299 => ResponseValue::upgrade(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + ///Builder for [`Client::instance_state_put`] + /// + ///[`Client::instance_state_put`]: super::Client::instance_state_put + #[derive(Debug, Clone)] + pub struct InstanceStatePut<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> InstanceStatePut<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + body: Err("body was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value + .try_into() + .map_err(|_| "conversion to `InstanceStateRequested` for body failed".to_string()); + self + } + + ///Sends a `PUT` request to `/instance/state` + pub async fn send(self) -> Result, Error> { + let Self { client, body } = self; + let body = body.map_err(Error::InvalidRequest)?; + let url = format!("{}/instance/state", client.baseurl,); + let request = client.client.put(url).json(&body).build()?; + let result = client.client.execute(request).await; + 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)), + } + } + } + + ///Builder for [`Client::instance_state_monitor`] + /// + ///[`Client::instance_state_monitor`]: super::Client::instance_state_monitor + #[derive(Debug, Clone)] + pub struct InstanceStateMonitor<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> InstanceStateMonitor<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client, + body: Err("body was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value.try_into().map_err(|_| { + "conversion to `InstanceStateMonitorRequest` for body failed".to_string() + }); + self + } + + ///Sends a `GET` request to `/instance/state-monitor` + pub async fn send( + self, + ) -> Result, Error> + { + let Self { client, body } = self; + let body = body.map_err(Error::InvalidRequest)?; + let url = format!("{}/instance/state-monitor", client.baseurl,); + let request = client.client.get(url).json(&body).build()?; + let result = client.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)), + } + } + } +} + +pub mod prelude { + pub use self::super::Client; +} diff --git a/progenitor-impl/tests/output/propolis-server-positional.out b/progenitor-impl/tests/output/propolis-server-positional.out new file mode 100644 index 0000000..ab6ce04 --- /dev/null +++ b/progenitor-impl/tests/output/propolis-server-positional.out @@ -0,0 +1,535 @@ +#[allow(unused_imports)] +use progenitor_client::{encode_path, RequestBuilderExt}; +pub use progenitor_client::{ByteStream, Error, ResponseValue}; +pub mod types { + use serde::{Deserialize, Serialize}; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CrucibleOpts { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cert_pem: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub control: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub flush_timeout: Option, + pub id: uuid::Uuid, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub key_pem: Option, + pub lossy: bool, + pub read_only: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub root_cert_pem: Option, + pub target: Vec, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DiskAttachment { + pub disk_id: uuid::Uuid, + pub generation_id: u64, + pub state: DiskAttachmentState, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum DiskAttachmentState { + Detached, + Destroyed, + Faulted, + Attached(uuid::Uuid), + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DiskRequest { + pub device: String, + pub gen: u64, + pub name: String, + pub read_only: bool, + pub slot: Slot, + pub volume_construction_request: VolumeConstructionRequest, + } + + ///Error information from a response. + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Error { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_code: Option, + pub message: String, + pub request_id: String, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Instance { + pub disks: Vec, + pub nics: Vec, + pub properties: InstanceProperties, + pub state: InstanceState, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceEnsureRequest { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cloud_init_bytes: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub disks: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub migrate: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub nics: Vec, + pub properties: InstanceProperties, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceEnsureResponse { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub migrate: Option, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceGetResponse { + pub instance: Instance, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceMigrateInitiateRequest { + pub migration_id: uuid::Uuid, + pub src_addr: String, + pub src_uuid: uuid::Uuid, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceMigrateInitiateResponse { + pub migration_id: uuid::Uuid, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceMigrateStatusRequest { + pub migration_id: uuid::Uuid, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceMigrateStatusResponse { + pub state: MigrationState, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceProperties { + ///ID of the bootrom used to initialize this Instance. + pub bootrom_id: uuid::Uuid, + ///Free-form text description of an Instance. + pub description: String, + ///Unique identifier for this Instance. + pub id: uuid::Uuid, + ///ID of the image used to initialize this Instance. + pub image_id: uuid::Uuid, + ///Size of memory allocated to the Instance, in MiB. + pub memory: u64, + ///Human-readable name of the Instance. + pub name: String, + ///Number of vCPUs to be allocated to the Instance. + pub vcpus: u8, + } + + ///Current state of an Instance. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum InstanceState { + Creating, + Starting, + Running, + Stopping, + Stopped, + Rebooting, + Migrating, + Repairing, + Failed, + Destroyed, + } + + impl ToString for InstanceState { + fn to_string(&self) -> String { + match *self { + Self::Creating => "Creating".to_string(), + Self::Starting => "Starting".to_string(), + Self::Running => "Running".to_string(), + Self::Stopping => "Stopping".to_string(), + Self::Stopped => "Stopped".to_string(), + Self::Rebooting => "Rebooting".to_string(), + Self::Migrating => "Migrating".to_string(), + Self::Repairing => "Repairing".to_string(), + Self::Failed => "Failed".to_string(), + Self::Destroyed => "Destroyed".to_string(), + } + } + } + + impl std::str::FromStr for InstanceState { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Creating" => Ok(Self::Creating), + "Starting" => Ok(Self::Starting), + "Running" => Ok(Self::Running), + "Stopping" => Ok(Self::Stopping), + "Stopped" => Ok(Self::Stopped), + "Rebooting" => Ok(Self::Rebooting), + "Migrating" => Ok(Self::Migrating), + "Repairing" => Ok(Self::Repairing), + "Failed" => Ok(Self::Failed), + "Destroyed" => Ok(Self::Destroyed), + _ => Err("invalid value"), + } + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceStateMonitorRequest { + pub gen: u64, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceStateMonitorResponse { + pub gen: u64, + pub state: InstanceState, + } + + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum InstanceStateRequested { + Run, + Stop, + Reboot, + MigrateStart, + } + + impl ToString for InstanceStateRequested { + fn to_string(&self) -> String { + match *self { + Self::Run => "Run".to_string(), + Self::Stop => "Stop".to_string(), + Self::Reboot => "Reboot".to_string(), + Self::MigrateStart => "MigrateStart".to_string(), + } + } + } + + impl std::str::FromStr for InstanceStateRequested { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Run" => Ok(Self::Run), + "Stop" => Ok(Self::Stop), + "Reboot" => Ok(Self::Reboot), + "MigrateStart" => Ok(Self::MigrateStart), + _ => Err("invalid value"), + } + } + } + + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum MigrationState { + Sync, + RamPush, + Pause, + RamPushDirty, + Device, + Arch, + Resume, + RamPull, + Finish, + Error, + } + + impl ToString for MigrationState { + fn to_string(&self) -> String { + match *self { + Self::Sync => "Sync".to_string(), + Self::RamPush => "RamPush".to_string(), + Self::Pause => "Pause".to_string(), + Self::RamPushDirty => "RamPushDirty".to_string(), + Self::Device => "Device".to_string(), + Self::Arch => "Arch".to_string(), + Self::Resume => "Resume".to_string(), + Self::RamPull => "RamPull".to_string(), + Self::Finish => "Finish".to_string(), + Self::Error => "Error".to_string(), + } + } + } + + impl std::str::FromStr for MigrationState { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "Sync" => Ok(Self::Sync), + "RamPush" => Ok(Self::RamPush), + "Pause" => Ok(Self::Pause), + "RamPushDirty" => Ok(Self::RamPushDirty), + "Device" => Ok(Self::Device), + "Arch" => Ok(Self::Arch), + "Resume" => Ok(Self::Resume), + "RamPull" => Ok(Self::RamPull), + "Finish" => Ok(Self::Finish), + "Error" => Ok(Self::Error), + _ => Err("invalid value"), + } + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NetworkInterface { + pub attachment: NetworkInterfaceAttachmentState, + pub name: String, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub enum NetworkInterfaceAttachmentState { + Detached, + Faulted, + Attached(Slot), + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NetworkInterfaceRequest { + pub name: String, + pub slot: Slot, + } + + ///A stable index which is translated by Propolis into a PCI BDF, visible + /// to the guest. + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Slot(pub u8); + impl std::ops::Deref for Slot { + type Target = u8; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(tag = "type")] + pub enum VolumeConstructionRequest { + #[serde(rename = "volume")] + Volume { + block_size: u64, + id: uuid::Uuid, + #[serde(default, skip_serializing_if = "Option::is_none")] + read_only_parent: Option>, + sub_volumes: Vec, + }, + #[serde(rename = "url")] + Url { + block_size: u64, + id: uuid::Uuid, + url: String, + }, + #[serde(rename = "region")] + Region { + block_size: u64, + gen: u64, + opts: CrucibleOpts, + }, + #[serde(rename = "file")] + File { + block_size: u64, + id: uuid::Uuid, + path: String, + }, + } +} + +#[derive(Clone, Debug)] +pub struct Client { + pub(crate) baseurl: String, + pub(crate) client: reqwest::Client, +} + +impl Client { + pub fn new(baseurl: &str) -> Self { + let dur = std::time::Duration::from_secs(15); + let client = reqwest::ClientBuilder::new() + .connect_timeout(dur) + .timeout(dur) + .build() + .unwrap(); + Self::new_with_client(baseurl, client) + } + + pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { + Self { + baseurl: baseurl.to_string(), + client, + } + } + + pub fn baseurl(&self) -> &String { + &self.baseurl + } + + pub fn client(&self) -> &reqwest::Client { + &self.client + } +} + +impl Client { + ///Sends a `GET` request to `/instance` + pub async fn instance_get<'a>( + &'a self, + ) -> Result, Error> { + let url = format!("{}/instance", 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)), + } + } + + ///Sends a `PUT` request to `/instance` + pub async fn instance_ensure<'a>( + &'a self, + body: &'a types::InstanceEnsureRequest, + ) -> Result, Error> { + let url = format!("{}/instance", self.baseurl,); + let request = self.client.put(url).json(&body).build()?; + let result = self.client.execute(request).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)), + } + } + + ///Issue a snapshot request to a crucible backend + /// + ///Sends a `POST` request to `/instance/disk/{id}/snapshot/{snapshot_id}` + pub async fn instance_issue_crucible_snapshot_request<'a>( + &'a self, + id: &'a uuid::Uuid, + snapshot_id: &'a uuid::Uuid, + ) -> Result, Error> { + let url = format!( + "{}/instance/disk/{}/snapshot/{}", + self.baseurl, + encode_path(&id.to_string()), + encode_path(&snapshot_id.to_string()), + ); + let request = self.client.post(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)), + } + } + + ///Sends a `GET` request to `/instance/migrate/status` + pub async fn instance_migrate_status<'a>( + &'a self, + body: &'a types::InstanceMigrateStatusRequest, + ) -> Result, Error> { + let url = format!("{}/instance/migrate/status", self.baseurl,); + let request = self.client.get(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)), + } + } + + ///Sends a `GET` request to `/instance/serial` + pub async fn instance_serial<'a>( + &'a self, + ) -> Result, Error> { + let url = format!("{}/instance/serial", self.baseurl,); + let request = self + .client + .get(url) + .header(reqwest::header::CONNECTION, "Upgrade") + .header(reqwest::header::UPGRADE, "websocket") + .header(reqwest::header::SEC_WEBSOCKET_VERSION, "13") + .header( + reqwest::header::SEC_WEBSOCKET_KEY, + base64::encode(rand::random::<[u8; 16]>()), + ) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 101u16 => ResponseValue::upgrade(response).await, + 200..=299 => ResponseValue::upgrade(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + + ///Sends a `PUT` request to `/instance/state` + pub async fn instance_state_put<'a>( + &'a self, + body: types::InstanceStateRequested, + ) -> Result, Error> { + let url = format!("{}/instance/state", self.baseurl,); + let request = self.client.put(url).json(&body).build()?; + let result = self.client.execute(request).await; + 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)), + } + } + + ///Sends a `GET` request to `/instance/state-monitor` + pub async fn instance_state_monitor<'a>( + &'a self, + body: &'a types::InstanceStateMonitorRequest, + ) -> Result, Error> { + let url = format!("{}/instance/state-monitor", self.baseurl,); + let request = self.client.get(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)), + } + } +} + +pub mod prelude { + pub use super::Client; +} diff --git a/progenitor-impl/tests/test_output.rs b/progenitor-impl/tests/test_output.rs index c9c9076..1a4d798 100644 --- a/progenitor-impl/tests/test_output.rs +++ b/progenitor-impl/tests/test_output.rs @@ -60,6 +60,11 @@ fn test_nexus() { verify_apis("nexus"); } +#[test] +fn test_propolis_server() { + verify_apis("propolis-server"); +} + // TODO this file is full of inconsistencies and incorrectly specified types. // It's an interesting test to consider whether we try to do our best to // interpret the intent or just fail. diff --git a/progenitor/Cargo.toml b/progenitor/Cargo.toml index d830700..f498529 100644 --- a/progenitor/Cargo.toml +++ b/progenitor/Cargo.toml @@ -17,10 +17,12 @@ serde_json = "1.0" clap = { version = "3.2.22", features = ["derive"] } [dev-dependencies] +base64 = "0.13" chrono = { version = "0.4", features = ["serde"] } -futures = "0.3.21" +futures = "0.3.24" percent-encoding = "2.2" +rand = "0.8" regress = "0.4.1" -reqwest = { version = "0.11", features = ["json", "stream"] } +reqwest = { version = "0.11.12", features = ["json", "stream"] } schemars = { version = "0.8.10", features = ["uuid1"] } uuid = { version = "1.0", features = ["serde", "v4"] } diff --git a/progenitor/tests/build_propolis.rs b/progenitor/tests/build_propolis.rs new file mode 100644 index 0000000..360e39b --- /dev/null +++ b/progenitor/tests/build_propolis.rs @@ -0,0 +1,23 @@ +// Copyright 2022 Oxide Computer Company + +// ensure that the websocket channel used for serial console compiles. +mod propolis_client { + progenitor::generate_api!( + spec = "../sample_openapi/propolis-server.json", + interface = Builder, + tags = Merged, + ); +} + +use propolis_client::Client; + +pub fn _ignore() { + let _ = async { + let _upgraded: reqwest::Upgraded = Client::new("") + .instance_serial() + .send() + .await + .unwrap() + .into_inner(); + }; +} diff --git a/sample_openapi/propolis-server.json b/sample_openapi/propolis-server.json new file mode 100644 index 0000000..ee6ba4e --- /dev/null +++ b/sample_openapi/propolis-server.json @@ -0,0 +1,850 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Oxide Propolis Server API", + "description": "API for interacting with the Propolis hypervisor frontend.", + "contact": { + "url": "https://oxide.computer", + "email": "api@oxide.computer" + }, + "version": "0.0.1" + }, + "paths": { + "/instance": { + "get": { + "operationId": "instance_get", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceGetResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "operationId": "instance_ensure", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceEnsureRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceEnsureResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/instance/disk/{id}/snapshot/{snapshot_id}": { + "post": { + "summary": "Issue a snapshot request to a crucible backend", + "operationId": "instance_issue_crucible_snapshot_request", + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "simple" + }, + { + "in": "path", + "name": "snapshot_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Null", + "type": "string", + "enum": [ + null + ] + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/instance/migrate/status": { + "get": { + "operationId": "instance_migrate_status", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceMigrateStatusRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceMigrateStatusResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/instance/serial": { + "get": { + "operationId": "instance_serial", + "responses": { + "default": { + "description": "", + "content": { + "*/*": { + "schema": {} + } + } + } + }, + "x-dropshot-websocket": {} + } + }, + "/instance/state": { + "put": { + "operationId": "instance_state_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceStateRequested" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/instance/state-monitor": { + "get": { + "operationId": "instance_state_monitor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceStateMonitorRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceStateMonitorResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + } + }, + "components": { + "responses": { + "Error": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "schemas": { + "CrucibleOpts": { + "type": "object", + "properties": { + "cert_pem": { + "nullable": true, + "type": "string" + }, + "control": { + "nullable": true, + "type": "string" + }, + "flush_timeout": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "id": { + "type": "string", + "format": "uuid" + }, + "key": { + "nullable": true, + "type": "string" + }, + "key_pem": { + "nullable": true, + "type": "string" + }, + "lossy": { + "type": "boolean" + }, + "read_only": { + "type": "boolean" + }, + "root_cert_pem": { + "nullable": true, + "type": "string" + }, + "target": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "lossy", + "read_only", + "target" + ] + }, + "DiskAttachment": { + "type": "object", + "properties": { + "disk_id": { + "type": "string", + "format": "uuid" + }, + "generation_id": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "state": { + "$ref": "#/components/schemas/DiskAttachmentState" + } + }, + "required": [ + "disk_id", + "generation_id", + "state" + ] + }, + "DiskAttachmentState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "Detached", + "Destroyed", + "Faulted" + ] + }, + { + "type": "object", + "properties": { + "Attached": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "Attached" + ], + "additionalProperties": false + } + ] + }, + "DiskRequest": { + "type": "object", + "properties": { + "device": { + "type": "string" + }, + "gen": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "name": { + "type": "string" + }, + "read_only": { + "type": "boolean" + }, + "slot": { + "$ref": "#/components/schemas/Slot" + }, + "volume_construction_request": { + "$ref": "#/components/schemas/VolumeConstructionRequest" + } + }, + "required": [ + "device", + "gen", + "name", + "read_only", + "slot", + "volume_construction_request" + ] + }, + "Error": { + "description": "Error information from a response.", + "type": "object", + "properties": { + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "message", + "request_id" + ] + }, + "Instance": { + "type": "object", + "properties": { + "disks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DiskAttachment" + } + }, + "nics": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkInterface" + } + }, + "properties": { + "$ref": "#/components/schemas/InstanceProperties" + }, + "state": { + "$ref": "#/components/schemas/InstanceState" + } + }, + "required": [ + "disks", + "nics", + "properties", + "state" + ] + }, + "InstanceEnsureRequest": { + "type": "object", + "properties": { + "cloud_init_bytes": { + "nullable": true, + "type": "string" + }, + "disks": { + "default": [], + "type": "array", + "items": { + "$ref": "#/components/schemas/DiskRequest" + } + }, + "migrate": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InstanceMigrateInitiateRequest" + } + ] + }, + "nics": { + "default": [], + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkInterfaceRequest" + } + }, + "properties": { + "$ref": "#/components/schemas/InstanceProperties" + } + }, + "required": [ + "properties" + ] + }, + "InstanceEnsureResponse": { + "type": "object", + "properties": { + "migrate": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InstanceMigrateInitiateResponse" + } + ] + } + } + }, + "InstanceGetResponse": { + "type": "object", + "properties": { + "instance": { + "$ref": "#/components/schemas/Instance" + } + }, + "required": [ + "instance" + ] + }, + "InstanceMigrateInitiateRequest": { + "type": "object", + "properties": { + "migration_id": { + "type": "string", + "format": "uuid" + }, + "src_addr": { + "type": "string" + }, + "src_uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "migration_id", + "src_addr", + "src_uuid" + ] + }, + "InstanceMigrateInitiateResponse": { + "type": "object", + "properties": { + "migration_id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "migration_id" + ] + }, + "InstanceMigrateStatusRequest": { + "type": "object", + "properties": { + "migration_id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "migration_id" + ] + }, + "InstanceMigrateStatusResponse": { + "type": "object", + "properties": { + "state": { + "$ref": "#/components/schemas/MigrationState" + } + }, + "required": [ + "state" + ] + }, + "InstanceProperties": { + "type": "object", + "properties": { + "bootrom_id": { + "description": "ID of the bootrom used to initialize this Instance.", + "type": "string", + "format": "uuid" + }, + "description": { + "description": "Free-form text description of an Instance.", + "type": "string" + }, + "id": { + "description": "Unique identifier for this Instance.", + "type": "string", + "format": "uuid" + }, + "image_id": { + "description": "ID of the image used to initialize this Instance.", + "type": "string", + "format": "uuid" + }, + "memory": { + "description": "Size of memory allocated to the Instance, in MiB.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "name": { + "description": "Human-readable name of the Instance.", + "type": "string" + }, + "vcpus": { + "description": "Number of vCPUs to be allocated to the Instance.", + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "bootrom_id", + "description", + "id", + "image_id", + "memory", + "name", + "vcpus" + ] + }, + "InstanceState": { + "description": "Current state of an Instance.", + "type": "string", + "enum": [ + "Creating", + "Starting", + "Running", + "Stopping", + "Stopped", + "Rebooting", + "Migrating", + "Repairing", + "Failed", + "Destroyed" + ] + }, + "InstanceStateMonitorRequest": { + "type": "object", + "properties": { + "gen": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "required": [ + "gen" + ] + }, + "InstanceStateMonitorResponse": { + "type": "object", + "properties": { + "gen": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "state": { + "$ref": "#/components/schemas/InstanceState" + } + }, + "required": [ + "gen", + "state" + ] + }, + "InstanceStateRequested": { + "type": "string", + "enum": [ + "Run", + "Stop", + "Reboot", + "MigrateStart" + ] + }, + "MigrationState": { + "type": "string", + "enum": [ + "Sync", + "RamPush", + "Pause", + "RamPushDirty", + "Device", + "Arch", + "Resume", + "RamPull", + "Finish", + "Error" + ] + }, + "NetworkInterface": { + "type": "object", + "properties": { + "attachment": { + "$ref": "#/components/schemas/NetworkInterfaceAttachmentState" + }, + "name": { + "type": "string" + } + }, + "required": [ + "attachment", + "name" + ] + }, + "NetworkInterfaceAttachmentState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "Detached", + "Faulted" + ] + }, + { + "type": "object", + "properties": { + "Attached": { + "$ref": "#/components/schemas/Slot" + } + }, + "required": [ + "Attached" + ], + "additionalProperties": false + } + ] + }, + "NetworkInterfaceRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "slot": { + "$ref": "#/components/schemas/Slot" + } + }, + "required": [ + "name", + "slot" + ] + }, + "Slot": { + "description": "A stable index which is translated by Propolis into a PCI BDF, visible to the guest.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "VolumeConstructionRequest": { + "oneOf": [ + { + "type": "object", + "properties": { + "block_size": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "id": { + "type": "string", + "format": "uuid" + }, + "read_only_parent": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/VolumeConstructionRequest" + } + ] + }, + "sub_volumes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VolumeConstructionRequest" + } + }, + "type": { + "type": "string", + "enum": [ + "volume" + ] + } + }, + "required": [ + "block_size", + "id", + "sub_volumes", + "type" + ] + }, + { + "type": "object", + "properties": { + "block_size": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "url" + ] + }, + "url": { + "type": "string" + } + }, + "required": [ + "block_size", + "id", + "type", + "url" + ] + }, + { + "type": "object", + "properties": { + "block_size": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "gen": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "opts": { + "$ref": "#/components/schemas/CrucibleOpts" + }, + "type": { + "type": "string", + "enum": [ + "region" + ] + } + }, + "required": [ + "block_size", + "gen", + "opts", + "type" + ] + }, + { + "type": "object", + "properties": { + "block_size": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "id": { + "type": "string", + "format": "uuid" + }, + "path": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "file" + ] + } + }, + "required": [ + "block_size", + "id", + "path", + "type" + ] + } + ] + } + } + } +} \ No newline at end of file