From d225c9277372d1f918399619fe9a46fead74ddd2 Mon Sep 17 00:00:00 2001 From: Emma <156218556+em-baggie@users.noreply.github.com> Date: Sat, 14 Jun 2025 17:59:35 +0100 Subject: [PATCH 1/8] add ctv3 --- rust/codelist-rs/src/types.rs | 6 + .../src/ctv3_validator.rs | 227 ++++++++++++++++++ rust/codelist-validator-rs/src/lib.rs | 1 + rust/codelist-validator-rs/src/validator.rs | 9 +- 4 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 rust/codelist-validator-rs/src/ctv3_validator.rs diff --git a/rust/codelist-rs/src/types.rs b/rust/codelist-rs/src/types.rs index 072b57a..ba0ca05 100644 --- a/rust/codelist-rs/src/types.rs +++ b/rust/codelist-rs/src/types.rs @@ -19,6 +19,7 @@ pub enum CodeListType { ICD10, SNOMED, OPCS, + CTV3, } impl CodeListType { @@ -56,6 +57,7 @@ impl FromStr for CodeListType { "icd10" => Ok(CodeListType::ICD10), "snomed" => Ok(CodeListType::SNOMED), "opcs" => Ok(CodeListType::OPCS), + "ctv3" => Ok(CodeListType::CTV3), invalid_string => Err(CodeListError::invalid_code_list_type(invalid_string)), } } @@ -71,6 +73,7 @@ impl fmt::Display for CodeListType { CodeListType::ICD10 => "ICD10", CodeListType::SNOMED => "SNOMED", CodeListType::OPCS => "OPCS", + CodeListType::CTV3 => "CTV3", }; write!(f, "{s}") } @@ -85,6 +88,7 @@ mod tests { assert!(matches!(CodeListType::from_str("icd10"), Ok(CodeListType::ICD10))); assert!(matches!(CodeListType::from_str("snomed"), Ok(CodeListType::SNOMED))); assert!(matches!(CodeListType::from_str("opcs"), Ok(CodeListType::OPCS))); + assert!(matches!(CodeListType::from_str("ctv3"), Ok(CodeListType::CTV3))); assert!(matches!(CodeListType::from_str("invalid"), Err(CodeListError::InvalidCodeListType { name }) if name == "invalid")); } @@ -94,6 +98,7 @@ mod tests { assert!(matches!(CodeListType::from_str("ICD10"), Ok(CodeListType::ICD10))); assert!(matches!(CodeListType::from_str("SNOMED"), Ok(CodeListType::SNOMED))); assert!(matches!(CodeListType::from_str("OPCS"), Ok(CodeListType::OPCS))); + assert!(matches!(CodeListType::from_str("ctv3"), Ok(CodeListType::CTV3))); } #[test] @@ -101,5 +106,6 @@ mod tests { assert_eq!(CodeListType::ICD10.to_string(), "ICD10"); assert_eq!(CodeListType::SNOMED.to_string(), "SNOMED"); assert_eq!(CodeListType::OPCS.to_string(), "OPCS"); + assert_eq!(CodeListType::CTV3.to_string(), "CTV3"); } } diff --git a/rust/codelist-validator-rs/src/ctv3_validator.rs b/rust/codelist-validator-rs/src/ctv3_validator.rs new file mode 100644 index 0000000..280b6f3 --- /dev/null +++ b/rust/codelist-validator-rs/src/ctv3_validator.rs @@ -0,0 +1,227 @@ +use std::sync::LazyLock; + +use codelist_rs::codelist::CodeList; +use regex::Regex; + +use crate::{errors::CodeListValidatorError, validator::CodeValidator}; + +pub struct Ctv3Validator<'a>(pub &'a CodeList); + +static REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"^(?:[a-zA-Z0-9]{5}|[a-zA-Z0-9]{4}\.|[a-zA-Z0-9]{3}\.\.|[a-zA-Z0-9]{2}\.\.\.|[a-zA-Z0-9]{1}\.\.\.\.)$").expect("Unable to create regex") +}); + +impl CodeValidator for Ctv3Validator<'_> { + fn validate_code(&self, code: &str) -> Result<(), CodeListValidatorError> { + if code.len() > 5 { + return Err(CodeListValidatorError::invalid_code_length( + code, + "Code is greater than 5 characters in length", + self.0.codelist_type.to_string(), + )); + } + + if code.len() < 5 { + return Err(CodeListValidatorError::invalid_code_length( + code, + "Code is less than 5 characters in length", + self.0.codelist_type.to_string(), + )); + } + + if !REGEX.is_match(code) { + return Err(CodeListValidatorError::invalid_code_contents( + code, + "Code does not match the expected format", + self.0.codelist_type.to_string(), + )); + } + + Ok(()) + } + + fn validate_all_code(&self) -> Result<(), CodeListValidatorError> { + let mut reasons = Vec::new(); + + for (code, _) in self.0.entries.iter() { + if let Err(err) = self.validate_code(code) { + reasons.push(err.to_string()); + } + } + + if reasons.is_empty() { + Ok(()) + } else { + Err(CodeListValidatorError::invalid_codelist(reasons)) + } + } +} + +#[cfg(test)] +mod tests { + use codelist_rs::{ + codelist::CodeList, + errors::CodeListError, + metadata::{ + categorisation_and_usage::CategorisationAndUsage, metadata_source::Source, + provenance::Provenance, purpose_and_context::PurposeAndContext, + validation_and_review::ValidationAndReview, Metadata, + }, + types::CodeListType, + }; + + use super::*; + use crate::validator::Validator; + + // Helper function to create test metadata + fn create_test_metadata() -> Metadata { + Metadata::new( + Provenance::new(Source::ManuallyCreated, None), + CategorisationAndUsage::new(None, None, None), + PurposeAndContext::new(None, None, None), + ValidationAndReview::new(None, None, None, None, None), + ) + } + + // Helper function to create a test codelist with two entries, default options + // and test metadata + fn create_test_codelist() -> Result { + let codelist = CodeList::new( + "test_codelist".to_string(), + CodeListType::CTV3, + create_test_metadata(), + None, + ); + Ok(codelist) + } + + #[test] + fn test_validate_codelist_with_valid_code() -> Result<(), CodeListError> { + let mut codelist = create_test_codelist()?; + let _ = codelist.add_entry("A9f..".to_string(), None, None); + assert!(codelist.validate_codes().is_ok()); + Ok(()) + } + + #[test] + fn test_validate_code_with_invalid_code_length_too_long() -> Result<(), CodeListError> { + let codelist = create_test_codelist()?; + let validator = Ctv3Validator(&codelist); + let code: &'static str = "A009000000"; + let error = validator.validate_code(code).unwrap_err().to_string(); + assert_eq!(error, "Code A009000000 is an invalid length for type CTV3. Reason: Code is greater than 5 characters in length"); + Ok(()) + } + + #[test] + fn test_validate_code_with_invalid_code_length_too_short() -> Result<(), CodeListError> { + let codelist = create_test_codelist()?; + let validator = Ctv3Validator(&codelist); + let code = "Af."; + let error = validator.validate_code(code).unwrap_err().to_string(); + assert_eq!(error, "Code Af. is an invalid length for type CTV3. Reason: Code is less than 5 characters in length"); + Ok(()) + } + + #[test] + fn test_validate_invalid_code_dot_first_character() -> Result<(), CodeListError> { + let codelist = create_test_codelist()?; + let validator = Ctv3Validator(&codelist); + let code = ".a009"; + let error = validator.validate_code(code).unwrap_err().to_string(); + assert_eq!(error, "Code .a009 contents is invalid for type CTV3. Reason: Code does not match the expected format"); + Ok(()) + } + + #[test] + fn test_validate_invalid_code_dot_middle_character_between_letters() -> Result<(), CodeListError> + { + let codelist = create_test_codelist()?; + let validator = Ctv3Validator(&codelist); + let code = "10a.f"; + let error = validator.validate_code(code).unwrap_err().to_string(); + assert_eq!(error, "Code 10a.f contents is invalid for type CTV3. Reason: Code does not match the expected format"); + Ok(()) + } + + #[test] + fn test_validate_invalid_code_invalid_characters() -> Result<(), CodeListError> { + let codelist = create_test_codelist()?; + let validator = Ctv3Validator(&codelist); + let code = "Af!!!"; + let error = validator.validate_code(code).unwrap_err().to_string(); + assert_eq!(error, "Code Af!!! contents is invalid for type CTV3. Reason: Code does not match the expected format"); + Ok(()) + } + + #[test] + fn test_validate_codelist_with_valid_codes() -> Result<(), CodeListError> { + let mut codelist = create_test_codelist()?; + codelist.add_entry("Af918".to_string(), None, None)?; + codelist.add_entry("ABb..".to_string(), None, None)?; + codelist.add_entry("alkif".to_string(), None, None)?; + codelist.add_entry("F....".to_string(), None, None)?; + codelist.add_entry("bn89.".to_string(), None, None)?; + codelist.add_entry("Me...".to_string(), None, None)?; + codelist.add_entry("99999".to_string(), None, None)?; + codelist.add_entry("kk98.".to_string(), None, None)?; + assert!(codelist.validate_codes().is_ok()); + Ok(()) + } + + #[test] + fn test_validate_codelist_with_all_invalid_codes() -> Result<(), CodeListError> { + let mut codelist = create_test_codelist()?; + codelist.add_entry("A00900000".to_string(), None, None)?; + codelist.add_entry("10".to_string(), None, None)?; + codelist.add_entry("a.9jb".to_string(), None, None)?; + codelist.add_entry("..9jJ".to_string(), None, None)?; + codelist.add_entry("A00A".to_string(), None, None)?; + codelist.add_entry("*unf.".to_string(), None, None)?; + codelist.add_entry("..j..".to_string(), None, None)?; + codelist.add_entry("9874ji".to_string(), None, None)?; + let error = codelist.validate_codes().unwrap_err(); + let error_string = error.to_string(); + + assert!(error_string.contains("Some codes in the list are invalid. Details:")); + assert!(error_string.contains("Code A00900000 is an invalid length for type CTV3. Reason: Code is greater than 5 characters in length")); + assert!(error_string.contains("Code 10 is an invalid length for type CTV3. Reason: Code is less than 5 characters in length")); + assert!(error_string.contains("Code a.9jb contents is invalid for type CTV3. Reason: Code does not match the expected format")); + assert!(error_string.contains("Code ..9jJ contents is invalid for type CTV3. Reason: Code does not match the expected format")); + assert!(error_string.contains("Code A00A is an invalid length for type CTV3. Reason: Code is less than 5 characters in length")); + assert!(error_string.contains("Code *unf. contents is invalid for type CTV3. Reason: Code does not match the expected format")); + assert!(error_string.contains("Code ..j.. contents is invalid for type CTV3. Reason: Code does not match the expected format")); + assert!(error_string.contains("Code 9874ji is an invalid length for type CTV3. Reason: Code is greater than 5 characters in length")); + + assert!( + matches!(error, CodeListValidatorError::InvalidCodelist { reasons } if reasons.len() == 8) + ); + Ok(()) + } + + #[test] + fn test_validate_codelist_with_mixed_invalid_and_valid_codes() -> Result<(), CodeListError> { + let mut codelist = create_test_codelist()?; + codelist.add_entry("A54..".to_string(), None, None)?; + codelist.add_entry("1009.".to_string(), None, None)?; + codelist.add_entry("jk90L".to_string(), None, None)?; + codelist.add_entry("LK...".to_string(), None, None)?; + codelist.add_entry("N40".to_string(), None, None)?; + codelist.add_entry("A00.l".to_string(), None, None)?; + codelist.add_entry("Q90.....".to_string(), None, None)?; + codelist.add_entry("A..9k".to_string(), None, None)?; + let error = codelist.validate_codes().unwrap_err(); + let error_string = error.to_string(); + + assert!(error_string.contains("Some codes in the list are invalid. Details:")); + assert!(error_string.contains("Code N40 is an invalid length for type CTV3. Reason: Code is less than 5 characters in length")); + assert!(error_string.contains("Code A00.l contents is invalid for type CTV3. Reason: Code does not match the expected format")); + assert!(error_string.contains("Code Q90..... is an invalid length for type CTV3. Reason: Code is greater than 5 characters in length")); + assert!(error_string.contains("Code A..9k contents is invalid for type CTV3. Reason: Code does not match the expected format")); + + assert!( + matches!(error, CodeListValidatorError::InvalidCodelist { reasons } if reasons.len() == 4) + ); + Ok(()) + } +} diff --git a/rust/codelist-validator-rs/src/lib.rs b/rust/codelist-validator-rs/src/lib.rs index 24f7bc0..c2ff0cd 100644 --- a/rust/codelist-validator-rs/src/lib.rs +++ b/rust/codelist-validator-rs/src/lib.rs @@ -1,5 +1,6 @@ extern crate core; +pub mod ctv3_validator; pub mod errors; pub mod icd10_validator; pub mod opcs_validator; diff --git a/rust/codelist-validator-rs/src/validator.rs b/rust/codelist-validator-rs/src/validator.rs index 646672d..25a5eb2 100644 --- a/rust/codelist-validator-rs/src/validator.rs +++ b/rust/codelist-validator-rs/src/validator.rs @@ -2,14 +2,14 @@ use codelist_rs::{codelist::CodeList, types::CodeListType}; use crate::{ - errors::CodeListValidatorError, icd10_validator::IcdValidator, opcs_validator::OpcsValidator, - snomed_validator::SnomedValidator, + ctv3_validator::Ctv3Validator, errors::CodeListValidatorError, icd10_validator::IcdValidator, + opcs_validator::OpcsValidator, snomed_validator::SnomedValidator, }; /// Validator trait for validating a codelist. /// -/// `validate_code`: validates a single OPCS code -/// `validate_all_code`: validates all OPCS codes in the codelist +/// `validate_code`: validates a single code +/// `validate_all_code`: validates all codes in the codelist pub(crate) trait CodeValidator { fn validate_code(&self, code: &str) -> Result<(), CodeListValidatorError>; // for 1 code fn validate_all_code(&self) -> Result<(), CodeListValidatorError>; @@ -26,6 +26,7 @@ impl Validator for CodeList { CodeListType::ICD10 => IcdValidator(self).validate_all_code(), CodeListType::SNOMED => SnomedValidator(self).validate_all_code(), CodeListType::OPCS => OpcsValidator(self).validate_all_code(), + CodeListType::CTV3 => Ctv3Validator(self).validate_all_code(), } } } From 098ecd26f66a062b2556f572d33aaf30388aae4e Mon Sep 17 00:00:00 2001 From: Emma <156218556+em-baggie@users.noreply.github.com> Date: Sat, 14 Jun 2025 18:23:57 +0100 Subject: [PATCH 2/8] add validation rules to docs --- .../src/ctv3_validator.rs | 11 ++++++++++ .../src/icd10_validator.rs | 21 +++++++++++++++++++ .../src/opcs_validator.rs | 8 +++++++ .../src/snomed_validator.rs | 7 ++++++- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/rust/codelist-validator-rs/src/ctv3_validator.rs b/rust/codelist-validator-rs/src/ctv3_validator.rs index 280b6f3..6954245 100644 --- a/rust/codelist-validator-rs/src/ctv3_validator.rs +++ b/rust/codelist-validator-rs/src/ctv3_validator.rs @@ -1,3 +1,14 @@ +//! CTV3 validator for validating CTV3 codes in a codelist +//! +//! Validation Rules +//! 1. The code must be exactly 5 characters in length. +//! 2. Only alphanumeric characters (a-z, A-Z, 0-9) and dots (.) are allowed. +//! 3. The code must match one of these patterns: +//! - 5 alphanumeric characters (e.g. "Af918") +//! - 4 alphanumeric characters followed by a dot (e.g. "ABb1.") +//! - 3 alphanumeric characters followed by two dots (e.g. "Me4..") +//! - 2 alphanumeric characters followed by three dots (e.g. "Fb...") +//! - 1 alphanumeric character followed by four dots (e.g. "F....") use std::sync::LazyLock; use codelist_rs::codelist::CodeList; diff --git a/rust/codelist-validator-rs/src/icd10_validator.rs b/rust/codelist-validator-rs/src/icd10_validator.rs index 88666a8..303a0e2 100644 --- a/rust/codelist-validator-rs/src/icd10_validator.rs +++ b/rust/codelist-validator-rs/src/icd10_validator.rs @@ -1,3 +1,13 @@ +//! ICD10 validator for validating ICD10 codes in a codelist +//! +//! Validation Rules +//! 1. The code must be 7 characters or less. +//! 2. The first character must be a letter. +//! 3. The second and third characters must be numbers. +//! 4. The fourth character must be a dot, or a number or X. +//! 5. If the fourth character is a dot, there must be at least 1 number after the dot. +//! 6. If the fourth character is a X, there are no further characters. +//! 7. The fifth to seventh characters must be numbers if present. use std::sync::LazyLock; use codelist_rs::codelist::CodeList; @@ -179,6 +189,17 @@ mod tests { Ok(()) } + #[test] + fn test_validate_invalid_code_lowercase_letter( + ) -> Result<(), CodeListError> { + let codelist = create_test_codelist()?; + let validator = IcdValidator(&codelist); + let code = "a54"; + let error = validator.validate_code(code).unwrap_err().to_string(); + assert_eq!(error, "Code a54 contents is invalid for type ICD10. Reason: Code does not match the expected format"); + Ok(()) + } + #[test] fn test_validate_codelist_with_valid_codes() -> Result<(), CodeListError> { let mut codelist = create_test_codelist()?; diff --git a/rust/codelist-validator-rs/src/opcs_validator.rs b/rust/codelist-validator-rs/src/opcs_validator.rs index 20f4d90..1cec7f3 100644 --- a/rust/codelist-validator-rs/src/opcs_validator.rs +++ b/rust/codelist-validator-rs/src/opcs_validator.rs @@ -1,3 +1,11 @@ +//! OPCS validator for validating OPCS codes in a codelist +//! +//! Validation Rules +//! 1. The code must be 3-5 characters long. +//! 2. The first character must be a letter. +//! 3. The second and third characters must be numbers. +//! 4. If there is a fourth character and it is a dot, there must be a number after the dot. +//! 5. The fifth character, if present, is a number. use std::sync::LazyLock; use codelist_rs::codelist::CodeList; diff --git a/rust/codelist-validator-rs/src/snomed_validator.rs b/rust/codelist-validator-rs/src/snomed_validator.rs index 63f15e4..4cd91e9 100644 --- a/rust/codelist-validator-rs/src/snomed_validator.rs +++ b/rust/codelist-validator-rs/src/snomed_validator.rs @@ -1,8 +1,13 @@ -// SNOMED validator for validating SNOMED codes in a codelist +//! SNOMED validator for validating SNOMED codes in a codelist +//! +//! Validation Rules +//! 1. The code consist of numbers only. +//! 2. The code must be between 6 and 18 numbers in length. use codelist_rs::codelist::CodeList; use crate::{errors::CodeListValidatorError, validator::CodeValidator}; + pub struct SnomedValidator<'a>(pub &'a CodeList); const MAX_LENGTH: u32 = 18; From 4ae7779f5a4570dfa750b13cecdd3a8b084278e3 Mon Sep 17 00:00:00 2001 From: Emma <156218556+em-baggie@users.noreply.github.com> Date: Sat, 14 Jun 2025 18:26:28 +0100 Subject: [PATCH 3/8] formatting --- rust/codelist-validator-rs/src/ctv3_validator.rs | 2 +- rust/codelist-validator-rs/src/icd10_validator.rs | 5 ++--- rust/codelist-validator-rs/src/opcs_validator.rs | 2 +- rust/codelist-validator-rs/src/snomed_validator.rs | 3 +-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/rust/codelist-validator-rs/src/ctv3_validator.rs b/rust/codelist-validator-rs/src/ctv3_validator.rs index 6954245..f1c1e34 100644 --- a/rust/codelist-validator-rs/src/ctv3_validator.rs +++ b/rust/codelist-validator-rs/src/ctv3_validator.rs @@ -1,5 +1,5 @@ //! CTV3 validator for validating CTV3 codes in a codelist -//! +//! //! Validation Rules //! 1. The code must be exactly 5 characters in length. //! 2. Only alphanumeric characters (a-z, A-Z, 0-9) and dots (.) are allowed. diff --git a/rust/codelist-validator-rs/src/icd10_validator.rs b/rust/codelist-validator-rs/src/icd10_validator.rs index 303a0e2..ac4c143 100644 --- a/rust/codelist-validator-rs/src/icd10_validator.rs +++ b/rust/codelist-validator-rs/src/icd10_validator.rs @@ -1,5 +1,5 @@ //! ICD10 validator for validating ICD10 codes in a codelist -//! +//! //! Validation Rules //! 1. The code must be 7 characters or less. //! 2. The first character must be a letter. @@ -190,8 +190,7 @@ mod tests { } #[test] - fn test_validate_invalid_code_lowercase_letter( - ) -> Result<(), CodeListError> { + fn test_validate_invalid_code_lowercase_letter() -> Result<(), CodeListError> { let codelist = create_test_codelist()?; let validator = IcdValidator(&codelist); let code = "a54"; diff --git a/rust/codelist-validator-rs/src/opcs_validator.rs b/rust/codelist-validator-rs/src/opcs_validator.rs index 1cec7f3..01acd65 100644 --- a/rust/codelist-validator-rs/src/opcs_validator.rs +++ b/rust/codelist-validator-rs/src/opcs_validator.rs @@ -1,5 +1,5 @@ //! OPCS validator for validating OPCS codes in a codelist -//! +//! //! Validation Rules //! 1. The code must be 3-5 characters long. //! 2. The first character must be a letter. diff --git a/rust/codelist-validator-rs/src/snomed_validator.rs b/rust/codelist-validator-rs/src/snomed_validator.rs index 4cd91e9..782e481 100644 --- a/rust/codelist-validator-rs/src/snomed_validator.rs +++ b/rust/codelist-validator-rs/src/snomed_validator.rs @@ -1,5 +1,5 @@ //! SNOMED validator for validating SNOMED codes in a codelist -//! +//! //! Validation Rules //! 1. The code consist of numbers only. //! 2. The code must be between 6 and 18 numbers in length. @@ -7,7 +7,6 @@ use codelist_rs::codelist::CodeList; use crate::{errors::CodeListValidatorError, validator::CodeValidator}; - pub struct SnomedValidator<'a>(pub &'a CodeList); const MAX_LENGTH: u32 = 18; From ae1f191e57df9bb75661699abf934ed22457a54d Mon Sep 17 00:00:00 2001 From: Emma <156218556+em-baggie@users.noreply.github.com> Date: Sat, 14 Jun 2025 20:51:52 +0100 Subject: [PATCH 4/8] edit docs and regex --- rust/codelist-validator-rs/src/ctv3_validator.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/rust/codelist-validator-rs/src/ctv3_validator.rs b/rust/codelist-validator-rs/src/ctv3_validator.rs index f1c1e34..ced6234 100644 --- a/rust/codelist-validator-rs/src/ctv3_validator.rs +++ b/rust/codelist-validator-rs/src/ctv3_validator.rs @@ -3,12 +3,7 @@ //! Validation Rules //! 1. The code must be exactly 5 characters in length. //! 2. Only alphanumeric characters (a-z, A-Z, 0-9) and dots (.) are allowed. -//! 3. The code must match one of these patterns: -//! - 5 alphanumeric characters (e.g. "Af918") -//! - 4 alphanumeric characters followed by a dot (e.g. "ABb1.") -//! - 3 alphanumeric characters followed by two dots (e.g. "Me4..") -//! - 2 alphanumeric characters followed by three dots (e.g. "Fb...") -//! - 1 alphanumeric character followed by four dots (e.g. "F....") +//! 3. The code starts with 0-5 alphanumeric characters followed by dots to pad to 5 characters. use std::sync::LazyLock; use codelist_rs::codelist::CodeList; @@ -19,7 +14,7 @@ use crate::{errors::CodeListValidatorError, validator::CodeValidator}; pub struct Ctv3Validator<'a>(pub &'a CodeList); static REGEX: LazyLock = LazyLock::new(|| { - Regex::new(r"^(?:[a-zA-Z0-9]{5}|[a-zA-Z0-9]{4}\.|[a-zA-Z0-9]{3}\.\.|[a-zA-Z0-9]{2}\.\.\.|[a-zA-Z0-9]{1}\.\.\.\.)$").expect("Unable to create regex") + Regex::new(r"^(?:[a-zA-Z0-9]{5}|[a-zA-Z0-9]{4}\.|[a-zA-Z0-9]{3}\.\.|[a-zA-Z0-9]{2}\.\.\.|[a-zA-Z0-9]{1}\.\.\.\.|\.{5})$").expect("Unable to create regex") }); impl CodeValidator for Ctv3Validator<'_> { @@ -175,7 +170,7 @@ mod tests { codelist.add_entry("bn89.".to_string(), None, None)?; codelist.add_entry("Me...".to_string(), None, None)?; codelist.add_entry("99999".to_string(), None, None)?; - codelist.add_entry("kk98.".to_string(), None, None)?; + codelist.add_entry(".....".to_string(), None, None)?; assert!(codelist.validate_codes().is_ok()); Ok(()) } From e577d5baf4342ad0880ea11b112a052335e72fd9 Mon Sep 17 00:00:00 2001 From: Emma <156218556+em-baggie@users.noreply.github.com> Date: Sun, 15 Jun 2025 21:35:43 +0100 Subject: [PATCH 5/8] fix test_from_str_case_insensitive --- rust/codelist-rs/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/codelist-rs/src/types.rs b/rust/codelist-rs/src/types.rs index ba0ca05..c69a298 100644 --- a/rust/codelist-rs/src/types.rs +++ b/rust/codelist-rs/src/types.rs @@ -98,7 +98,7 @@ mod tests { assert!(matches!(CodeListType::from_str("ICD10"), Ok(CodeListType::ICD10))); assert!(matches!(CodeListType::from_str("SNOMED"), Ok(CodeListType::SNOMED))); assert!(matches!(CodeListType::from_str("OPCS"), Ok(CodeListType::OPCS))); - assert!(matches!(CodeListType::from_str("ctv3"), Ok(CodeListType::CTV3))); + assert!(matches!(CodeListType::from_str("CTV3"), Ok(CodeListType::CTV3))); } #[test] From fc7a98f6d16b019d2f400516c4ecee41d4b1ad10 Mon Sep 17 00:00:00 2001 From: Emma <156218556+em-baggie@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:39:41 +0100 Subject: [PATCH 6/8] remove create_test_metadata() and replace with default --- rust/codelist-rs/src/codelist_factory.rs | 14 ++------------ rust/codelist-validator-rs/src/ctv3_validator.rs | 12 +----------- rust/codelist-validator-rs/src/icd10_validator.rs | 12 +----------- rust/codelist-validator-rs/src/opcs_validator.rs | 12 +----------- rust/codelist-validator-rs/src/snomed_validator.rs | 12 +----------- 5 files changed, 6 insertions(+), 56 deletions(-) diff --git a/rust/codelist-rs/src/codelist_factory.rs b/rust/codelist-rs/src/codelist_factory.rs index 270cab9..93eceb1 100644 --- a/rust/codelist-rs/src/codelist_factory.rs +++ b/rust/codelist-rs/src/codelist_factory.rs @@ -423,18 +423,8 @@ mod tests { validation_and_review::ValidationAndReview, }; - // Helper function to create test metadata - fn create_test_metadata() -> Metadata { - Metadata { - provenance: Provenance::new(Source::ManuallyCreated, None), - categorisation_and_usage: CategorisationAndUsage::new(None, None, None), - purpose_and_context: PurposeAndContext::new(None, None, None), - validation_and_review: ValidationAndReview::new(None, None, None, None, None), - } - } - fn create_test_codelist_factory() -> CodeListFactory { - let metadata = create_test_metadata(); + let metadata = Metadata::default(); let codelist_type = CodeListType::ICD10; let codelist_options = CodeListOptions::default(); CodeListFactory::new(codelist_options, metadata, codelist_type) @@ -459,7 +449,7 @@ mod tests { #[test] fn test_new_codelist_factory() { - let metadata = create_test_metadata(); + let metadata = Metadata::default(); let codelist_type = CodeListType::ICD10; let codelist_options = CodeListOptions::default(); let metadata_clone = metadata.clone(); // Clone before moving diff --git a/rust/codelist-validator-rs/src/ctv3_validator.rs b/rust/codelist-validator-rs/src/ctv3_validator.rs index ced6234..e21c45a 100644 --- a/rust/codelist-validator-rs/src/ctv3_validator.rs +++ b/rust/codelist-validator-rs/src/ctv3_validator.rs @@ -79,23 +79,13 @@ mod tests { use super::*; use crate::validator::Validator; - // Helper function to create test metadata - fn create_test_metadata() -> Metadata { - Metadata::new( - Provenance::new(Source::ManuallyCreated, None), - CategorisationAndUsage::new(None, None, None), - PurposeAndContext::new(None, None, None), - ValidationAndReview::new(None, None, None, None, None), - ) - } - // Helper function to create a test codelist with two entries, default options // and test metadata fn create_test_codelist() -> Result { let codelist = CodeList::new( "test_codelist".to_string(), CodeListType::CTV3, - create_test_metadata(), + Metadata::default(), None, ); Ok(codelist) diff --git a/rust/codelist-validator-rs/src/icd10_validator.rs b/rust/codelist-validator-rs/src/icd10_validator.rs index ac4c143..5b2b005 100644 --- a/rust/codelist-validator-rs/src/icd10_validator.rs +++ b/rust/codelist-validator-rs/src/icd10_validator.rs @@ -75,23 +75,13 @@ mod tests { use super::*; use crate::validator::Validator; - // Helper function to create test metadata - fn create_test_metadata() -> Metadata { - Metadata::new( - Provenance::new(Source::ManuallyCreated, None), - CategorisationAndUsage::new(None, None, None), - PurposeAndContext::new(None, None, None), - ValidationAndReview::new(None, None, None, None, None), - ) - } - // Helper function to create a test codelist with two entries, default options // and test metadata fn create_test_codelist() -> Result { let codelist = CodeList::new( "test_codelist".to_string(), CodeListType::ICD10, - create_test_metadata(), + Metadata::default(), None, ); Ok(codelist) diff --git a/rust/codelist-validator-rs/src/opcs_validator.rs b/rust/codelist-validator-rs/src/opcs_validator.rs index 01acd65..d848c04 100644 --- a/rust/codelist-validator-rs/src/opcs_validator.rs +++ b/rust/codelist-validator-rs/src/opcs_validator.rs @@ -81,23 +81,13 @@ mod tests { use super::*; use crate::validator::Validator; - // Helper function to create test metadata - fn create_test_metadata() -> Metadata { - Metadata::new( - Provenance::new(Source::ManuallyCreated, None), - CategorisationAndUsage::new(None, None, None), - PurposeAndContext::new(None, None, None), - ValidationAndReview::new(None, None, None, None, None), - ) - } - // Helper function to create a test codelist with two entries, default options // and test metadata fn create_test_codelist() -> Result { let codelist = CodeList::new( "test_codelist".to_string(), CodeListType::OPCS, - create_test_metadata(), + Metadata::default(), None, ); Ok(codelist) diff --git a/rust/codelist-validator-rs/src/snomed_validator.rs b/rust/codelist-validator-rs/src/snomed_validator.rs index 782e481..1be4fe0 100644 --- a/rust/codelist-validator-rs/src/snomed_validator.rs +++ b/rust/codelist-validator-rs/src/snomed_validator.rs @@ -63,23 +63,13 @@ mod tests { use super::*; use crate::validator::Validator; - // Helper function to create test metadata - fn create_test_metadata() -> Metadata { - Metadata::new( - Provenance::new(Source::ManuallyCreated, None), - CategorisationAndUsage::new(None, None, None), - PurposeAndContext::new(None, None, None), - ValidationAndReview::new(None, None, None, None, None), - ) - } - // Helper function to create a test codelist with two entries, default options // and test metadata fn create_test_codelist() -> Result { let codelist = CodeList::new( "test_codelist".to_string(), CodeListType::SNOMED, - create_test_metadata(), + Metadata::default(), None, ); Ok(codelist) From 20e327eda1cad7d9a134140111856d4a4fadfe31 Mon Sep 17 00:00:00 2001 From: Emma <156218556+em-baggie@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:42:56 +0100 Subject: [PATCH 7/8] formatting to make clippy happy --- rust/codelist-rs/src/codelist_factory.rs | 6 +----- rust/codelist-validator-rs/src/ctv3_validator.rs | 6 +----- rust/codelist-validator-rs/src/icd10_validator.rs | 6 +----- rust/codelist-validator-rs/src/opcs_validator.rs | 6 +----- rust/codelist-validator-rs/src/snomed_validator.rs | 6 +----- 5 files changed, 5 insertions(+), 25 deletions(-) diff --git a/rust/codelist-rs/src/codelist_factory.rs b/rust/codelist-rs/src/codelist_factory.rs index 93eceb1..2daeff5 100644 --- a/rust/codelist-rs/src/codelist_factory.rs +++ b/rust/codelist-rs/src/codelist_factory.rs @@ -417,11 +417,7 @@ mod tests { use tempfile::tempdir; use super::*; - use crate::metadata::{ - categorisation_and_usage::CategorisationAndUsage, metadata_source::Source, - provenance::Provenance, purpose_and_context::PurposeAndContext, - validation_and_review::ValidationAndReview, - }; + fn create_test_codelist_factory() -> CodeListFactory { let metadata = Metadata::default(); diff --git a/rust/codelist-validator-rs/src/ctv3_validator.rs b/rust/codelist-validator-rs/src/ctv3_validator.rs index e21c45a..14f6cb1 100644 --- a/rust/codelist-validator-rs/src/ctv3_validator.rs +++ b/rust/codelist-validator-rs/src/ctv3_validator.rs @@ -68,11 +68,7 @@ mod tests { use codelist_rs::{ codelist::CodeList, errors::CodeListError, - metadata::{ - categorisation_and_usage::CategorisationAndUsage, metadata_source::Source, - provenance::Provenance, purpose_and_context::PurposeAndContext, - validation_and_review::ValidationAndReview, Metadata, - }, + metadata::Metadata, types::CodeListType, }; diff --git a/rust/codelist-validator-rs/src/icd10_validator.rs b/rust/codelist-validator-rs/src/icd10_validator.rs index 5b2b005..9f01501 100644 --- a/rust/codelist-validator-rs/src/icd10_validator.rs +++ b/rust/codelist-validator-rs/src/icd10_validator.rs @@ -64,11 +64,7 @@ mod tests { use codelist_rs::{ codelist::CodeList, errors::CodeListError, - metadata::{ - categorisation_and_usage::CategorisationAndUsage, metadata_source::Source, - provenance::Provenance, purpose_and_context::PurposeAndContext, - validation_and_review::ValidationAndReview, Metadata, - }, + metadata::Metadata, types::CodeListType, }; diff --git a/rust/codelist-validator-rs/src/opcs_validator.rs b/rust/codelist-validator-rs/src/opcs_validator.rs index d848c04..a6d46c8 100644 --- a/rust/codelist-validator-rs/src/opcs_validator.rs +++ b/rust/codelist-validator-rs/src/opcs_validator.rs @@ -70,11 +70,7 @@ mod tests { use codelist_rs::{ codelist::CodeList, errors::CodeListError, - metadata::{ - categorisation_and_usage::CategorisationAndUsage, metadata_source::Source, - provenance::Provenance, purpose_and_context::PurposeAndContext, - validation_and_review::ValidationAndReview, Metadata, - }, + metadata::Metadata, types::CodeListType, }; diff --git a/rust/codelist-validator-rs/src/snomed_validator.rs b/rust/codelist-validator-rs/src/snomed_validator.rs index 1be4fe0..c9cfc31 100644 --- a/rust/codelist-validator-rs/src/snomed_validator.rs +++ b/rust/codelist-validator-rs/src/snomed_validator.rs @@ -52,11 +52,7 @@ mod tests { use codelist_rs::{ codelist::CodeList, errors::CodeListError, - metadata::{ - categorisation_and_usage::CategorisationAndUsage, metadata_source::Source, - provenance::Provenance, purpose_and_context::PurposeAndContext, - validation_and_review::ValidationAndReview, Metadata, - }, + metadata::Metadata, types::CodeListType, }; From c6c8d2c5a4e32feaf07cb8bea9ba49ca95fffb78 Mon Sep 17 00:00:00 2001 From: Emma <156218556+em-baggie@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:46:23 +0100 Subject: [PATCH 8/8] more formatting --- rust/codelist-rs/src/codelist_factory.rs | 1 - rust/codelist-validator-rs/src/ctv3_validator.rs | 5 +---- rust/codelist-validator-rs/src/icd10_validator.rs | 5 +---- rust/codelist-validator-rs/src/opcs_validator.rs | 5 +---- rust/codelist-validator-rs/src/snomed_validator.rs | 5 +---- 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/rust/codelist-rs/src/codelist_factory.rs b/rust/codelist-rs/src/codelist_factory.rs index 2daeff5..0d3b978 100644 --- a/rust/codelist-rs/src/codelist_factory.rs +++ b/rust/codelist-rs/src/codelist_factory.rs @@ -417,7 +417,6 @@ mod tests { use tempfile::tempdir; use super::*; - fn create_test_codelist_factory() -> CodeListFactory { let metadata = Metadata::default(); diff --git a/rust/codelist-validator-rs/src/ctv3_validator.rs b/rust/codelist-validator-rs/src/ctv3_validator.rs index 14f6cb1..2d3d943 100644 --- a/rust/codelist-validator-rs/src/ctv3_validator.rs +++ b/rust/codelist-validator-rs/src/ctv3_validator.rs @@ -66,10 +66,7 @@ impl CodeValidator for Ctv3Validator<'_> { #[cfg(test)] mod tests { use codelist_rs::{ - codelist::CodeList, - errors::CodeListError, - metadata::Metadata, - types::CodeListType, + codelist::CodeList, errors::CodeListError, metadata::Metadata, types::CodeListType, }; use super::*; diff --git a/rust/codelist-validator-rs/src/icd10_validator.rs b/rust/codelist-validator-rs/src/icd10_validator.rs index 9f01501..3a9a087 100644 --- a/rust/codelist-validator-rs/src/icd10_validator.rs +++ b/rust/codelist-validator-rs/src/icd10_validator.rs @@ -62,10 +62,7 @@ impl CodeValidator for IcdValidator<'_> { #[cfg(test)] mod tests { use codelist_rs::{ - codelist::CodeList, - errors::CodeListError, - metadata::Metadata, - types::CodeListType, + codelist::CodeList, errors::CodeListError, metadata::Metadata, types::CodeListType, }; use super::*; diff --git a/rust/codelist-validator-rs/src/opcs_validator.rs b/rust/codelist-validator-rs/src/opcs_validator.rs index a6d46c8..20d4ec9 100644 --- a/rust/codelist-validator-rs/src/opcs_validator.rs +++ b/rust/codelist-validator-rs/src/opcs_validator.rs @@ -68,10 +68,7 @@ impl CodeValidator for OpcsValidator<'_> { #[cfg(test)] mod tests { use codelist_rs::{ - codelist::CodeList, - errors::CodeListError, - metadata::Metadata, - types::CodeListType, + codelist::CodeList, errors::CodeListError, metadata::Metadata, types::CodeListType, }; use super::*; diff --git a/rust/codelist-validator-rs/src/snomed_validator.rs b/rust/codelist-validator-rs/src/snomed_validator.rs index c9cfc31..0ba0aa8 100644 --- a/rust/codelist-validator-rs/src/snomed_validator.rs +++ b/rust/codelist-validator-rs/src/snomed_validator.rs @@ -50,10 +50,7 @@ impl CodeValidator for SnomedValidator<'_> { #[cfg(test)] mod tests { use codelist_rs::{ - codelist::CodeList, - errors::CodeListError, - metadata::Metadata, - types::CodeListType, + codelist::CodeList, errors::CodeListError, metadata::Metadata, types::CodeListType, }; use super::*;