Skip to content

Commit

Permalink
Add "example" to openapi schemas output (via schemars) (#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
bahamas10 authored Apr 22, 2022
1 parent c376837 commit 0e971f5
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ https://github.com/oxidecomputer/dropshot/compare/v0.6.0\...HEAD[Full list of co
* https://github.com/oxidecomputer/dropshot/pull/296[#296] `ApiDescription` includes a `tag_config` method to specify both predefined tags with descriptions and links as well as a tag policy to ensure that endpoints, for example, only use predefined tags or have at least one tag.
* https://github.com/oxidecomputer/dropshot/pull/317[#317] Allow use of usdt probes with stable Rust. Dropshot consumers can build with USDT probes enabled on stable compilers >= 1.59 (except on MacOS).
* https://github.com/oxidecomputer/dropshot/pull/310[#310] Freeform (and streaming) response bodies may be specified with specific HTTP response codes e.g. by having an endpoint return `Result<HttpResponseOk<FreeformBody>, HttpError>`.
- https://github.com/oxidecomputer/dropshot/pull/325[#325] The example field (if present) for `JsonSchema` objects in the API will be present in the OpenAPI output (and note that no validation of the example is performed)

== 0.6.0 (released 2021-11-18)

Expand Down
77 changes: 77 additions & 0 deletions dropshot/examples/schema-with-example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*!
* Example use of dropshot to output OpenAPI compatible JSON. This program
* specifically illustrates how to add examples to each schema using schemars,
* and how that will be reflected in the resultant JSON generated when ran.
*/

use dropshot::{
endpoint, ApiDescription, HttpError, HttpResponseOk, RequestContext,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

/*
* Define 2 structs here - Bar is nested inside Foo and should result in an
* example that looks like:
*
* {
* "id": 1,
* "bar": {
* "id: 2
* }
* }
*/

#[derive(Deserialize, Serialize, JsonSchema)]
#[schemars(example = "foo_example")]
/// # Foo Object
struct Foo {
/// Foo ID
id: u32,

/// All Foo's have a Bar
bar: Bar,
}

#[derive(Deserialize, Serialize, JsonSchema)]
#[schemars(example = "bar_example")]
/// # Bar Object
struct Bar {
/// Bar ID
id: u32,
}

/// Used by schemars to generate the `Foo` example.
fn foo_example() -> Foo {
Foo { id: 1, bar: bar_example() }
}

/// Used by schemars to generate the `Bar` example.
fn bar_example() -> Bar {
Bar { id: 2 }
}

fn main() -> Result<(), String> {
let mut api = ApiDescription::new();
api.register(get_foo).unwrap();

api.openapi("Examples", "0.0.0")
.write(&mut std::io::stdout())
.map_err(|e| e.to_string())?;

Ok(())
}

#[endpoint {
method = GET,
path = "/foo",
tags = [ "foo" ],
}]
/// Get a foo
async fn get_foo(
_rqctx: Arc<RequestContext<()>>,
) -> Result<HttpResponseOk<Foo>, HttpError> {
let foo = foo_example();
Ok(HttpResponseOk(foo))
}
3 changes: 3 additions & 0 deletions dropshot/src/api_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,9 @@ fn j2oas_schema_object(
if let Some(name) = name {
data.title = Some(name.clone());
}
if let Some(example) = obj.extensions.get("example") {
data.example = Some(example.clone());
}

openapiv3::ReferenceOr::Item(openapiv3::Schema {
schema_data: data,
Expand Down
68 changes: 68 additions & 0 deletions dropshot/tests/test_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,32 @@
}
}
},
"/with/example": {
"get": {
"tags": [
"it"
],
"operationId": "handler19",
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ObjectWithExample"
}
}
}
},
"4XX": {
"$ref": "#/components/responses/Error"
},
"5XX": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/with/headers": {
"get": {
"tags": [
Expand Down Expand Up @@ -633,6 +659,20 @@
"request_id"
]
},
"NestedObjectWithExample": {
"example": {
"nick_name": "baz"
},
"type": "object",
"properties": {
"nick_name": {
"type": "string"
}
},
"required": [
"nick_name"
]
},
"NeverDuplicatedBodyNextLevel": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -707,6 +747,34 @@
"b"
]
},
"ObjectWithExample": {
"example": {
"id": 456,
"name": "foo bar",
"nested": {
"nick_name": "baz"
}
},
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "uint32",
"minimum": 0
},
"name": {
"type": "string"
},
"nested": {
"$ref": "#/components/schemas/NestedObjectWithExample"
}
},
"required": [
"id",
"name",
"nested"
]
},
"Response": {
"type": "object"
},
Expand Down
38 changes: 38 additions & 0 deletions dropshot/tests/test_openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,43 @@ async fn handler18(
Ok(HttpResponseOk(body.into()))
}

#[derive(Serialize, JsonSchema)]
#[schemars(example = "example_object_with_example")]
struct ObjectWithExample {
id: u32,
name: String,
nested: NestedObjectWithExample,
}

#[derive(Serialize, JsonSchema)]
#[schemars(example = "example_nested_object_with_example")]
struct NestedObjectWithExample {
nick_name: String,
}

fn example_object_with_example() -> ObjectWithExample {
ObjectWithExample {
id: 456,
name: "foo bar".into(),
nested: example_nested_object_with_example(),
}
}

fn example_nested_object_with_example() -> NestedObjectWithExample {
NestedObjectWithExample { nick_name: "baz".into() }
}

#[endpoint {
method = GET,
path = "/with/example",
tags = ["it"],
}]
async fn handler19(
_rqctx: Arc<RequestContext<()>>,
) -> Result<HttpResponseOk<ObjectWithExample>, HttpError> {
Ok(HttpResponseOk(example_object_with_example()))
}

fn make_api(
maybe_tag_config: Option<TagConfig>,
) -> Result<ApiDescription<()>, String> {
Expand Down Expand Up @@ -376,6 +413,7 @@ fn make_api(
api.register(handler16)?;
api.register(handler17)?;
api.register(handler18)?;
api.register(handler19)?;
Ok(api)
}

Expand Down
68 changes: 68 additions & 0 deletions dropshot/tests/test_openapi_fuller.json
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,32 @@
}
}
},
"/with/example": {
"get": {
"tags": [
"it"
],
"operationId": "handler19",
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ObjectWithExample"
}
}
}
},
"4XX": {
"$ref": "#/components/responses/Error"
},
"5XX": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/with/headers": {
"get": {
"tags": [
Expand Down Expand Up @@ -641,6 +667,20 @@
"request_id"
]
},
"NestedObjectWithExample": {
"example": {
"nick_name": "baz"
},
"type": "object",
"properties": {
"nick_name": {
"type": "string"
}
},
"required": [
"nick_name"
]
},
"NeverDuplicatedBodyNextLevel": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -715,6 +755,34 @@
"b"
]
},
"ObjectWithExample": {
"example": {
"id": 456,
"name": "foo bar",
"nested": {
"nick_name": "baz"
}
},
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "uint32",
"minimum": 0
},
"name": {
"type": "string"
},
"nested": {
"$ref": "#/components/schemas/NestedObjectWithExample"
}
},
"required": [
"id",
"name",
"nested"
]
},
"Response": {
"type": "object"
},
Expand Down

0 comments on commit 0e971f5

Please sign in to comment.