From d28f2d6116770992b6d120058e548d1445e1be76 Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Mon, 18 Oct 2021 22:56:48 -0700 Subject: [PATCH 1/4] migrate to openapiv3 1.0.0-beta.2 --- Cargo.lock | 4 +- dropshot/Cargo.toml | 2 +- dropshot/src/api_description.rs | 76 +---- dropshot/tests/test_openapi.rs | 28 +- dropshot/tests/test_openapi_old.json | 457 --------------------------- 5 files changed, 22 insertions(+), 545 deletions(-) delete mode 100644 dropshot/tests/test_openapi_old.json diff --git a/Cargo.lock b/Cargo.lock index c353580c9..9550fe4ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -587,9 +587,9 @@ checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "openapiv3" -version = "0.5.0" +version = "1.0.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90228d34f20d9fff3174d19b0d41e67f52711045270b540a2a2c2dc41ccb4085" +checksum = "791f5f66415ca5d11fb29e8dcf0b923adff5b2c9bd1cf54621777494c8f8f688" dependencies = [ "indexmap", "serde", diff --git a/dropshot/Cargo.toml b/dropshot/Cargo.toml index 5d468e353..00edd33c4 100644 --- a/dropshot/Cargo.toml +++ b/dropshot/Cargo.toml @@ -18,7 +18,7 @@ futures = "0.3.1" hostname = "0.3.0" http = "0.2.0" indexmap = "1.0.0" -openapiv3 = "0.5.0" +openapiv3 = "1.0.0-beta.2" paste = "1.0.0" percent-encoding = "2.1.0" proc-macro2 = "1.0.27" diff --git a/dropshot/src/api_description.rs b/dropshot/src/api_description.rs index e143418fd..ea2b4f2d2 100644 --- a/dropshot/src/api_description.rs +++ b/dropshot/src/api_description.rs @@ -428,52 +428,6 @@ impl ApiDescription { OpenApiDefinition::new(self, title.as_ref(), version.as_ref()) } - /** - * Emit the OpenAPI Spec document describing this API in its JSON form. - * - * This routine is deprecated in favour of the new openapi() builder - * routine. - */ - #[allow(clippy::too_many_arguments)] - #[deprecated(note = "switch to openapi()")] - pub fn print_openapi( - &self, - out: &mut dyn std::io::Write, - title: &dyn ToString, - description: Option<&dyn ToString>, - terms_of_service: Option<&dyn ToString>, - contact_name: Option<&dyn ToString>, - contact_url: Option<&dyn ToString>, - contact_email: Option<&dyn ToString>, - license_name: Option<&dyn ToString>, - license_url: Option<&dyn ToString>, - version: &dyn ToString, - ) -> serde_json::Result<()> { - let mut oapi = self.openapi(title.to_string(), version.to_string()); - if let Some(s) = description { - oapi.description(s.to_string()); - } - if let Some(s) = terms_of_service { - oapi.terms_of_service(s.to_string()); - } - if let Some(s) = contact_name { - oapi.contact_name(s.to_string()); - } - if let Some(s) = contact_url { - oapi.contact_url(s.to_string()); - } - if let Some(s) = contact_email { - oapi.contact_email(s.to_string()); - } - if let (Some(name), Some(url)) = (license_name, license_url) { - oapi.license(name.to_string(), url.to_string()); - } else if let Some(name) = license_name { - oapi.license_name(name.to_string()); - } - - oapi.write(out) - } - /** * Internal routine for constructing the OpenAPI definition describing this * API in its JSON form. @@ -493,7 +447,7 @@ impl ApiDescription { if !endpoint.visible { continue; } - let path = openapi.paths.entry(path).or_insert( + let path = openapi.paths.paths.entry(path).or_insert( openapiv3::ReferenceOr::Item(openapiv3::PathItem::default()), ); @@ -612,9 +566,7 @@ impl ApiDescription { mime_type.to_string(), openapiv3::MediaType { schema: Some(schema), - example: None, - examples: indexmap::IndexMap::new(), - encoding: indexmap::IndexMap::new(), + ..Default::default() }, ); @@ -653,9 +605,7 @@ impl ApiDescription { CONTENT_TYPE_JSON.to_string(), openapiv3::MediaType { schema: Some(j2oas_schema(name.as_ref(), &js)), - example: None, - examples: indexmap::IndexMap::new(), - encoding: indexmap::IndexMap::new(), + ..Default::default() }, ); } @@ -788,7 +738,7 @@ fn j2oas_schema_object( let ty = match &obj.instance_type { Some(schemars::schema::SingleOrVec::Single(ty)) => Some(ty.as_ref()), Some(schemars::schema::SingleOrVec::Vec(_)) => { - unimplemented!("unsupported by openapiv3") + panic!("unsupported by openapiv3") } None => None, }; @@ -870,8 +820,7 @@ fn j2oas_subschemas( .map(|schema| j2oas_schema(None, schema)) .collect::>(), }, - (None, None, None) => todo!("missed a valid case"), - _ => panic!("invalid"), + _ => panic!("invalid subschema {:#?}", subschemas), } } @@ -1045,7 +994,13 @@ fn j2oas_string( let enumeration = enum_values .iter() - .flat_map(|v| v.iter().map(|vv| vv.as_str().unwrap().to_string())) + .flat_map(|v| { + v.iter().map(|vv| match vv { + serde_json::Value::Null => None, + serde_json::Value::String(s) => Some(s.clone()), + _ => panic!("unexpected enumeration value {:?}", vv), + }) + }) .collect::>(); openapiv3::SchemaKind::Type(openapiv3::Type::String( @@ -1067,9 +1022,12 @@ fn j2oas_array( openapiv3::SchemaKind::Type(openapiv3::Type::Array(openapiv3::ArrayType { items: match &arr.items { Some(schemars::schema::SingleOrVec::Single(schema)) => { - box_reference_or(j2oas_schema(None, &schema)) + Some(box_reference_or(j2oas_schema(None, &schema))) + } + Some(schemars::schema::SingleOrVec::Vec(_)) => { + panic!("OpenAPI v3.0.x cannot support tuple-like arrays") } - _ => unimplemented!("don't think this is valid"), + None => None, }, min_items: arr.min_items.map(|n| n as usize), max_items: arr.max_items.map(|n| n as usize), diff --git a/dropshot/tests/test_openapi.rs b/dropshot/tests/test_openapi.rs index 0a1a98b49..bd995af67 100644 --- a/dropshot/tests/test_openapi.rs +++ b/dropshot/tests/test_openapi.rs @@ -285,37 +285,13 @@ fn make_api() -> Result, String> { Ok(api) } -#[test] -fn test_openapi_old() -> Result<(), String> { - let api = make_api()?; - let mut output = Cursor::new(Vec::new()); - - #[allow(deprecated)] - let _ = api.print_openapi( - &mut output, - &"test", - None, - None, - None, - None, - None, - None, - None, - &"threeve", - ); - let actual = from_utf8(&output.get_ref()).unwrap(); - - expectorate::assert_contents("tests/test_openapi_old.json", actual); - Ok(()) -} - #[test] fn test_openapi() -> Result<(), String> { let api = make_api()?; let mut output = Cursor::new(Vec::new()); let _ = api.openapi("test", "threeve").write(&mut output); - let actual = from_utf8(&output.get_ref()).unwrap(); + let actual = from_utf8(output.get_ref()).unwrap(); expectorate::assert_contents("tests/test_openapi.json", actual); Ok(()) @@ -333,7 +309,7 @@ fn test_openapi_fuller() -> Result<(), String> { .license_name("CDDL") .terms_of_service("no hat, no cane? no service!") .write(&mut output); - let actual = from_utf8(&output.get_ref()).unwrap(); + let actual = from_utf8(output.get_ref()).unwrap(); expectorate::assert_contents("tests/test_openapi_fuller.json", actual); Ok(()) diff --git a/dropshot/tests/test_openapi_old.json b/dropshot/tests/test_openapi_old.json deleted file mode 100644 index ac87cc9ff..000000000 --- a/dropshot/tests/test_openapi_old.json +++ /dev/null @@ -1,457 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "test", - "version": "threeve" - }, - "paths": { - "/datagoeshere": { - "put": { - "operationId": "handler7", - "requestBody": { - "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation" - } - } - } - }, - "/dup1": { - "get": { - "operationId": "handler8", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NeverDuplicatedResponseTopLevel" - } - } - } - } - } - } - }, - "/dup2": { - "get": { - "operationId": "handler9", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NeverDuplicatedResponseTopLevel" - } - } - } - } - } - } - }, - "/dup5": { - "put": { - "operationId": "handler10", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NeverDuplicatedBodyTopLevel" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation" - } - } - } - }, - "/dup6": { - "put": { - "operationId": "handler11", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NeverDuplicatedBodyTopLevel" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation" - } - } - } - }, - "/dup7": { - "put": { - "operationId": "handler12", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NeverDuplicatedTop" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation" - } - } - } - }, - "/dup8": { - "get": { - "operationId": "handler13", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NeverDuplicatedTop" - } - } - } - } - } - } - }, - "/impairment": { - "get": { - "operationId": "handler6", - "parameters": [ - { - "in": "query", - "name": "a_number", - "schema": { - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "style": "form" - }, - { - "in": "query", - "name": "limit", - "schema": { - "nullable": true, - "description": "Maximum number of items returned by a single call", - "type": "integer", - "format": "uint32", - "minimum": 1 - }, - "style": "form" - }, - { - "in": "query", - "name": "page_token", - "schema": { - "nullable": true, - "description": "Token returned by previous call to retreive the subsequent page", - "type": "string" - }, - "style": "form" - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseItemResultsPage" - } - } - } - } - }, - "x-dropshot-pagination": true - } - }, - "/test/camera": { - "post": { - "operationId": "handler4", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BodyParam" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "successful creation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Response" - } - } - } - } - } - } - }, - "/test/man/{x}": { - "delete": { - "operationId": "handler3", - "parameters": [ - { - "in": "path", - "name": "x", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "204": { - "description": "successful deletion" - } - } - } - }, - "/test/person": { - "get": { - "description": "This is a multi-line comment. It uses Rust-style.", - "operationId": "handler1", - "responses": { - "200": { - "description": "successful operation" - } - } - } - }, - "/test/tv/{x}": { - "post": { - "tags": [ - "person", - "woman", - "man", - "camera", - "tv" - ], - "operationId": "handler5", - "parameters": [ - { - "in": "path", - "name": "x", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - }, - { - "in": "query", - "name": "tomax", - "required": true, - "schema": { - "type": "string" - }, - "style": "form" - }, - { - "in": "query", - "name": "xamot", - "schema": { - "nullable": true, - "type": "string" - }, - "style": "form" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BodyParam" - } - } - }, - "required": true - }, - "responses": { - "202": { - "description": "successfully enqueued operation" - } - } - } - }, - "/test/woman": { - "put": { - "description": "This is a multi-line comment. It uses C-style.", - "operationId": "handler2", - "parameters": [ - { - "in": "query", - "name": "tomax", - "required": true, - "schema": { - "type": "string" - }, - "style": "form" - }, - { - "in": "query", - "name": "xamot", - "schema": { - "nullable": true, - "type": "string" - }, - "style": "form" - } - ], - "responses": { - "204": { - "description": "resource updated" - } - } - } - } - }, - "components": { - "schemas": { - "BodyParam": { - "type": "object", - "properties": { - "any": {}, - "x": { - "type": "string" - } - }, - "required": [ - "any", - "x" - ] - }, - "NeverDuplicatedBodyNextLevel": { - "type": "object", - "properties": { - "v": { - "type": "boolean" - } - }, - "required": [ - "v" - ] - }, - "NeverDuplicatedBodyTopLevel": { - "type": "object", - "properties": { - "_b": { - "$ref": "#/components/schemas/NeverDuplicatedBodyNextLevel" - } - }, - "required": [ - "_b" - ] - }, - "NeverDuplicatedNext": { - "type": "object", - "properties": { - "v": { - "type": "boolean" - } - }, - "required": [ - "v" - ] - }, - "NeverDuplicatedResponseNextLevel": { - "type": "object", - "properties": { - "v": { - "type": "boolean" - } - }, - "required": [ - "v" - ] - }, - "NeverDuplicatedResponseTopLevel": { - "type": "object", - "properties": { - "b": { - "$ref": "#/components/schemas/NeverDuplicatedResponseNextLevel" - } - }, - "required": [ - "b" - ] - }, - "NeverDuplicatedTop": { - "type": "object", - "properties": { - "b": { - "$ref": "#/components/schemas/NeverDuplicatedNext" - } - }, - "required": [ - "b" - ] - }, - "Response": { - "type": "object" - }, - "ResponseItem": { - "type": "object", - "properties": { - "word": { - "type": "string" - } - }, - "required": [ - "word" - ] - }, - "ResponseItemResultsPage": { - "description": "A single page of results", - "type": "object", - "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/ResponseItem" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" - } - }, - "required": [ - "items" - ] - } - } - } -} \ No newline at end of file From 7d58a8d68f31019192219f4425eba57fc03d5474 Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Sat, 6 Nov 2021 23:00:22 -0700 Subject: [PATCH 2/4] update changelog --- CHANGELOG.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 10fdaad9c..6bf6fc4ef 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -25,6 +25,7 @@ https://github.com/oxidecomputer/dropshot/compare/v0.5.1\...HEAD[Full list of co * https://github.com/oxidecomputer/dropshot/pull/100[#100] The type used for the "limit" argument for paginated resources has changed. This limit refers to the number of items that an HTTP client can ask for in a single request to a paginated endpoint. The limit is now 4294967295, where it may have previously been larger. This is not expected to affect consumers because this limit is far larger than practical. For details, see #100. * https://github.com/oxidecomputer/dropshot/pull/116[#116] Unused, non-`pub` endpoints from the `#[endpoint { ... }]` macro now produce a lint warning. This is *technically* a breaking change for those who may have had unused endpoints and compiled with `#[deny(warning)]` or `#[deny(dead_code)]` thus implicitly relying on the *absence* of a warning about the endpoint being unused. * https://github.com/oxidecomputer/dropshot/pull/118[#118] Path handling has changed. Escape sequences are decoded so that path parameters will no longer include those escape sequences. In addition, paths for endpoints added via `ApiDescription::register()` may not contain consecutive "/" characters. +* https://github.com/oxidecomputer/dropshot/pull/161[#161] The `ApiDescription::print_openapi()` interface (previously deprecated) has been removed. Now use `ApiDescription::openapi()` followed by a call to `OpenApiDefinition::write()` for equivalent functionality. == 0.5.1 (released 2021-03-18) From 6e7873b7b53d341e564934c43b4147c7ddd22774 Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Sat, 6 Nov 2021 23:08:52 -0700 Subject: [PATCH 3/4] bump openapiv3 version version --- dropshot/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dropshot/Cargo.toml b/dropshot/Cargo.toml index c2f49464c..a84cc8003 100644 --- a/dropshot/Cargo.toml +++ b/dropshot/Cargo.toml @@ -18,7 +18,7 @@ futures = "0.3.1" hostname = "0.3.0" http = "0.2.5" indexmap = "1.7.0" -openapiv3 = "1.0.0-beta.2" +openapiv3 = "1.0.0-beta.3" paste = "1.0.0" percent-encoding = "2.1.0" proc-macro2 = "1.0.32" From dc291c313ad3a0f522e839fd7a6e719ab7aedb94 Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Sat, 6 Nov 2021 23:23:22 -0700 Subject: [PATCH 4/4] update Cargo.lock --- Cargo.lock | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd8d013a0..7fe2a24d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,12 +218,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - [[package]] name = "dtrace-parser" version = "0.1.11" @@ -534,12 +528,6 @@ version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" -[[package]] -name = "linked-hash-map" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" - [[package]] name = "lock_api" version = "0.4.5" @@ -678,14 +666,13 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "openapiv3" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791f5f66415ca5d11fb29e8dcf0b923adff5b2c9bd1cf54621777494c8f8f688" +checksum = "f360315c88221c3142def78cd6fff55426b3119ae362bd52d1fe22928284e825" dependencies = [ "indexmap", "serde", "serde_json", - "serde_yaml", ] [[package]] @@ -938,18 +925,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" -dependencies = [ - "dtoa", - "indexmap", - "serde", - "yaml-rust", -] - [[package]] name = "sha-1" version = "0.8.2" @@ -1408,15 +1383,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "zerocopy" version = "0.3.0"