This is a simple crate to validate OpenAPI v3.0.3 content. It flags constructs that we've determined are not "ergonomic" or "well-designed". In particular we try to avoid constructs that lead to structures that SDK generators would have a hard time turning into easy-to-use native constructs.
A schema that describes a type may include subschemas where one, all, or any of
the subschemas might match ( for the oneOf
, allOf
, and anyOf
fields
respectively). For example, the following Rust code produces such a schema with
mixed types:
#[derive(JsonSchema)]
pub enum E {
ThingA(String),
ThingB,
}
A JSON object that used this enum
for the type of a field could look like this:
{
"field": { "ThingA": "some value" }
}
or this:
{
"field": "ThingB"
}
So field
may be either a string or an object. This complicates the
description of these types and is harder to represent in SDKs (in particular
those without Rust's ability for enums to have associated values). To avoid
this, we can simply use serde
's facility for annotating enums. In particular,
we prefer "adjacently
tagged" enums:
#[derive(JsonSchema)]
#[serde(tag = "type", content = "value")]
pub enum E {
ThingA(String),
ThingB,
}
This produces JSON like this:
{
"field1": { "type": "ThingA", "value": "some value" },
"field2": { "type": "ThingB" }
}
Paths (routes) with compound-words as components should use kebab case.
This | /service-processors/{sp_id}/serial-console |
Not this | /service_processors/{sp_id}/serial_console |
In general, we use the typical Rust naming conventions.
- All type names should be
PascalCase
. - All
operation_id
s should besnake_case
. - All operation properties should be
snake_case
. - All struct (and struct enum variant) members should be
snake_case
. - All enum variants should be
snake_case
. (Note that depending on the serde tagging scheme used, variant names may appear in OpenAPI as either struct property names (external tagging) or as constant values (internal or adjacent tagging). The choice ofsnake_case
makes naming uniform regardless of the tagging scheme.)
Type names are already PascalCase
by normal Rust conventions. If you need
(really?) to have a type with a non-PascalCase name, you can renamed it like
this:
#[derive(JsonSchema)]
#[allow(non_camel_case_types)]
#[serde(rename = "IllumosButUpperCase")]
struct illumosIsAlwaysLowerCaseIGuess {
// ...
}
Operation IDs come from the function name. If you obey the normal Rust convention, your functions are already snake_case. There isn't currently a facility to change the operation name; file an issue in (dropshot)[https://github.com/oxidecomputer/dropshot] if this is required.
Rust enum
s typically name variants with PascalCase
. Typically you'll rename
them all to snake_case
:
#[derive(JsonSchema)]
#[serde(rename_all = "snake_case")]
enum Things {
ThingA,
ThingB,
}
Sometimes you might prefer SCREAMING_SNAKE_CASE
e.g. for things that are more
typically abbreviated:
#[derive(JsonSchema)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
enum Things {
ThingA,
ThingB,
}
It's tempting to name fields that are UUIDs with an _uuid
suffix, but this
is redundant. For simplicity and consistency we use the _id
suffix instead.
(Drophot)[https://github.com/oxidecomputer/dropshot] makes it easy (too easy!)
to accidentally return a null
response when you intend to return an empty
response.
Consider this handler:
#[endpoint {
method = POST,
path = "/device/confirm",
}]
pub async fn device_auth_confirm(
rqctx: Arc<RequestContext<Arc<ServerContext>>>,
) -> Result<HttpResponseOk<()>, HttpError> {
// ...
}
The corresponding OpenAPI responses
will be:
{
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"title": "Null",
"type": "string",
"enum": [
null
]
}
}
}
}
}
Instead, use HttpResponseUpdatedNoContent
:
#[endpoint {
method = POST,
path = "/device/confirm",
}]
pub async fn device_auth_confirm(
rqctx: Arc<RequestContext<Arc<ServerContext>>>,
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
// ...
}
These rules only apply to APIs that are "external".
Both dropshot
and schemars
use rustdoc comments as the basis for
documentation fields (specifically title
and description
). As such, it's
easy to accidentally allow internally-relevant documentation leak out as
externally-visible in the OpenAPI document. It's not possible to simply infer
this from text alone, but we do look for shibboleths such as a Rust path
delimeter (::
) and bracketed expressions with no subsequent parentheses
([title](http://link.dest)
being reasonable).