Skip to content

Commit

Permalink
Merge pull request #706 from input-output-hk/djo/705/api_version_enfo…
Browse files Browse the repository at this point in the history
…rcement_update_

[#705] Api version enforcement update
  • Loading branch information
Alenar authored Jan 23, 2023
2 parents 25f2d34 + c2c99ea commit 88a1696
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 17 deletions.
10 changes: 6 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion mithril-aggregator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-aggregator"
version = "0.2.6"
version = "0.2.7"
description = "A Mithril Aggregator server"
authors = { workspace = true }
edition = { workspace = true }
Expand All @@ -19,6 +19,7 @@ flate2 = "1.0.23"
hex = "0.4.3"
mithril-common = { path = "../mithril-common" }
reqwest = { version = "0.11", features = ["json"] }
semver = "1.0.16"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9.10"
Expand Down
37 changes: 31 additions & 6 deletions mithril-aggregator/src/http_server/routes/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use crate::http_server::routes::{
use crate::http_server::SERVER_BASE_PATH;
use crate::DependencyManager;

use mithril_common::MITHRIL_API_VERSION;
use mithril_common::{MITHRIL_API_VERSION, MITHRIL_API_VERSION_REQUIREMENT};

use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::StatusCode;
use slog_scope::warn;
use std::sync::Arc;
use warp::http::Method;
use warp::reject::Reject;
Expand All @@ -18,6 +19,11 @@ pub struct VersionMismatchError;

impl Reject for VersionMismatchError {}

#[derive(Debug)]
pub struct VersionParseError;

impl Reject for VersionParseError {}

/// Routes
pub fn routes(
dependency_manager: Arc<DependencyManager>,
Expand Down Expand Up @@ -52,8 +58,14 @@ fn header_must_be() -> impl Filter<Extract = (), Error = Rejection> + Copy {
.and_then(|maybe_header: Option<String>| async move {
match maybe_header {
None => Ok(()),
Some(version) if version == MITHRIL_API_VERSION => Ok(()),
Some(_version) => Err(warp::reject::custom(VersionMismatchError)),
Some(version) => match semver::Version::parse(&version) {
Ok(version) if MITHRIL_API_VERSION_REQUIREMENT.matches(&version) => Ok(()),
Ok(_version) => Err(warp::reject::custom(VersionMismatchError)),
Err(err) => {
warn!("⇄ HTTP SERVER::api_version_check::parse_error"; "error" => ?err);
Err(warp::reject::custom(VersionParseError))
}
},
}
})
.untuple_one()
Expand All @@ -78,7 +90,20 @@ mod tests {
.path("/aggregator/whatever")
.filter(&filters)
.await
.unwrap();
.expect("request without a version in headers should not be rejected");
}

#[tokio::test]
async fn test_parse_version_error() {
let filters = header_must_be();
warp::test::request()
.header("mithril-api-version", "not_a_version")
.path("/aggregator/whatever")
.filter(&filters)
.await
.expect_err(
r#"request with an unparsable version should be rejected with a version parse error"#,
);
}

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

#[tokio::test]
Expand All @@ -100,6 +125,6 @@ mod tests {
.path("/aggregator/whatever")
.filter(&filters)
.await
.unwrap();
.expect("request with the current api version should not be rejected");
}
}
2 changes: 1 addition & 1 deletion mithril-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-client"
version = "0.2.3"
version = "0.2.4"
description = "A Mithril Client"
authors = { workspace = true }
edition = { workspace = true }
Expand Down
3 changes: 2 additions & 1 deletion mithril-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-common"
version = "0.2.6"
version = "0.2.7"
authors = { workspace = true }
edition = { workspace = true }
documentation = { workspace = true }
Expand Down Expand Up @@ -28,6 +28,7 @@ hex = "0.4.3"
http = "0.2.6"
jsonschema = "0.16.0"
kes-summed-ed25519 = { version = "0.1.1", features = ["serde_enabled"] }
lazy_static = "1.4.0"
mockall = "0.11.0"
nom = "7.1"
rand-chacha-dalek-compat = { package = "rand_chacha", version = "0.2" }
Expand Down
91 changes: 90 additions & 1 deletion mithril-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,97 @@ pub mod test_utils;
pub use beacon_provider::{BeaconProvider, BeaconProviderError, BeaconProviderImpl};
pub use entities::{CardanoNetwork, MagicId};

use lazy_static::lazy_static;
use semver::{Version, VersionReq};

/// Mithril API protocol version
/// this is the same as the one in openapi.yml file.
/// If you want to update this version to reflect changes in the protocol,
/// please also update the entry in the openapi.yml
pub const MITHRIL_API_VERSION: &str = "0.1.0";
pub const MITHRIL_API_VERSION: &str = "0.1.1";

lazy_static! {
/// The [SemVer version requirement][semver::VersionReq] associated with the [MITHRIL_API_VERSION].
///
/// A beta version (0.x.y) will allow all versions within the same major & minor.
/// A stable version (>=1.x.y) will allow all versions within the same major.
pub static ref MITHRIL_API_VERSION_REQUIREMENT: VersionReq =
build_requirement_from_version(&Version::parse(MITHRIL_API_VERSION).unwrap());
}

fn build_requirement_from_version(version: &Version) -> VersionReq {
let mut req_version = version.clone();
req_version.patch = 0;

if version.major > 0 {
req_version.minor = 0;
}

VersionReq::parse(&req_version.to_string()).unwrap()
}

#[cfg(test)]
mod test {
use crate::{
build_requirement_from_version, MITHRIL_API_VERSION, MITHRIL_API_VERSION_REQUIREMENT,
};
use semver::{Version, VersionReq};

const API_SPEC_FILE: &str = "../openapi.yaml";

fn assert_versions_matches(versions: &[&str], requirement: &VersionReq) {
for string in versions {
let version = Version::parse(string).unwrap();
assert!(
requirement.matches(&version),
"Version {} did not match requirement: {}",
&version,
requirement
);
}
}

fn assert_versions_dont_matches(versions: &[&str], requirement: &VersionReq) {
for string in versions {
let version = Version::parse(string).unwrap();
assert!(
!requirement.matches(&version),
"Did not expect that version {} match requirement: {}",
&version,
requirement
);
}
}

#[test]
fn test_semver_requirement_matching() {
let beta_requirement = build_requirement_from_version(&Version::parse("0.2.4").unwrap());
assert_versions_matches(&["0.2.0", "0.2.4", "0.2.5", "0.2.99"], &beta_requirement);
assert_versions_dont_matches(&["0.1.10", "0.3.0", "1.0.0"], &beta_requirement);

let stable_requirement = build_requirement_from_version(&Version::parse("2.1.4").unwrap());
assert_versions_matches(
&["2.0.0", "2.1.0", "2.1.4", "2.1.5", "2.12.8"],
&stable_requirement,
);
assert_versions_dont_matches(&["0.0.0", "1.11.9", "3.0.0"], &stable_requirement);
}

#[test]
fn requirement_parsed_from_api_version_should_match_said_api_version() {
let api_version = Version::parse(MITHRIL_API_VERSION).unwrap();
assert!(MITHRIL_API_VERSION_REQUIREMENT.matches(&api_version));
}

#[test]
fn api_version_constant_should_match_version_in_openapi_yaml() {
let yaml_spec = std::fs::read_to_string(API_SPEC_FILE).unwrap();
let openapi: serde_json::Value = serde_yaml::from_str(&yaml_spec).unwrap();
let openapi_version = openapi["info"]["version"].as_str().unwrap();

assert_eq!(
openapi_version, MITHRIL_API_VERSION,
"MITHRIL_API_VERSION constant should always be synced with openapi.yaml version"
);
}
}
2 changes: 1 addition & 1 deletion mithril-signer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-signer"
version = "0.2.4"
version = "0.2.5"
description = "A Mithril Signer"
authors = { workspace = true }
edition = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
openapi: "3.0.0"
info:
# The protocol version is embedded in the code as constant in the
# `mithril-aggregator/src/http_server/mod.rs` file. If you plan to update it
# `mithril-common/src/lib.rs` file. If you plan to update it
# here to reflect changes in the API, please also update the constant in the
# Rust file.
version: 0.1.0
version: 0.1.1
title: Mithril Aggregator Server
description: |
The REST API provided by a Mithril Aggregator Node in a Mithril network.
Expand Down

0 comments on commit 88a1696

Please sign in to comment.