From f85f65ccab6e4147b8f1a6512dcc1218e356817f Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Fri, 15 Jul 2022 18:56:38 -0700 Subject: [PATCH] clean up InstanceCreate::user_data JSON schema --- nexus/src/external_api/params.rs | 45 +++++++++++++++++++++++++------- openapi/nexus.json | 3 ++- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/nexus/src/external_api/params.rs b/nexus/src/external_api/params.rs index f18c4edf1de..3770d541cba 100644 --- a/nexus/src/external_api/params.rs +++ b/nexus/src/external_api/params.rs @@ -12,7 +12,7 @@ use omicron_common::api::external::{ use schemars::JsonSchema; use serde::{ de::{self, Visitor}, - Deserialize, Deserializer, Serialize, + Deserialize, Deserializer, Serialize, Serializer, }; use std::net::IpAddr; use uuid::Uuid; @@ -404,10 +404,15 @@ pub struct InstanceCreate { /// User data for instance initialization systems (such as cloud-init). /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / /// characters with padding). Maximum 32 KiB unencoded data. - // TODO: this should emit `"format": "byte"`, but progenitor doesn't - // understand that yet. - #[schemars(default, with = "String")] - #[serde(default, with = "serde_user_data")] + // While serde happily accepts #[serde(with = "")] as a shorthand for + // specifing `serialize_with` and `deserialize_with`, schemars requires the + // argument to `with` to be a type rather than merely a path prefix (i.e. a + // mod or type). It's admittedly a bit tricky for schemars to address; + // unlike `serialize` or `deserialize`, `JsonSchema` requires several + // functions working together. It's unfortunate that schemars has this + // built-in incompatibility, exacerbated by its glacial rate of progress + // and immunity to offers of help. + #[serde(default, with = "UserData")] pub user_data: Vec, /// The network interfaces to be created for this instance. @@ -419,9 +424,8 @@ pub struct InstanceCreate { pub disks: Vec, } -mod serde_user_data { - use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; - +struct UserData; +impl UserData { pub fn serialize( data: &Vec, serializer: S, @@ -440,7 +444,7 @@ mod serde_user_data { Ok(buf) => { // if you change this, also update the stress test in crate::cidata if buf.len() > crate::cidata::MAX_USER_DATA_BYTES { - Err(D::Error::invalid_length( + Err(::invalid_length( buf.len(), &"less than 32 KiB", )) @@ -448,7 +452,7 @@ mod serde_user_data { Ok(buf) } } - Err(_) => Err(D::Error::invalid_value( + Err(_) => Err(::invalid_value( serde::de::Unexpected::Other("invalid base64 string"), &"a valid base64 string", )), @@ -456,6 +460,27 @@ mod serde_user_data { } } +impl JsonSchema for UserData { + fn schema_name() -> String { + "String".to_string() + } + + fn json_schema( + _: &mut schemars::gen::SchemaGenerator, + ) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::String.into()), + format: Some("byte".to_string()), + ..Default::default() + } + .into() + } + + fn is_referenceable() -> bool { + false + } +} + /// Migration parameters for an [`Instance`](omicron_common::api::external::Instance) #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct InstanceMigrate { diff --git a/openapi/nexus.json b/openapi/nexus.json index 40e7f0dbe25..c10ca3ab933 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -7830,7 +7830,8 @@ "user_data": { "description": "User data for instance initialization systems (such as cloud-init). Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / characters with padding). Maximum 32 KiB unencoded data.", "default": "", - "type": "string" + "type": "string", + "format": "byte" } }, "required": [