dropshot_api_manager/
apis.rs1use anyhow::{Context, bail};
4use dropshot::{ApiDescription, ApiDescriptionBuildErrors, StubContext};
5use dropshot_api_manager_types::{
6 ApiIdent, ManagedApiMetadata, SupportedVersion, ValidationContext, Versions,
7};
8use openapiv3::OpenAPI;
9use std::collections::BTreeMap;
10
11#[derive(Clone, Debug)]
13pub struct ManagedApiConfig {
14 pub ident: &'static str,
18
19 pub versions: Versions,
21
22 pub title: &'static str,
24
25 pub metadata: ManagedApiMetadata,
27
28 pub api_description:
34 fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,
35
36 pub extra_validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
38}
39
40#[derive(Debug)]
42pub struct ManagedApi {
43 ident: ApiIdent,
47
48 versions: Versions,
50
51 title: &'static str,
53
54 metadata: ManagedApiMetadata,
56
57 api_description:
63 fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,
64
65 extra_validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
67}
68
69impl From<ManagedApiConfig> for ManagedApi {
70 fn from(value: ManagedApiConfig) -> Self {
71 ManagedApi {
72 ident: ApiIdent::from(value.ident.to_owned()),
73 versions: value.versions,
74 title: value.title,
75 metadata: value.metadata,
76 api_description: value.api_description,
77 extra_validation: value.extra_validation,
78 }
79 }
80}
81
82impl ManagedApi {
83 pub fn ident(&self) -> &ApiIdent {
84 &self.ident
85 }
86
87 pub fn versions(&self) -> &Versions {
88 &self.versions
89 }
90
91 pub fn title(&self) -> &'static str {
92 self.title
93 }
94
95 pub fn metadata(&self) -> &ManagedApiMetadata {
96 &self.metadata
97 }
98
99 pub fn is_lockstep(&self) -> bool {
100 self.versions.is_lockstep()
101 }
102
103 pub fn is_versioned(&self) -> bool {
104 self.versions.is_versioned()
105 }
106
107 pub fn iter_versioned_versions(
108 &self,
109 ) -> Option<impl Iterator<Item = &SupportedVersion> + '_> {
110 self.versions.iter_versioned_versions()
111 }
112
113 pub fn iter_versions_semver(
114 &self,
115 ) -> impl Iterator<Item = &semver::Version> + '_ {
116 self.versions.iter_versions_semvers()
117 }
118
119 pub fn generate_openapi_doc(
120 &self,
121 version: &semver::Version,
122 ) -> anyhow::Result<OpenAPI> {
123 let contents = self.generate_spec_bytes(version)?;
128 serde_json::from_slice(&contents)
129 .context("generated document is not valid OpenAPI")
130 }
131
132 pub fn generate_spec_bytes(
133 &self,
134 version: &semver::Version,
135 ) -> anyhow::Result<Vec<u8>> {
136 let description = (self.api_description)().map_err(|error| {
137 anyhow::anyhow!("{}", error)
141 })?;
142 let mut openapi_def = description.openapi(self.title, version.clone());
143 if let Some(description) = self.metadata.description {
144 openapi_def.description(description);
145 }
146 if let Some(contact_url) = self.metadata.contact_url {
147 openapi_def.contact_url(contact_url);
148 }
149 if let Some(contact_email) = self.metadata.contact_email {
150 openapi_def.contact_email(contact_email);
151 }
152
153 let mut contents = Vec::new();
157 openapi_def.write(&mut contents)?;
158 Ok(contents)
159 }
160
161 pub fn extra_validation(
162 &self,
163 openapi: &OpenAPI,
164 validation_context: ValidationContext<'_>,
165 ) {
166 if let Some(extra_validation) = self.extra_validation {
167 extra_validation(openapi, validation_context);
168 }
169 }
170}
171
172#[derive(Debug)]
177pub struct ManagedApis {
178 apis: BTreeMap<ApiIdent, ManagedApi>,
179 validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
180}
181
182impl ManagedApis {
183 pub fn new(api_list: Vec<ManagedApiConfig>) -> anyhow::Result<ManagedApis> {
184 let mut apis = BTreeMap::new();
185 for api in api_list {
186 let api = ManagedApi::from(api);
187 if api.extra_validation.is_some() && api.is_versioned() {
188 bail!("extra validation is not supported for versioned APIs");
199 }
200
201 if let Some(old) = apis.insert(api.ident.clone(), api) {
202 bail!("API is defined twice: {:?}", &old.ident);
203 }
204 }
205
206 Ok(ManagedApis { apis, validation: None })
207 }
208
209 pub fn with_validation(
215 mut self,
216 validation: fn(&OpenAPI, ValidationContext<'_>),
217 ) -> Self {
218 self.validation = Some(validation);
219 self
220 }
221
222 pub fn validation(&self) -> Option<fn(&OpenAPI, ValidationContext<'_>)> {
224 self.validation
225 }
226
227 pub fn len(&self) -> usize {
228 self.apis.len()
229 }
230
231 pub fn is_empty(&self) -> bool {
232 self.apis.is_empty()
233 }
234
235 pub fn iter_apis(&self) -> impl Iterator<Item = &'_ ManagedApi> + '_ {
236 self.apis.values()
237 }
238
239 pub fn api(&self, ident: &ApiIdent) -> Option<&ManagedApi> {
240 self.apis.get(ident)
241 }
242}