Skip to content

Commit 88a1696

Browse files
authored
Merge pull request #706 from input-output-hk/djo/705/api_version_enforcement_update_
[#705] Api version enforcement update
2 parents 25f2d34 + c2c99ea commit 88a1696

File tree

8 files changed

+135
-17
lines changed

8 files changed

+135
-17
lines changed

Cargo.lock

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mithril-aggregator/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-aggregator"
3-
version = "0.2.6"
3+
version = "0.2.7"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }
@@ -19,6 +19,7 @@ flate2 = "1.0.23"
1919
hex = "0.4.3"
2020
mithril-common = { path = "../mithril-common" }
2121
reqwest = { version = "0.11", features = ["json"] }
22+
semver = "1.0.16"
2223
serde = { version = "1.0", features = ["derive"] }
2324
serde_json = "1.0"
2425
serde_yaml = "0.9.10"

mithril-aggregator/src/http_server/routes/router.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ use crate::http_server::routes::{
44
use crate::http_server::SERVER_BASE_PATH;
55
use crate::DependencyManager;
66

7-
use mithril_common::MITHRIL_API_VERSION;
7+
use mithril_common::{MITHRIL_API_VERSION, MITHRIL_API_VERSION_REQUIREMENT};
88

99
use reqwest::header::{HeaderMap, HeaderValue};
1010
use reqwest::StatusCode;
11+
use slog_scope::warn;
1112
use std::sync::Arc;
1213
use warp::http::Method;
1314
use warp::reject::Reject;
@@ -18,6 +19,11 @@ pub struct VersionMismatchError;
1819

1920
impl Reject for VersionMismatchError {}
2021

22+
#[derive(Debug)]
23+
pub struct VersionParseError;
24+
25+
impl Reject for VersionParseError {}
26+
2127
/// Routes
2228
pub fn routes(
2329
dependency_manager: Arc<DependencyManager>,
@@ -52,8 +58,14 @@ fn header_must_be() -> impl Filter<Extract = (), Error = Rejection> + Copy {
5258
.and_then(|maybe_header: Option<String>| async move {
5359
match maybe_header {
5460
None => Ok(()),
55-
Some(version) if version == MITHRIL_API_VERSION => Ok(()),
56-
Some(_version) => Err(warp::reject::custom(VersionMismatchError)),
61+
Some(version) => match semver::Version::parse(&version) {
62+
Ok(version) if MITHRIL_API_VERSION_REQUIREMENT.matches(&version) => Ok(()),
63+
Ok(_version) => Err(warp::reject::custom(VersionMismatchError)),
64+
Err(err) => {
65+
warn!("⇄ HTTP SERVER::api_version_check::parse_error"; "error" => ?err);
66+
Err(warp::reject::custom(VersionParseError))
67+
}
68+
},
5769
}
5870
})
5971
.untuple_one()
@@ -78,7 +90,20 @@ mod tests {
7890
.path("/aggregator/whatever")
7991
.filter(&filters)
8092
.await
81-
.unwrap();
93+
.expect("request without a version in headers should not be rejected");
94+
}
95+
96+
#[tokio::test]
97+
async fn test_parse_version_error() {
98+
let filters = header_must_be();
99+
warp::test::request()
100+
.header("mithril-api-version", "not_a_version")
101+
.path("/aggregator/whatever")
102+
.filter(&filters)
103+
.await
104+
.expect_err(
105+
r#"request with an unparsable version should be rejected with a version parse error"#,
106+
);
82107
}
83108

84109
#[tokio::test]
@@ -89,7 +114,7 @@ mod tests {
89114
.path("/aggregator/whatever")
90115
.filter(&filters)
91116
.await
92-
.unwrap_err();
117+
.expect_err(r#"request with bad version "0.0.999" should be rejected with a version mismatch error"#);
93118
}
94119

95120
#[tokio::test]
@@ -100,6 +125,6 @@ mod tests {
100125
.path("/aggregator/whatever")
101126
.filter(&filters)
102127
.await
103-
.unwrap();
128+
.expect("request with the current api version should not be rejected");
104129
}
105130
}

mithril-client/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-client"
3-
version = "0.2.3"
3+
version = "0.2.4"
44
description = "A Mithril Client"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-common/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-common"
3-
version = "0.2.6"
3+
version = "0.2.7"
44
authors = { workspace = true }
55
edition = { workspace = true }
66
documentation = { workspace = true }
@@ -28,6 +28,7 @@ hex = "0.4.3"
2828
http = "0.2.6"
2929
jsonschema = "0.16.0"
3030
kes-summed-ed25519 = { version = "0.1.1", features = ["serde_enabled"] }
31+
lazy_static = "1.4.0"
3132
mockall = "0.11.0"
3233
nom = "7.1"
3334
rand-chacha-dalek-compat = { package = "rand_chacha", version = "0.2" }

mithril-common/src/lib.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,97 @@ pub mod test_utils;
2525
pub use beacon_provider::{BeaconProvider, BeaconProviderError, BeaconProviderImpl};
2626
pub use entities::{CardanoNetwork, MagicId};
2727

28+
use lazy_static::lazy_static;
29+
use semver::{Version, VersionReq};
30+
2831
/// Mithril API protocol version
2932
/// this is the same as the one in openapi.yml file.
3033
/// If you want to update this version to reflect changes in the protocol,
3134
/// please also update the entry in the openapi.yml
32-
pub const MITHRIL_API_VERSION: &str = "0.1.0";
35+
pub const MITHRIL_API_VERSION: &str = "0.1.1";
36+
37+
lazy_static! {
38+
/// The [SemVer version requirement][semver::VersionReq] associated with the [MITHRIL_API_VERSION].
39+
///
40+
/// A beta version (0.x.y) will allow all versions within the same major & minor.
41+
/// A stable version (>=1.x.y) will allow all versions within the same major.
42+
pub static ref MITHRIL_API_VERSION_REQUIREMENT: VersionReq =
43+
build_requirement_from_version(&Version::parse(MITHRIL_API_VERSION).unwrap());
44+
}
45+
46+
fn build_requirement_from_version(version: &Version) -> VersionReq {
47+
let mut req_version = version.clone();
48+
req_version.patch = 0;
49+
50+
if version.major > 0 {
51+
req_version.minor = 0;
52+
}
53+
54+
VersionReq::parse(&req_version.to_string()).unwrap()
55+
}
56+
57+
#[cfg(test)]
58+
mod test {
59+
use crate::{
60+
build_requirement_from_version, MITHRIL_API_VERSION, MITHRIL_API_VERSION_REQUIREMENT,
61+
};
62+
use semver::{Version, VersionReq};
63+
64+
const API_SPEC_FILE: &str = "../openapi.yaml";
65+
66+
fn assert_versions_matches(versions: &[&str], requirement: &VersionReq) {
67+
for string in versions {
68+
let version = Version::parse(string).unwrap();
69+
assert!(
70+
requirement.matches(&version),
71+
"Version {} did not match requirement: {}",
72+
&version,
73+
requirement
74+
);
75+
}
76+
}
77+
78+
fn assert_versions_dont_matches(versions: &[&str], requirement: &VersionReq) {
79+
for string in versions {
80+
let version = Version::parse(string).unwrap();
81+
assert!(
82+
!requirement.matches(&version),
83+
"Did not expect that version {} match requirement: {}",
84+
&version,
85+
requirement
86+
);
87+
}
88+
}
89+
90+
#[test]
91+
fn test_semver_requirement_matching() {
92+
let beta_requirement = build_requirement_from_version(&Version::parse("0.2.4").unwrap());
93+
assert_versions_matches(&["0.2.0", "0.2.4", "0.2.5", "0.2.99"], &beta_requirement);
94+
assert_versions_dont_matches(&["0.1.10", "0.3.0", "1.0.0"], &beta_requirement);
95+
96+
let stable_requirement = build_requirement_from_version(&Version::parse("2.1.4").unwrap());
97+
assert_versions_matches(
98+
&["2.0.0", "2.1.0", "2.1.4", "2.1.5", "2.12.8"],
99+
&stable_requirement,
100+
);
101+
assert_versions_dont_matches(&["0.0.0", "1.11.9", "3.0.0"], &stable_requirement);
102+
}
103+
104+
#[test]
105+
fn requirement_parsed_from_api_version_should_match_said_api_version() {
106+
let api_version = Version::parse(MITHRIL_API_VERSION).unwrap();
107+
assert!(MITHRIL_API_VERSION_REQUIREMENT.matches(&api_version));
108+
}
109+
110+
#[test]
111+
fn api_version_constant_should_match_version_in_openapi_yaml() {
112+
let yaml_spec = std::fs::read_to_string(API_SPEC_FILE).unwrap();
113+
let openapi: serde_json::Value = serde_yaml::from_str(&yaml_spec).unwrap();
114+
let openapi_version = openapi["info"]["version"].as_str().unwrap();
115+
116+
assert_eq!(
117+
openapi_version, MITHRIL_API_VERSION,
118+
"MITHRIL_API_VERSION constant should always be synced with openapi.yaml version"
119+
);
120+
}
121+
}

mithril-signer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-signer"
3-
version = "0.2.4"
3+
version = "0.2.5"
44
description = "A Mithril Signer"
55
authors = { workspace = true }
66
edition = { workspace = true }

openapi.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
openapi: "3.0.0"
22
info:
33
# The protocol version is embedded in the code as constant in the
4-
# `mithril-aggregator/src/http_server/mod.rs` file. If you plan to update it
4+
# `mithril-common/src/lib.rs` file. If you plan to update it
55
# here to reflect changes in the API, please also update the constant in the
66
# Rust file.
7-
version: 0.1.0
7+
version: 0.1.1
88
title: Mithril Aggregator Server
99
description: |
1010
The REST API provided by a Mithril Aggregator Node in a Mithril network.

0 commit comments

Comments
 (0)