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, BTreeSet};
10
11#[derive(Clone, Debug)]
16pub struct ManagedApiConfig {
17 pub ident: &'static str,
21
22 pub versions: Versions,
24
25 pub title: &'static str,
27
28 pub metadata: ManagedApiMetadata,
30
31 pub api_description:
37 fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,
38
39 pub extra_validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
41}
42
43#[derive(Debug)]
45pub(crate) struct ManagedApi {
46 ident: ApiIdent,
50
51 versions: Versions,
53
54 title: &'static str,
56
57 metadata: ManagedApiMetadata,
59
60 api_description:
66 fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,
67
68 extra_validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
70}
71
72impl From<ManagedApiConfig> for ManagedApi {
73 fn from(value: ManagedApiConfig) -> Self {
74 ManagedApi {
75 ident: ApiIdent::from(value.ident.to_owned()),
76 versions: value.versions,
77 title: value.title,
78 metadata: value.metadata,
79 api_description: value.api_description,
80 extra_validation: value.extra_validation,
81 }
82 }
83}
84
85impl ManagedApi {
86 pub fn ident(&self) -> &ApiIdent {
87 &self.ident
88 }
89
90 pub fn versions(&self) -> &Versions {
91 &self.versions
92 }
93
94 pub fn title(&self) -> &'static str {
95 self.title
96 }
97
98 pub fn metadata(&self) -> &ManagedApiMetadata {
99 &self.metadata
100 }
101
102 pub fn is_lockstep(&self) -> bool {
103 self.versions.is_lockstep()
104 }
105
106 pub fn is_versioned(&self) -> bool {
107 self.versions.is_versioned()
108 }
109
110 pub fn iter_versioned_versions(
111 &self,
112 ) -> Option<impl Iterator<Item = &SupportedVersion> + '_> {
113 self.versions.iter_versioned_versions()
114 }
115
116 pub fn iter_versions_semver(
117 &self,
118 ) -> impl Iterator<Item = &semver::Version> + '_ {
119 self.versions.iter_versions_semvers()
120 }
121
122 pub fn generate_openapi_doc(
123 &self,
124 version: &semver::Version,
125 ) -> anyhow::Result<OpenAPI> {
126 let contents = self.generate_spec_bytes(version)?;
131 serde_json::from_slice(&contents)
132 .context("generated document is not valid OpenAPI")
133 }
134
135 pub fn generate_spec_bytes(
136 &self,
137 version: &semver::Version,
138 ) -> anyhow::Result<Vec<u8>> {
139 let description = (self.api_description)().map_err(|error| {
140 anyhow::anyhow!("{}", error)
144 })?;
145 let mut openapi_def = description.openapi(self.title, version.clone());
146 if let Some(description) = self.metadata.description {
147 openapi_def.description(description);
148 }
149 if let Some(contact_url) = self.metadata.contact_url {
150 openapi_def.contact_url(contact_url);
151 }
152 if let Some(contact_email) = self.metadata.contact_email {
153 openapi_def.contact_email(contact_email);
154 }
155
156 let mut contents = Vec::new();
160 openapi_def.write(&mut contents)?;
161 Ok(contents)
162 }
163
164 pub fn extra_validation(
165 &self,
166 openapi: &OpenAPI,
167 validation_context: ValidationContext<'_>,
168 ) {
169 if let Some(extra_validation) = self.extra_validation {
170 extra_validation(openapi, validation_context);
171 }
172 }
173}
174
175#[derive(Debug)]
180pub struct ManagedApis {
181 apis: BTreeMap<ApiIdent, ManagedApi>,
182 unknown_apis: BTreeSet<ApiIdent>,
183 validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
184}
185
186impl ManagedApis {
187 pub fn new(api_list: Vec<ManagedApiConfig>) -> anyhow::Result<ManagedApis> {
192 let mut apis = BTreeMap::new();
193 for api in api_list {
194 let api = ManagedApi::from(api);
195 if api.extra_validation.is_some() && api.is_versioned() {
196 bail!("extra validation is not supported for versioned APIs");
207 }
208
209 if let Some(old) = apis.insert(api.ident.clone(), api) {
210 bail!("API is defined twice: {:?}", &old.ident);
211 }
212 }
213
214 Ok(ManagedApis {
215 apis,
216 unknown_apis: BTreeSet::new(),
217 validation: None,
218 })
219 }
220
221 pub fn with_unknown_apis<I, S>(mut self, apis: I) -> Self
228 where
229 I: IntoIterator<Item = S>,
230 S: Into<ApiIdent>,
231 {
232 self.unknown_apis.extend(apis.into_iter().map(|s| s.into()));
233 self
234 }
235
236 pub fn with_validation(
242 mut self,
243 validation: fn(&OpenAPI, ValidationContext<'_>),
244 ) -> Self {
245 self.validation = Some(validation);
246 self
247 }
248
249 pub fn validation(&self) -> Option<fn(&OpenAPI, ValidationContext<'_>)> {
251 self.validation
252 }
253
254 pub fn len(&self) -> usize {
256 self.apis.len()
257 }
258
259 pub fn is_empty(&self) -> bool {
261 self.apis.is_empty()
262 }
263
264 pub(crate) fn iter_apis(
265 &self,
266 ) -> impl Iterator<Item = &'_ ManagedApi> + '_ {
267 self.apis.values()
268 }
269
270 pub(crate) fn api(&self, ident: &ApiIdent) -> Option<&ManagedApi> {
271 self.apis.get(ident)
272 }
273
274 pub fn unknown_apis(&self) -> &BTreeSet<ApiIdent> {
276 &self.unknown_apis
277 }
278}