Skip to content

Commit

Permalink
improve ergonomics of OpenAPI definition generation (oxidecomputer#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
jclulow committed Jan 1, 2021
1 parent 2882580 commit fccc955
Showing 1 changed file with 142 additions and 18 deletions.
160 changes: 142 additions & 18 deletions dropshot/src/api_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,22 @@ impl ApiDescription {
Ok(())
}

/**
* Emit the OpenAPI definition describing this API. Returns an
* OpenApiConfig which can be used to specify the contents of the definition
* and select an output format.
*/
pub fn openapi<S1, S2>(&self, title: S1, version: S2) -> OpenApiConfig
where
S1: AsRef<str>,
S2: AsRef<str>,
{
OpenApiConfig::new(self, title.as_ref(), version.as_ref())
}

/**
* Emit the OpenAPI Spec document describing this API in its JSON form.
*/
// TODO: There's a bunch of error handling we need here such as checking
// for duplicate parameter names.
pub fn print_openapi(
&self,
out: &mut dyn std::io::Write,
Expand All @@ -270,24 +281,42 @@ impl ApiDescription {
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.
*/
// TODO: There's a bunch of error handling we need here such as checking
// for duplicate parameter names.
fn gen_openapi(&self, info: openapiv3::Info) -> openapiv3::OpenAPI {
let mut openapi = openapiv3::OpenAPI::default();

openapi.openapi = "3.0.3".to_string();
openapi.info = openapiv3::Info {
title: title.to_string(),
description: description.map(ToString::to_string),
terms_of_service: terms_of_service.map(ToString::to_string),
contact: Some(openapiv3::Contact {
name: contact_name.map(ToString::to_string),
url: contact_url.map(ToString::to_string),
email: contact_email.map(ToString::to_string),
}),
license: license_name.map(|name| openapiv3::License {
name: name.to_string(),
url: license_url.map(ToString::to_string),
}),
version: version.to_string(),
};
openapi.info = info;

let settings = schemars::gen::SchemaSettings::openapi3();
let mut generator = schemars::gen::SchemaGenerator::new(settings);
Expand Down Expand Up @@ -480,7 +509,9 @@ impl ApiDescription {
schemas.insert(key.clone(), j2oas_schema(None, schema));
});

serde_json::to_writer_pretty(out, &openapi)
openapi
//serde_json::to_value(&openapi)
//serde_json::to_writer_pretty(out, &openapi)
}

/*
Expand Down Expand Up @@ -873,6 +904,99 @@ fn j2oas_object(
}
}

pub struct OpenApiConfig<'a> {
api: &'a ApiDescription,
info: openapiv3::Info,
}

impl<'a> OpenApiConfig<'a> {
fn new(
api: &'a ApiDescription,
title: &str,
version: &str,
) -> OpenApiConfig<'a> {
let info = openapiv3::Info {
title: title.to_string(),
version: version.to_string(),
..Default::default()
};
OpenApiConfig {
api,
info,
}
}

pub fn description<S: AsRef<str>>(&mut self, description: S) -> &mut Self {
self.info.description = Some(description.as_ref().to_string());
self
}

pub fn terms_of_service<S: AsRef<str>>(&mut self, url: S) -> &mut Self {
self.info.terms_of_service = Some(url.as_ref().to_string());
self
}

fn contact_mut(&mut self) -> &mut openapiv3::Contact {
if self.info.contact.is_none() {
self.info.contact = Some(openapiv3::Contact::default());
}
self.info.contact.as_mut().unwrap()
}

pub fn contact_name<S: AsRef<str>>(&mut self, name: S) -> &mut Self {
self.contact_mut().name = Some(name.as_ref().to_string());
self
}

pub fn contact_url<S: AsRef<str>>(&mut self, url: S) -> &mut Self {
self.contact_mut().url = Some(url.as_ref().to_string());
self
}

pub fn contact_email<S: AsRef<str>>(&mut self, email: S) -> &mut Self {
self.contact_mut().email = Some(email.as_ref().to_string());
self
}

fn license_mut(&mut self, name: &str) -> &mut openapiv3::License {
if self.info.license.is_none() {
self.info.license = Some(openapiv3::License {
name: name.to_string(),
url: None,
})
}
self.info.license.as_mut().unwrap()
}

pub fn license<S1, S2>(&mut self, name: S1, url: S2) -> &mut Self
where
S1: AsRef<str>,
S2: AsRef<str>,
{
self.license_mut(name.as_ref()).url = Some(url.as_ref().to_string());
self
}

pub fn license_name<S: AsRef<str>>(&mut self, name: S) -> &mut Self {
self.license_mut(name.as_ref());
self
}

pub fn json(&self) -> serde_json::Result<serde_json::Value> {
serde_json::to_value(&self.api.gen_openapi(self.info.clone()))
}

pub fn write(
&self,
out: &mut dyn std::io::Write,
) -> serde_json::Result<()> {
serde_json::to_writer_pretty(
out,
&self.api.gen_openapi(self.info.clone()),
)
}
}

#[cfg(test)]
mod test {
use super::super::error::HttpError;
Expand Down

0 comments on commit fccc955

Please sign in to comment.