Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 61 additions & 21 deletions crates/dropshot-api-manager-types/src/versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,15 @@ impl<'a> ExactSizeIterator for IterVersionsSemversInner<'a> {
}
}

/// Helper macro used to define API versions
/// Helper macro used to define API versions.
///
/// ```
/// use dropshot_api_manager_types::{
/// SupportedVersion, SupportedVersions, api_versions,
/// };
///
/// api_versions!([
/// // Define the API versions here.
/// // Define the API versions here. They must be in descending order.
/// (2, ADD_FOOBAR_OPERATION),
/// (1, INITIAL),
/// ]);
Expand All @@ -225,9 +225,15 @@ impl<'a> ExactSizeIterator for IterVersionsSemversInner<'a> {
/// pub const VERSION_INITIAL: semver::Version = semver::Version::new(1, 0, 0);
/// ```
///
/// It also defines a function called `pub fn supported_versions() ->
/// SupportedVersions` that, as the name suggests, returns a
/// [`SupportedVersions`] that describes these two supported API versions.
/// It also defines two functions:
///
/// * `pub fn supported_versions() -> SupportedVersions` that,
/// as the name suggests, returns a [`SupportedVersions`] that describes these
/// two supported API versions.
///
/// * `pub fn latest_version() -> semver::Version` that returns the latest
/// supported API version. The latest supported version is the first version
/// in the list (hence versions must be in descending order).
// Design constraints:
// - For each new API version, we need a developer-chosen semver and label that
// can be used to construct an identifier.
Expand All @@ -254,23 +260,41 @@ impl<'a> ExactSizeIterator for IterVersionsSemversInner<'a> {
// sure there wasn't a mismerge.
#[macro_export]
macro_rules! api_versions {
( [ $( (
$major:literal,
$name:ident
) ),* $(,)? ] ) => {
(
[
(
$latest_major:literal,
$latest_name: ident
)
$(,
(
$major:literal,
$name:ident
)
)*
$(,)?
] ) => {
dropshot_api_manager_types::paste! {
pub const [<VERSION_ $latest_name>]: $crate::semver::Version =
$crate::semver::Version::new($latest_major, 0, 0);

$(
pub const [<VERSION_ $name>]: $crate::semver::Version =
$crate::semver::Version::new($major, 0, 0);
)*

pub fn supported_versions() -> $crate::SupportedVersions {
let mut literal_versions = vec![
$crate::SupportedVersion::new([<VERSION_ $latest_name>], stringify!($latest_name)),
$( $crate::SupportedVersion::new([<VERSION_ $name>], stringify!($name)) ),*
];
literal_versions.reverse();
$crate::SupportedVersions::new(literal_versions)
}

pub const fn latest_version() -> $crate::semver::Version {
[<VERSION_ $latest_name>]
}
}
};
}
Expand All @@ -283,25 +307,41 @@ macro_rules! api_versions {
/// so we can just always bump the major number.
#[macro_export]
macro_rules! api_versions_picky {
( [ $( (
$major:literal,
$minor:literal,
$patch:literal,
$name:ident
) ),* $(,)? ] ) => {
( [
(
$latest_major:literal,
$latest_minor:literal,
$latest_patch:literal,
$latest_name: ident
)
$(,
(
$major:literal,
$minor:literal,
$patch:literal,
$name:ident
)
)* $(,)? ] ) => {
dropshot_api_manager_types::paste! {
pub const [<VERSION_ $latest_name>]: $crate::semver::Version =
$crate::semver::Version::new($latest_major, $latest_minor, $latest_patch);

$(
pub const [<VERSION_ $name>]: semver::Version =
semver::Version::new($major, $minor, $patch);
pub const [<VERSION_ $name>]: $crate::semver::Version =
$crate::semver::Version::new($major, $minor, $patch);
)*

#[track_caller]
pub fn supported_versions() -> SupportedVersions {
pub fn supported_versions() -> $crate::SupportedVersions {
let mut literal_versions = vec![
$( SupportedVersion::new([<VERSION_ $name>], $desc) ),*
$crate::SupportedVersion::new([<VERSION_ $latest_name>], stringify!($latest_name)),
$( $crate::SupportedVersion::new([<VERSION_ $name>], stringify!($name)) ),*
];
literal_versions.reverse();
SupportedVersions::new(literal_versions)
$crate::SupportedVersions::new(literal_versions)
}

pub const fn latest_version() -> $crate::semver::Version {
[<VERSION_ $latest_name>]
}
}
};
Expand Down
7 changes: 6 additions & 1 deletion crates/dropshot-api-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,12 @@ api_versions!([
**For both lockstep and versioned APIs:** once the API crate is defined, update the OpenAPI manager to manage the new OpenAPI document(s). Within this directory:

1. In your repository's integration point's `Cargo.toml`, add a dependency on the API crate.
2. Add the crate to the list of APIs managed by the OpenAPI manager. For versioned APIs, the `api_versions` macro defines a `supported_versions()` function, which you'll need to use
2. Add the crate to the list of APIs managed by the OpenAPI manager. For versioned APIs, the `api_versions` macro defines a `supported_versions()` function, which you'll need to use.

> [!NOTE]
> The `api_versions!` macro also generates a `latest_version` function which returns the latest version (the first version on the list).
>
> When creating the server, you must configure a [`version_policy`](https://docs.rs/dropshot/latest/dropshot/struct.ServerBuilder.html#method.version_policy) to indicate that the API is versioned. If you use the [`ClientSpecifiesVersionInHeader`](https://docs.rs/dropshot/latest/dropshot/struct.ClientSpecifiesVersionInHeader.html) policy, set the `max_version` to the output of `latest_version()`.

To ensure everything works well, run `cargo openapi generate`. Your OpenAPI document should be generated on disk and listed in the output.

Expand Down
6 changes: 6 additions & 0 deletions crates/integration-tests/tests/integration/versioned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ fn test_versioned_generate_basic() -> Result<()> {
let env = TestEnvironment::new()?;
let apis = versioned_health_apis()?;

// Check that latest_version exists.
assert_eq!(
versioned_health::latest_version(),
semver::Version::new(3, 0, 0),
);

// Initially, no documents should exist.
assert!(
!env.versioned_local_document_exists("versioned-health", "1.0.0")
Expand Down