dropshot_api_manager/
apis.rs1use anyhow::{Context, bail};
4use dropshot::{ApiDescription, ApiDescriptionBuildErrors, StubContext};
5use dropshot_api_manager_types::{
6 ApiIdent, IterVersionsSemvers, ManagedApiMetadata, SupportedVersion,
7 ValidationContext, Versions,
8};
9use openapiv3::OpenAPI;
10use std::collections::{BTreeMap, BTreeSet};
11
12#[derive(Clone, Debug)]
17pub struct ManagedApiConfig {
18 pub ident: &'static str,
22
23 pub versions: Versions,
25
26 pub title: &'static str,
28
29 pub metadata: ManagedApiMetadata,
31
32 pub api_description:
38 fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,
39
40 pub extra_validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
47}
48
49#[derive(Debug)]
51pub(crate) struct ManagedApi {
52 ident: ApiIdent,
56
57 versions: Versions,
59
60 title: &'static str,
62
63 metadata: ManagedApiMetadata,
65
66 api_description:
72 fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,
73
74 extra_validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
76}
77
78impl From<ManagedApiConfig> for ManagedApi {
79 fn from(value: ManagedApiConfig) -> Self {
80 ManagedApi {
81 ident: ApiIdent::from(value.ident.to_owned()),
82 versions: value.versions,
83 title: value.title,
84 metadata: value.metadata,
85 api_description: value.api_description,
86 extra_validation: value.extra_validation,
87 }
88 }
89}
90
91impl ManagedApi {
92 pub fn ident(&self) -> &ApiIdent {
93 &self.ident
94 }
95
96 pub fn versions(&self) -> &Versions {
97 &self.versions
98 }
99
100 pub fn title(&self) -> &'static str {
101 self.title
102 }
103
104 pub fn metadata(&self) -> &ManagedApiMetadata {
105 &self.metadata
106 }
107
108 pub fn is_lockstep(&self) -> bool {
109 self.versions.is_lockstep()
110 }
111
112 pub fn is_versioned(&self) -> bool {
113 self.versions.is_versioned()
114 }
115
116 pub fn iter_versioned_versions(
117 &self,
118 ) -> Option<impl Iterator<Item = &SupportedVersion> + '_> {
119 self.versions.iter_versioned_versions()
120 }
121
122 pub fn iter_versions_semver(&self) -> IterVersionsSemvers<'_> {
123 self.versions.iter_versions_semvers()
124 }
125
126 pub fn generate_openapi_doc(
127 &self,
128 version: &semver::Version,
129 ) -> anyhow::Result<OpenAPI> {
130 let contents = self.generate_spec_bytes(version)?;
135 serde_json::from_slice(&contents)
136 .context("generated document is not valid OpenAPI")
137 }
138
139 pub fn generate_spec_bytes(
140 &self,
141 version: &semver::Version,
142 ) -> anyhow::Result<Vec<u8>> {
143 let description = (self.api_description)().map_err(|error| {
144 anyhow::anyhow!("{}", error)
148 })?;
149 let mut openapi_def = description.openapi(self.title, version.clone());
150 if let Some(description) = self.metadata.description {
151 openapi_def.description(description);
152 }
153 if let Some(contact_url) = self.metadata.contact_url {
154 openapi_def.contact_url(contact_url);
155 }
156 if let Some(contact_email) = self.metadata.contact_email {
157 openapi_def.contact_email(contact_email);
158 }
159
160 let mut contents = Vec::new();
164 openapi_def.write(&mut contents)?;
165 Ok(contents)
166 }
167
168 pub fn extra_validation(
169 &self,
170 openapi: &OpenAPI,
171 validation_context: ValidationContext<'_>,
172 ) {
173 if let Some(extra_validation) = self.extra_validation {
174 extra_validation(openapi, validation_context);
175 }
176 }
177}
178
179#[derive(Debug)]
184pub struct ManagedApis {
185 apis: BTreeMap<ApiIdent, ManagedApi>,
186 unknown_apis: BTreeSet<ApiIdent>,
187 validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
188}
189
190impl ManagedApis {
191 pub fn new(api_list: Vec<ManagedApiConfig>) -> anyhow::Result<ManagedApis> {
196 let mut apis = BTreeMap::new();
197 for api in api_list {
198 let api = ManagedApi::from(api);
199 if let Some(old) = apis.insert(api.ident.clone(), api) {
200 bail!("API is defined twice: {:?}", &old.ident);
201 }
202 }
203
204 Ok(ManagedApis {
205 apis,
206 unknown_apis: BTreeSet::new(),
207 validation: None,
208 })
209 }
210
211 pub fn with_unknown_apis<I, S>(mut self, apis: I) -> Self
218 where
219 I: IntoIterator<Item = S>,
220 S: Into<ApiIdent>,
221 {
222 self.unknown_apis.extend(apis.into_iter().map(|s| s.into()));
223 self
224 }
225
226 pub fn with_validation(
232 mut self,
233 validation: fn(&OpenAPI, ValidationContext<'_>),
234 ) -> Self {
235 self.validation = Some(validation);
236 self
237 }
238
239 pub fn validation(&self) -> Option<fn(&OpenAPI, ValidationContext<'_>)> {
241 self.validation
242 }
243
244 pub fn len(&self) -> usize {
246 self.apis.len()
247 }
248
249 pub fn is_empty(&self) -> bool {
251 self.apis.is_empty()
252 }
253
254 pub(crate) fn iter_apis(
255 &self,
256 ) -> impl Iterator<Item = &'_ ManagedApi> + '_ {
257 self.apis.values()
258 }
259
260 pub(crate) fn api(&self, ident: &ApiIdent) -> Option<&ManagedApi> {
261 self.apis.get(ident)
262 }
263
264 pub fn unknown_apis(&self) -> &BTreeSet<ApiIdent> {
266 &self.unknown_apis
267 }
268}