diff --git a/dropshot/src/api_description.rs b/dropshot/src/api_description.rs index 4af881899..785ec2cf3 100644 --- a/dropshot/src/api_description.rs +++ b/dropshot/src/api_description.rs @@ -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(&self, title: S1, version: S2) -> OpenApiConfig + where + S1: AsRef, + S2: AsRef, + { + 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, @@ -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); @@ -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) } /* @@ -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>(&mut self, description: S) -> &mut Self { + self.info.description = Some(description.as_ref().to_string()); + self + } + + pub fn terms_of_service>(&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>(&mut self, name: S) -> &mut Self { + self.contact_mut().name = Some(name.as_ref().to_string()); + self + } + + pub fn contact_url>(&mut self, url: S) -> &mut Self { + self.contact_mut().url = Some(url.as_ref().to_string()); + self + } + + pub fn contact_email>(&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(&mut self, name: S1, url: S2) -> &mut Self + where + S1: AsRef, + S2: AsRef, + { + self.license_mut(name.as_ref()).url = Some(url.as_ref().to_string()); + self + } + + pub fn license_name>(&mut self, name: S) -> &mut Self { + self.license_mut(name.as_ref()); + self + } + + pub fn json(&self) -> serde_json::Result { + 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;