diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e5e54fb..6e886f9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,27 @@ jobs: - name: Lint run: just lint + msrv: + strategy: + matrix: + os: [ubuntu-latest] + rust: [stable] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Init Hermit + uses: cashapp/activate-hermit@v1 + with: + cache: true + - name: Setup + run: just setup + - name: Install MSRV + run: cargo install cargo-msrv + - name: MSRV + run: cargo msrv --path crates/web5/ verify + test: strategy: matrix: diff --git a/Justfile b/Justfile index 0d408f6f..a55113f1 100644 --- a/Justfile +++ b/Justfile @@ -8,7 +8,7 @@ setup: git submodule update --init --recursive fi if [[ "$(cargo 2>&1)" == *"rustup could not choose a version of cargo to run"* ]]; then - rustup default 1.80.0 + rustup default 1.75.0 rustup target add aarch64-apple-darwin fi diff --git a/crates/web5/Cargo.toml b/crates/web5/Cargo.toml index 773bd576..e6a0382e 100644 --- a/crates/web5/Cargo.toml +++ b/crates/web5/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" homepage.workspace = true repository.workspace = true license-file.workspace = true +rust-version = "1.75.0" [dependencies] base64 = { workspace = true } @@ -28,6 +29,7 @@ url = "2.5.0" uuid = { workspace = true } x25519-dalek = { version = "2.0.1", features = ["getrandom", "static_secrets"] } zbase32 = "0.1.2" +lazy_static = "1.5.0" [dev-dependencies] tokio = { version = "1.38.0", features = ["macros", "test-util"] } diff --git a/crates/web5/src/credentials/verifiable_credential_1_1.rs b/crates/web5/src/credentials/verifiable_credential_1_1.rs index e3229d66..9118fe1b 100644 --- a/crates/web5/src/credentials/verifiable_credential_1_1.rs +++ b/crates/web5/src/credentials/verifiable_credential_1_1.rs @@ -537,21 +537,24 @@ mod tests { use crate::json::JsonValue; use regex::Regex; use std::collections::HashMap; - use std::sync::LazyLock; const ISSUER_DID_URI: &str = "did:web:tbd.website"; const SUBJECT_DID_URI: &str = "did:dht:qgmmpyjw5hwnqfgzn7wmrm33ady8gb8z9ideib6m9gj4ys6wny8y"; - static ISSUER: LazyLock = LazyLock::new(|| Issuer::from(ISSUER_DID_URI)); - static CREDENTIAL_SUBJECT: LazyLock = - LazyLock::new(|| CredentialSubject::from(SUBJECT_DID_URI)); + fn issuer() -> Issuer { + Issuer::from(ISSUER_DID_URI) + } + fn credential_subject() -> CredentialSubject { + CredentialSubject::from(SUBJECT_DID_URI) + } mod create { use super::*; use crate::{test_helpers::UnitTestSuite, test_name}; - static TEST_SUITE: LazyLock = - LazyLock::new(|| UnitTestSuite::new("verifiable_credential_1_1_create")); + lazy_static::lazy_static! { + static ref TEST_SUITE: UnitTestSuite = UnitTestSuite::new("verifiable_credential_1_1_create"); + } #[test] fn z_assert_all_suite_cases_covered() { @@ -569,8 +572,7 @@ mod tests { fn test_default_context_added_if_not_supplied() { TEST_SUITE.include(test_name!()); - let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), None).unwrap(); assert_eq!(vc.context, vec![BASE_CONTEXT]); } @@ -584,9 +586,7 @@ mod tests { ..Default::default() }); - let vc = - VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), options).unwrap(); assert_eq!(vc.context, vec![BASE_CONTEXT]); } @@ -601,9 +601,7 @@ mod tests { ..Default::default() }); - let vc = - VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), options).unwrap(); assert_eq!(vc.context, vec![BASE_CONTEXT, custom_context]); } @@ -612,8 +610,7 @@ mod tests { fn test_default_type_added_if_not_supplied() { TEST_SUITE.include(test_name!()); - let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), None).unwrap(); assert_eq!(vc.r#type, vec![BASE_TYPE]); } @@ -627,9 +624,7 @@ mod tests { ..Default::default() }); - let vc = - VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), options).unwrap(); assert_eq!(vc.r#type, vec![BASE_TYPE]); } @@ -644,9 +639,7 @@ mod tests { ..Default::default() }); - let vc = - VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), options).unwrap(); assert_eq!(vc.r#type, vec![BASE_TYPE, custom_type]); } @@ -655,8 +648,7 @@ mod tests { fn test_id_generated_if_not_supplied() { TEST_SUITE.include(test_name!()); - let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), None).unwrap(); let uuid_regex = Regex::new(r"^urn:uuid:[0-9a-fA-F-]{36}$").unwrap(); assert!(uuid_regex.is_match(&vc.id)); @@ -672,9 +664,7 @@ mod tests { ..Default::default() }); - let vc = - VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), options).unwrap(); assert_eq!(vc.id, custom_id); } @@ -684,8 +674,7 @@ mod tests { TEST_SUITE.include(test_name!()); let empty_issuer = Issuer::from(""); - let result = - VerifiableCredential::create(empty_issuer, CREDENTIAL_SUBJECT.clone(), None); + let result = VerifiableCredential::create(empty_issuer, credential_subject(), None); match result { Err(Web5Error::Parameter(err_msg)) => { @@ -699,10 +688,9 @@ mod tests { fn test_issuer_string_must_be_set() { TEST_SUITE.include(test_name!()); - let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), None).unwrap(); - assert_eq!(vc.issuer, ISSUER.clone()); + assert_eq!(vc.issuer, issuer()); } #[test] @@ -715,7 +703,7 @@ mod tests { additional_properties: None, }); - let result = VerifiableCredential::create(issuer, CREDENTIAL_SUBJECT.clone(), None); + let result = VerifiableCredential::create(issuer, credential_subject(), None); match result { Err(Web5Error::Parameter(err_msg)) => { @@ -735,7 +723,7 @@ mod tests { additional_properties: None, }); - let result = VerifiableCredential::create(issuer, CREDENTIAL_SUBJECT.clone(), None); + let result = VerifiableCredential::create(issuer, credential_subject(), None); match result { Err(Web5Error::Parameter(err_msg)) => { @@ -755,8 +743,8 @@ mod tests { additional_properties: None, }); - let vc = VerifiableCredential::create(issuer.clone(), CREDENTIAL_SUBJECT.clone(), None) - .unwrap(); + let vc = + VerifiableCredential::create(issuer.clone(), credential_subject(), None).unwrap(); assert_eq!(vc.issuer, issuer); } @@ -778,8 +766,8 @@ mod tests { additional_properties: Some(additional_properties.clone()), }); - let vc = VerifiableCredential::create(issuer.clone(), CREDENTIAL_SUBJECT.clone(), None) - .unwrap(); + let vc = + VerifiableCredential::create(issuer.clone(), credential_subject(), None).unwrap(); match vc.issuer { Issuer::Object(ref obj) => { @@ -795,7 +783,7 @@ mod tests { let credential_subject = CredentialSubject::from(""); - let result = VerifiableCredential::create(ISSUER.clone(), credential_subject, None); + let result = VerifiableCredential::create(issuer(), credential_subject, None); match result { Err(Web5Error::Parameter(err_msg)) => { @@ -809,10 +797,9 @@ mod tests { fn test_credential_subject_must_be_set() { TEST_SUITE.include(test_name!()); - let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), None).unwrap(); - assert_eq!(vc.credential_subject, CREDENTIAL_SUBJECT.clone()); + assert_eq!(vc.credential_subject, credential_subject()); } #[test] @@ -831,8 +818,8 @@ mod tests { additional_properties: Some(additional_properties.clone()), }; - let vc = VerifiableCredential::create(ISSUER.clone(), credential_subject.clone(), None) - .unwrap(); + let vc = + VerifiableCredential::create(issuer(), credential_subject.clone(), None).unwrap(); assert_eq!( vc.credential_subject.additional_properties, @@ -851,9 +838,7 @@ mod tests { ..Default::default() }); - let vc = - VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), options).unwrap(); assert_eq!(vc.issuance_date, issuance_date); } @@ -862,8 +847,7 @@ mod tests { fn test_issuance_date_must_be_now_if_not_supplied() { TEST_SUITE.include(test_name!()); - let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), None).unwrap(); let now = SystemTime::now(); let hundred_millis_ago = now - std::time::Duration::from_millis(100); @@ -881,9 +865,7 @@ mod tests { ..Default::default() }); - let vc = - VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) - .unwrap(); + let vc = VerifiableCredential::create(issuer(), credential_subject(), options).unwrap(); assert_eq!(vc.expiration_date, Some(expiration_date)); } diff --git a/crates/web5/src/crypto/dsa/ed25519.rs b/crates/web5/src/crypto/dsa/ed25519.rs index 7bd3e2ab..30d71256 100644 --- a/crates/web5/src/crypto/dsa/ed25519.rs +++ b/crates/web5/src/crypto/dsa/ed25519.rs @@ -167,13 +167,14 @@ mod tests { use super::*; use crate::{test_helpers::UnitTestSuite, test_name}; use general_purpose::URL_SAFE_NO_PAD; - use std::sync::LazyLock; + use lazy_static::lazy_static; mod generate { use super::*; - static TEST_SUITE: LazyLock = - LazyLock::new(|| UnitTestSuite::new("ed25519_generate")); + lazy_static! { + static ref TEST_SUITE: UnitTestSuite = UnitTestSuite::new("ed25519_generate"); + } #[test] fn z_assert_all_suite_cases_covered() { @@ -238,8 +239,9 @@ mod tests { mod sign { use super::*; - static TEST_SUITE: LazyLock = - LazyLock::new(|| UnitTestSuite::new("ed25519_sign")); + lazy_static! { + static ref TEST_SUITE: UnitTestSuite = UnitTestSuite::new("ed25519_sign"); + } #[test] fn z_assert_all_suite_cases_covered() { @@ -324,8 +326,9 @@ mod tests { mod verify { use super::*; - static TEST_SUITE: LazyLock = - LazyLock::new(|| UnitTestSuite::new("ed25519_verify")); + lazy_static! { + static ref TEST_SUITE: UnitTestSuite = UnitTestSuite::new("ed25519_verify"); + } fn generate_keys() -> (Jwk, Jwk) { let private_jwk = Ed25519Generator::generate(); diff --git a/crates/web5/src/crypto/jwk.rs b/crates/web5/src/crypto/jwk.rs index 2a4751f1..25fad3cb 100644 --- a/crates/web5/src/crypto/jwk.rs +++ b/crates/web5/src/crypto/jwk.rs @@ -76,10 +76,11 @@ mod tests { mod compute_thumbprint { use super::*; use crate::{errors::Web5Error, test_helpers::UnitTestSuite, test_name}; - use std::sync::LazyLock; + use lazy_static::lazy_static; - static TEST_SUITE: LazyLock = - LazyLock::new(|| UnitTestSuite::new("jwk_compute_thumbprint")); + lazy_static! { + static ref TEST_SUITE: UnitTestSuite = UnitTestSuite::new("jwk_compute_thumbprint"); + } #[test] fn z_assert_all_suite_cases_covered() { diff --git a/crates/web5/src/dids/did.rs b/crates/web5/src/dids/did.rs index c01548a0..684fd89b 100644 --- a/crates/web5/src/dids/did.rs +++ b/crates/web5/src/dids/did.rs @@ -1,7 +1,8 @@ use crate::errors::{Result, Web5Error}; +use lazy_static::lazy_static; use regex::Regex; +use std::collections::HashMap; use std::fmt; -use std::{collections::HashMap, sync::LazyLock}; #[derive(Debug, Clone, Default, PartialEq)] pub struct Did { @@ -28,36 +29,31 @@ static PATH_INDEX: usize = 6; static QUERY_INDEX: usize = 7; static FRAGMENT_INDEX: usize = 8; -static DID_URL_PATTERN: LazyLock = LazyLock::new(|| { - // relevant ABNF rules: https://www.w3.org/TR/did-core/#did-syntax - let pct_encoded_pattern: &str = r"(?:%[0-9a-fA-F]{2})"; - let method_pattern: &str = r"([a-z0-9]+)"; - let param_char_pattern: &str = r"[a-zA-Z0-9_.:%-]"; - let path_pattern: &str = r"(/[^#?]*)?"; - let query_pattern: &str = r"(\?[^\#]*)?"; - let fragment_pattern: &str = r"(\#.*)?"; - let id_char_pattern = format!(r"(?:[a-zA-Z0-9._-]|{})", pct_encoded_pattern); - let method_id_pattern = format!(r"((?:{}*:)*({}+))", id_char_pattern, id_char_pattern); - let param_pattern = format!(r";{}+={}*", param_char_pattern, param_char_pattern); - let params_pattern = format!(r"(({})*)", param_pattern); - - Regex::new(&format!( - r"^did:{}:{}{}{}{}{}$", - method_pattern, - method_id_pattern, - params_pattern, - path_pattern, - query_pattern, - fragment_pattern - )) - .map_err(|e| { - Web5Error::Parameter(format!( - "DID_URL_PATTERN regex instantiation failure: {}", - e +lazy_static! { + static ref DID_URL_PATTERN: Regex = { + let pct_encoded_pattern: &str = r"(?:%[0-9a-fA-F]{2})"; + let method_pattern: &str = r"([a-z0-9]+)"; + let param_char_pattern: &str = r"[a-zA-Z0-9_.:%-]"; + let path_pattern: &str = r"(/[^#?]*)?"; + let query_pattern: &str = r"(\?[^\#]*)?"; + let fragment_pattern: &str = r"(\#.*)?"; + let id_char_pattern = format!(r"(?:[a-zA-Z0-9._-]|{})", pct_encoded_pattern); + let method_id_pattern = format!(r"((?:{}*:)*({}+))", id_char_pattern, id_char_pattern); + let param_pattern = format!(r";{}+={}*", param_char_pattern, param_char_pattern); + let params_pattern = format!(r"(({})*)", param_pattern); + + Regex::new(&format!( + r"^did:{}:{}{}{}{}{}$", + method_pattern, + method_id_pattern, + params_pattern, + path_pattern, + query_pattern, + fragment_pattern )) - }) - .unwrap() // immediately panic on startup if regex is faulty, this will assist in shift-left development -}); + .unwrap() + }; +} impl Did { pub fn parse(uri: &str) -> Result { @@ -118,8 +114,10 @@ mod tests { use super::*; use crate::{test_helpers::UnitTestSuite, test_name}; - static TEST_SUITE: LazyLock = - LazyLock::new(|| UnitTestSuite::new("did_parse")); + lazy_static::lazy_static! { + static ref TEST_SUITE: UnitTestSuite = + UnitTestSuite::new("did_parse"); + } #[test] fn z_assert_all_suite_cases_covered() {