Skip to content

Commit 4e04448

Browse files
committed
[spr] initial version
Created using spr 1.3.6-beta.1
1 parent a8c7dcf commit 4e04448

File tree

7 files changed

+115
-63
lines changed

7 files changed

+115
-63
lines changed

crates/dropshot-api-manager/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ For extra validation on some documents, it's recommended that you put them on th
163163

164164
1. In the API crate, add dependencies on `openapiv3` and `dropshot-api-manager-types`.
165165
2. Define a function with signature `fn validate_api(spec: &openapiv3::OpenAPI, mut cx: dropshot_api_manager_types::ValidationContext<'_>) which performs the extra validation steps.
166-
3. In the list of managed APIs, set the `extra_validation` field to this function.
166+
3. Convert the `ManagedApiConfig` to a `ManagedApi` and call the `with_extra_validation` builder method with this function.
167167

168168
Currently, the validator can do two things:
169169

crates/dropshot-api-manager/src/apis.rs

Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ use dropshot_api_manager_types::{
77
ValidationContext, Versions,
88
};
99
use openapiv3::OpenAPI;
10-
use std::collections::{BTreeMap, BTreeSet};
10+
use std::{
11+
collections::{BTreeMap, BTreeSet},
12+
fmt,
13+
};
14+
15+
use crate::validation::DynValidationFn;
1116

1217
/// Describes an API managed by the Dropshot API manager.
1318
///
@@ -36,22 +41,13 @@ pub struct ManagedApiConfig {
3641
/// server implementation.
3742
pub api_description:
3843
fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,
39-
40-
/// Extra validation to perform on the OpenAPI document, if any.
41-
///
42-
/// For versioned APIs, extra validation is performed on *all* versions,
43-
/// including blessed ones. You may want to skip performing validation on
44-
/// blessed versions, though, because they're immutable. To do so, use
45-
/// [`ValidationContext::is_blessed`].
46-
pub extra_validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
4744
}
4845

4946
/// Describes an API managed by the Dropshot API manager.
5047
///
5148
/// This type is typically created from a [`ManagedApiConfig`] and can be
5249
/// further configured using builder methods before being passed to
5350
/// [`ManagedApis::new`].
54-
#[derive(Debug)]
5551
pub struct ManagedApi {
5652
/// The API-specific part of the filename that's used for API descriptions
5753
///
@@ -76,7 +72,12 @@ pub struct ManagedApi {
7672
fn() -> Result<ApiDescription<StubContext>, ApiDescriptionBuildErrors>,
7773

7874
/// Extra validation to perform on the OpenAPI document, if any.
79-
extra_validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
75+
///
76+
/// For versioned APIs, extra validation is performed on *all* versions,
77+
/// including blessed ones. You may want to skip performing validation on
78+
/// blessed versions, though, because they're immutable. To do so, use
79+
/// [`ValidationContext::is_blessed`].
80+
extra_validation: Option<Box<DynValidationFn>>,
8081

8182
/// If true, allow trivial changes (doc updates, type renames) for the
8283
/// latest blessed version without requiring version bumps.
@@ -85,15 +86,52 @@ pub struct ManagedApi {
8586
allow_trivial_changes_for_latest: bool,
8687
}
8788

89+
impl fmt::Debug for ManagedApi {
90+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91+
let Self {
92+
ident,
93+
versions,
94+
title,
95+
metadata,
96+
api_description: _,
97+
extra_validation,
98+
allow_trivial_changes_for_latest,
99+
} = self;
100+
101+
f.debug_struct("ManagedApi")
102+
.field("ident", ident)
103+
.field("versions", versions)
104+
.field("title", title)
105+
.field("metadata", metadata)
106+
.field("api_description", &"...")
107+
.field(
108+
"extra_validation",
109+
&extra_validation.as_ref().map(|_| "..."),
110+
)
111+
.field(
112+
"allow_trivial_changes_for_latest",
113+
allow_trivial_changes_for_latest,
114+
)
115+
.finish()
116+
}
117+
}
118+
88119
impl From<ManagedApiConfig> for ManagedApi {
89120
fn from(value: ManagedApiConfig) -> Self {
121+
let ManagedApiConfig {
122+
ident,
123+
versions,
124+
title,
125+
metadata,
126+
api_description,
127+
} = value;
90128
ManagedApi {
91-
ident: ApiIdent::from(value.ident.to_owned()),
92-
versions: value.versions,
93-
title: value.title,
94-
metadata: value.metadata,
95-
api_description: value.api_description,
96-
extra_validation: value.extra_validation,
129+
ident: ApiIdent::from(ident.to_owned()),
130+
versions,
131+
title,
132+
metadata,
133+
api_description,
134+
extra_validation: None,
97135
allow_trivial_changes_for_latest: false,
98136
}
99137
}
@@ -147,6 +185,20 @@ impl ManagedApi {
147185
self.allow_trivial_changes_for_latest
148186
}
149187

188+
/// Sets extra validation to perform on the OpenAPI document.
189+
///
190+
/// For versioned APIs, extra validation is performed on *all* versions,
191+
/// including blessed ones. You may want to skip performing validation on
192+
/// blessed versions, though, because they're immutable. To do so, use
193+
/// [`ValidationContext::is_blessed`].
194+
pub fn with_extra_validation<F>(mut self, f: F) -> Self
195+
where
196+
F: Fn(&OpenAPI, ValidationContext<'_>) + Send + 'static,
197+
{
198+
self.extra_validation = Some(Box::new(f));
199+
self
200+
}
201+
150202
pub(crate) fn iter_versioned_versions(
151203
&self,
152204
) -> Option<impl Iterator<Item = &SupportedVersion> + '_> {
@@ -204,7 +256,7 @@ impl ManagedApi {
204256
openapi: &OpenAPI,
205257
validation_context: ValidationContext<'_>,
206258
) {
207-
if let Some(extra_validation) = self.extra_validation {
259+
if let Some(extra_validation) = &self.extra_validation {
208260
extra_validation(openapi, validation_context);
209261
}
210262
}
@@ -214,11 +266,22 @@ impl ManagedApi {
214266
/// tool.
215267
///
216268
/// This is repo-specific state that's passed into the OpenAPI manager.
217-
#[derive(Debug)]
218269
pub struct ManagedApis {
219270
apis: BTreeMap<ApiIdent, ManagedApi>,
220271
unknown_apis: BTreeSet<ApiIdent>,
221-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
272+
validation: Option<Box<DynValidationFn>>,
273+
}
274+
275+
impl fmt::Debug for ManagedApis {
276+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277+
let Self { apis, unknown_apis, validation } = self;
278+
279+
f.debug_struct("ManagedApis")
280+
.field("apis", apis)
281+
.field("unknown_apis", unknown_apis)
282+
.field("validation", &validation.as_ref().map(|_| "..."))
283+
.finish()
284+
}
222285
}
223286

224287
impl ManagedApis {
@@ -268,17 +331,17 @@ impl ManagedApis {
268331
/// This function will be called for each API document. The
269332
/// [`ValidationContext`] can be used to report errors, as well as extra
270333
/// files for which the contents need to be compared with those on disk.
271-
pub fn with_validation(
272-
mut self,
273-
validation: fn(&OpenAPI, ValidationContext<'_>),
274-
) -> Self {
275-
self.validation = Some(validation);
334+
pub fn with_validation<F>(mut self, validation: F) -> Self
335+
where
336+
F: Fn(&OpenAPI, ValidationContext<'_>) + Send + 'static,
337+
{
338+
self.validation = Some(Box::new(validation));
276339
self
277340
}
278341

279342
/// Returns the validation function for all APIs.
280-
pub fn validation(&self) -> Option<fn(&OpenAPI, ValidationContext<'_>)> {
281-
self.validation
343+
pub(crate) fn validation(&self) -> Option<&DynValidationFn> {
344+
self.validation.as_deref()
282345
}
283346

284347
/// Returns the number of APIs managed by this instance.

crates/dropshot-api-manager/src/resolved.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@ use crate::{
1212
spec_files_generated::{GeneratedApiSpecFile, GeneratedFiles},
1313
spec_files_generic::ApiFiles,
1414
spec_files_local::{LocalApiSpecFile, LocalFiles},
15-
validation::{CheckStale, CheckStatus, overwrite_file, validate},
15+
validation::{
16+
CheckStale, CheckStatus, DynValidationFn, overwrite_file, validate,
17+
},
1618
};
1719
use anyhow::{Context, anyhow};
1820
use camino::{Utf8Path, Utf8PathBuf};
19-
use dropshot_api_manager_types::{
20-
ApiIdent, ApiSpecFileName, ValidationContext,
21-
};
22-
use openapiv3::OpenAPI;
21+
use dropshot_api_manager_types::{ApiIdent, ApiSpecFileName};
2322
use std::{
2423
collections::{BTreeMap, BTreeSet},
2524
fmt::{Debug, Display},
@@ -651,7 +650,7 @@ fn resolve_orphaned_local_specs<'a>(
651650
fn resolve_api<'a>(
652651
env: &'a ResolvedEnv,
653652
api: &'a ManagedApi,
654-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
653+
validation: Option<&DynValidationFn>,
655654
api_blessed: Option<&'a ApiFiles<BlessedApiSpecFile>>,
656655
api_generated: &'a ApiFiles<GeneratedApiSpecFile>,
657656
api_local: Option<&'a ApiFiles<Vec<LocalApiSpecFile>>>,
@@ -861,7 +860,7 @@ fn resolve_api<'a>(
861860
fn resolve_api_lockstep<'a>(
862861
env: &'a ResolvedEnv,
863862
api: &'a ManagedApi,
864-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
863+
validation: Option<&DynValidationFn>,
865864
api_generated: &'a ApiFiles<GeneratedApiSpecFile>,
866865
api_local: Option<&'a ApiFiles<Vec<LocalApiSpecFile>>>,
867866
) -> BTreeMap<semver::Version, Resolution<'a>> {
@@ -934,7 +933,7 @@ struct ApiVersion<'a> {
934933
fn resolve_api_version<'a>(
935934
env: &'_ ResolvedEnv,
936935
api: &'_ ManagedApi,
937-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
936+
validation: Option<&DynValidationFn>,
938937
version: ApiVersion<'_>,
939938
blessed: Option<&'a BlessedApiSpecFile>,
940939
generated: &'a GeneratedApiSpecFile,
@@ -953,7 +952,7 @@ fn resolve_api_version<'a>(
953952
fn resolve_api_version_blessed<'a>(
954953
env: &'_ ResolvedEnv,
955954
api: &'_ ManagedApi,
956-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
955+
validation: Option<&DynValidationFn>,
957956
version: ApiVersion<'_>,
958957
blessed: &'a BlessedApiSpecFile,
959958
generated: &'a GeneratedApiSpecFile,
@@ -1052,7 +1051,7 @@ fn resolve_api_version_blessed<'a>(
10521051
fn resolve_api_version_local<'a>(
10531052
env: &'_ ResolvedEnv,
10541053
api: &'_ ManagedApi,
1055-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
1054+
validation: Option<&DynValidationFn>,
10561055
version: ApiVersion<'_>,
10571056
generated: &'a GeneratedApiSpecFile,
10581057
local: &'a [LocalApiSpecFile],
@@ -1093,7 +1092,7 @@ fn resolve_api_version_local<'a>(
10931092
fn validate_generated(
10941093
env: &ResolvedEnv,
10951094
api: &ManagedApi,
1096-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
1095+
validation: Option<&DynValidationFn>,
10971096
version: ApiVersion<'_>,
10981097
generated: &GeneratedApiSpecFile,
10991098
problems: &mut Vec<Problem<'_>>,

crates/dropshot-api-manager/src/spec_files_generic.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,6 @@ mod test {
835835
..ManagedApiMetadata::default()
836836
},
837837
api_description: unimplemented_fn,
838-
extra_validation: None,
839838
},
840839
ManagedApiConfig {
841840
ident: "versioned",
@@ -850,7 +849,6 @@ mod test {
850849
..ManagedApiMetadata::default()
851850
},
852851
api_description: unimplemented_fn,
853-
extra_validation: None,
854852
},
855853
];
856854

crates/dropshot-api-manager/src/validation.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ use dropshot_api_manager_types::{
1414
use openapiv3::OpenAPI;
1515
use std::io::Write;
1616

17+
/// A validation function that can be called on an OpenAPI document.
18+
pub(crate) type DynValidationFn =
19+
dyn Fn(&OpenAPI, ValidationContext<'_>) + Send;
20+
1721
pub fn validate(
1822
env: &ResolvedEnv,
1923
api: &ManagedApi,
2024
is_latest: bool,
2125
is_blessed: Option<bool>,
22-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
26+
validation: Option<&DynValidationFn>,
2327
generated: &GeneratedApiSpecFile,
2428
) -> anyhow::Result<Vec<(Utf8PathBuf, CheckStatus)>> {
2529
let openapi = generated.openapi();
@@ -49,7 +53,7 @@ fn validate_generated_openapi_document(
4953
file_name: &ApiSpecFileName,
5054
is_latest: bool,
5155
is_blessed: Option<bool>,
52-
validation: Option<fn(&OpenAPI, ValidationContext<'_>)>,
56+
validation: Option<&DynValidationFn>,
5357
) -> anyhow::Result<ValidationResult> {
5458
let mut validation_context = ValidationContextImpl {
5559
ident: api.ident().clone(),

0 commit comments

Comments
 (0)