use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutcomeClass {
Success,
Error,
}
macro_rules! outcome_kinds {
($( $(#[$doc:meta])* ($variant:ident, $token:literal, $class:ident) ),+ $(,)?) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum OutcomeKind {
$( $(#[$doc])* #[serde(rename = $token)] $variant, )+
}
impl OutcomeKind {
pub const ALL: &[OutcomeKind] = &[ $(OutcomeKind::$variant,)+ ];
#[must_use]
pub fn token(self) -> &'static str {
match self { $( OutcomeKind::$variant => $token, )+ }
}
#[must_use]
pub fn class(self) -> OutcomeClass {
match self { $( OutcomeKind::$variant => OutcomeClass::$class, )+ }
}
#[must_use]
pub fn from_token(token: &str) -> Option<Self> {
match token { $( $token => Some(OutcomeKind::$variant), )+ _ => None }
}
}
};
}
outcome_kinds! {
(Created, "created", Success),
(Ok, "ok", Success),
(OkEmpty, "ok_empty", Success),
(Updated, "updated", Success),
(Deleted, "deleted", Success),
(Stored, "stored", Success),
(Accepted, "accepted", Success),
(AlreadyExists, "already_exists", Error),
(NotFound, "not_found", Error),
(VersionNotFound, "version_not_found", Error),
(PreconditionFailed, "precondition_failed", Error),
(PreconditionMissing, "precondition_missing", Error),
(ValidationFailed, "validation_failed", Error),
(TemplateNotFound, "template_not_found", Error),
(TemplateMismatch, "template_mismatch", Error),
(MissingTemplateId, "missing_template_id", Error),
(AlreadyDeleted, "already_deleted", Error),
(Conflict, "conflict", Error),
(NotAcceptable, "not_acceptable", Error),
(UnsupportedMedia, "unsupported_media", Error),
(InvalidQuery, "invalid_query", Error),
(Timeout, "timeout", Error),
(Unauthenticated, "unauthenticated", Error),
(Forbidden, "forbidden", Error),
(BadRequest, "bad_request", Error),
(MethodNotAllowed, "method_not_allowed", Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CaseKind {
Functional,
Content,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CaseStatus {
#[default]
Active,
Retired,
Draft,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Component {
Ehr,
EhrComposition,
EhrContribution,
EhrDirectory,
#[serde(rename = "DEFINITION_ADL14")]
DefinitionAdl14,
#[serde(rename = "DEFINITION_ADL2")]
DefinitionAdl2,
DefinitionQuery,
Query,
Demographic,
Admin,
Messaging,
Content,
SimplifiedFormats,
Security,
Performance,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Family {
Platform,
Enterprise,
Security,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Tier {
#[serde(rename = "CORE")]
Core,
#[serde(rename = "STANDARD")]
Standard,
#[serde(rename = "OPTIONS")]
Options,
#[serde(rename = "SEC-BASIC")]
SecBasic,
#[serde(rename = "D")]
EnterpriseD,
#[serde(rename = "M")]
EnterpriseM,
#[serde(rename = "X")]
EnterpriseX,
}
impl Tier {
#[must_use]
pub fn family(self) -> Family {
match self {
Tier::Core | Tier::Standard | Tier::Options => Family::Platform,
Tier::SecBasic => Family::Security,
Tier::EnterpriseD | Tier::EnterpriseM | Tier::EnterpriseX => Family::Enterprise,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Disposition {
LooseAssert,
FixedHandling,
OptionSelect,
ReportOnly,
StatementDeclared,
Editorial,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum FormatName {
#[serde(rename = "canonical-json")]
CanonicalJson,
#[serde(rename = "canonical-xml")]
CanonicalXml,
#[serde(rename = "wt-flat")]
WtFlat,
#[serde(rename = "wt-structured")]
WtStructured,
#[serde(rename = "wt")]
Wt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CorpusFormat {
#[serde(rename = "canonical-json")]
CanonicalJson,
#[serde(rename = "canonical-xml")]
CanonicalXml,
#[serde(rename = "wt-flat")]
WtFlat,
#[serde(rename = "wt-structured")]
WtStructured,
#[serde(rename = "opt-xml")]
OptXml,
#[serde(rename = "aql-text")]
AqlText,
#[serde(rename = "adl2-text")]
Adl2Text,
#[serde(rename = "adl14-text")]
Adl14Text,
#[serde(rename = "raw-json")]
RawJson,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
Head,
Options,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Iteration {
ResetPerRow,
SinglePass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ServerState {
Empty,
Exclusive,
Any,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FixtureVerdict {
Valid,
Invalid,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PlaceholderPolicy {
#[serde(rename = "runtime-random")]
RuntimeRandom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum XmlNamespace {
#[serde(rename = "openehr-published")]
Published,
#[serde(rename = "openehr-v1")]
V1,
#[serde(rename = "openehr-v2")]
V2,
}
impl XmlNamespace {
#[must_use]
pub const fn token(self) -> &'static str {
match self {
Self::Published => "openehr-published",
Self::V1 => "openehr-v1",
Self::V2 => "openehr-v2",
}
}
#[must_use]
pub fn accepts(self, uri: &str) -> bool {
match self {
Self::Published => uri == Self::V1_URI || uri == Self::V2_URI,
Self::V1 => uri == Self::V1_URI,
Self::V2 => uri == Self::V2_URI,
}
}
pub const V1_URI: &str = "http://schemas.openehr.org/v1";
pub const V2_URI: &str = "http://schemas.openehr.org/v2";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IgnoreSetName {
ServerAssigned,
CtxDefaults,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SpecComponent {
Rm,
Base,
Am,
Aql,
ItsRest,
Term,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum ChangeType {
Create,
Modify,
Deleted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ResultSetMatch {
Ordered,
Set,
Count,
Contains,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CellComparison {
#[default]
Lexeme,
Instant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ItsName {
#[serde(rename = "its-rest")]
ItsRest,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MemberVersionType {
#[serde(rename = "UPDATE_VERSION")]
Update,
#[serde(rename = "ORIGINAL_VERSION")]
Original,
#[serde(rename = "IMPORTED_VERSION")]
Imported,
}
impl MemberVersionType {
pub const ALL: &[MemberVersionType] = &[
MemberVersionType::Update,
MemberVersionType::Original,
MemberVersionType::Imported,
];
#[must_use]
pub const fn token(self) -> &'static str {
match self {
Self::Update => "UPDATE_VERSION",
Self::Original => "ORIGINAL_VERSION",
Self::Imported => "IMPORTED_VERSION",
}
}
#[must_use]
pub fn from_token(token: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|t| t.token() == token)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MemberChangeType {
#[serde(rename = "creation")]
Creation,
#[serde(rename = "amendment")]
Amendment,
#[serde(rename = "modification")]
Modification,
#[serde(rename = "synthesis")]
Synthesis,
#[serde(rename = "unknown")]
Unknown,
#[serde(rename = "deleted")]
Deleted,
#[serde(rename = "attestation")]
Attestation,
#[serde(rename = "restoration")]
Restoration,
#[serde(rename = "format conversion")]
FormatConversion,
}
impl MemberChangeType {
pub const ALL: &[MemberChangeType] = &[
MemberChangeType::Creation,
MemberChangeType::Amendment,
MemberChangeType::Modification,
MemberChangeType::Synthesis,
MemberChangeType::Deleted,
MemberChangeType::Attestation,
MemberChangeType::Restoration,
MemberChangeType::FormatConversion,
MemberChangeType::Unknown,
];
#[must_use]
pub const fn token(self) -> &'static str {
match self {
Self::Creation => "creation",
Self::Amendment => "amendment",
Self::Modification => "modification",
Self::Synthesis => "synthesis",
Self::Unknown => "unknown",
Self::Deleted => "deleted",
Self::Attestation => "attestation",
Self::Restoration => "restoration",
Self::FormatConversion => "format conversion",
}
}
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::Creation => "249",
Self::Amendment => "250",
Self::Modification => "251",
Self::Synthesis => "252",
Self::Unknown => "253",
Self::Deleted => "523",
Self::Attestation => "666",
Self::Restoration => "816",
Self::FormatConversion => "817",
}
}
#[must_use]
pub fn from_token(token: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|c| c.token() == token)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum VersionLifecycleState {
Complete,
Incomplete,
Deleted,
Inactive,
Abandoned,
}
impl VersionLifecycleState {
pub const ALL: &[VersionLifecycleState] = &[
VersionLifecycleState::Complete,
VersionLifecycleState::Incomplete,
VersionLifecycleState::Deleted,
VersionLifecycleState::Inactive,
VersionLifecycleState::Abandoned,
];
#[must_use]
pub const fn token(self) -> &'static str {
match self {
Self::Complete => "complete",
Self::Incomplete => "incomplete",
Self::Deleted => "deleted",
Self::Inactive => "inactive",
Self::Abandoned => "abandoned",
}
}
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::Complete => "532",
Self::Incomplete => "553",
Self::Deleted => "523",
Self::Inactive => "800",
Self::Abandoned => "801",
}
}
#[must_use]
pub fn from_token(token: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|s| s.token() == token)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum XVersionedClass {
#[serde(rename = "X_VERSIONED_COMPOSITION")]
Composition,
#[serde(rename = "X_VERSIONED_EHR_STATUS")]
EhrStatus,
#[serde(rename = "X_VERSIONED_EHR_ACCESS")]
EhrAccess,
#[serde(rename = "X_VERSIONED_FOLDER")]
Folder,
#[serde(rename = "X_VERSIONED_PARTY")]
Party,
}
impl XVersionedClass {
pub const ALL: &[XVersionedClass] = &[
XVersionedClass::Composition,
XVersionedClass::EhrStatus,
XVersionedClass::EhrAccess,
XVersionedClass::Folder,
XVersionedClass::Party,
];
#[must_use]
pub const fn token(self) -> &'static str {
match self {
Self::Composition => "X_VERSIONED_COMPOSITION",
Self::EhrStatus => "X_VERSIONED_EHR_STATUS",
Self::EhrAccess => "X_VERSIONED_EHR_ACCESS",
Self::Folder => "X_VERSIONED_FOLDER",
Self::Party => "X_VERSIONED_PARTY",
}
}
#[must_use]
pub fn from_token(token: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|c| c.token() == token)
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::*;
#[test]
fn outcome_tokens_round_trip() {
for kind in OutcomeKind::ALL {
assert_eq!(OutcomeKind::from_token(kind.token()), Some(*kind));
}
assert_eq!(OutcomeKind::ALL.len(), 26);
assert_eq!(OutcomeKind::Created.class(), OutcomeClass::Success);
assert_eq!(OutcomeKind::Timeout.class(), OutcomeClass::Error);
assert_eq!(OutcomeKind::Accepted.class(), OutcomeClass::Success);
assert_eq!(OutcomeKind::BadRequest.class(), OutcomeClass::Error);
assert_eq!(OutcomeKind::MethodNotAllowed.class(), OutcomeClass::Error);
for token in ["accepted", "bad_request", "method_not_allowed"] {
assert!(
OutcomeKind::from_token(token).is_some(),
"{token} must parse"
);
}
}
#[test]
fn tier_families() {
assert_eq!(Tier::Core.family(), Family::Platform);
assert_eq!(Tier::SecBasic.family(), Family::Security);
assert_eq!(Tier::EnterpriseD.family(), Family::Enterprise);
}
#[test]
fn serde_tokens() {
let kind: OutcomeKind =
serde_json::from_value(serde_json::json!("already_exists")).unwrap();
assert_eq!(kind, OutcomeKind::AlreadyExists);
let tier: Tier = serde_json::from_value(serde_json::json!("SEC-BASIC")).unwrap();
assert_eq!(tier, Tier::SecBasic);
let comp: Component =
serde_json::from_value(serde_json::json!("DEFINITION_ADL14")).unwrap();
assert_eq!(comp, Component::DefinitionAdl14);
let fmt: FormatName = serde_json::from_value(serde_json::json!("wt-flat")).unwrap();
assert_eq!(fmt, FormatName::WtFlat);
}
#[test]
fn every_audit_change_type_carries_its_own_code_and_round_trips() {
let tokens: BTreeSet<&str> = MemberChangeType::ALL.iter().map(|c| c.token()).collect();
let codes: BTreeSet<&str> = MemberChangeType::ALL.iter().map(|c| c.code()).collect();
assert_eq!(tokens.len(), MemberChangeType::ALL.len());
assert_eq!(codes.len(), MemberChangeType::ALL.len());
for change in MemberChangeType::ALL {
assert_eq!(MemberChangeType::from_token(change.token()), Some(*change));
}
assert_eq!(MemberChangeType::Creation.code(), "249");
assert_eq!(
MemberChangeType::FormatConversion.token(),
"format conversion"
);
}
#[test]
fn every_version_lifecycle_state_carries_its_own_code_and_round_trips() {
let codes: BTreeSet<&str> = VersionLifecycleState::ALL
.iter()
.map(|s| s.code())
.collect();
assert_eq!(codes.len(), VersionLifecycleState::ALL.len());
for state in VersionLifecycleState::ALL {
assert_eq!(
VersionLifecycleState::from_token(state.token()),
Some(*state)
);
}
assert_eq!(VersionLifecycleState::Complete.code(), "532");
}
#[test]
fn every_x_versioned_class_round_trips_its_rm_class_name() {
for class in XVersionedClass::ALL {
assert_eq!(XVersionedClass::from_token(class.token()), Some(*class));
assert!(
class.token().starts_with("X_VERSIONED_"),
"{}",
class.token()
);
}
assert_eq!(XVersionedClass::from_token("X_VERSIONED_NOTHING"), None);
}
#[test]
fn every_spec_component_carries_a_distinct_authored_key() {
let tokens: Vec<&str> = SpecComponent::ALL.iter().map(|c| c.token()).collect();
assert_eq!(tokens, ["rm", "base", "am", "aql", "its_rest", "term"]);
}
#[test]
fn every_xml_namespace_expectation_renders_its_token_and_scopes_its_uris() {
assert_eq!(XmlNamespace::Published.token(), "openehr-published");
assert_eq!(XmlNamespace::V1.token(), "openehr-v1");
assert_eq!(XmlNamespace::V2.token(), "openehr-v2");
assert!(XmlNamespace::Published.accepts(XmlNamespace::V1_URI));
assert!(XmlNamespace::Published.accepts(XmlNamespace::V2_URI));
assert!(XmlNamespace::V1.accepts(XmlNamespace::V1_URI));
assert!(!XmlNamespace::V1.accepts(XmlNamespace::V2_URI));
assert!(XmlNamespace::V2.accepts(XmlNamespace::V2_URI));
assert!(!XmlNamespace::V2.accepts(XmlNamespace::V1_URI));
}
}
impl CaseKind {
pub const ALL: &[CaseKind] = &[CaseKind::Functional, CaseKind::Content];
}
impl CaseStatus {
pub const ALL: &[CaseStatus] = &[CaseStatus::Active, CaseStatus::Retired, CaseStatus::Draft];
}
impl Component {
pub const ALL: &[Component] = &[
Component::Ehr,
Component::EhrComposition,
Component::EhrContribution,
Component::EhrDirectory,
Component::DefinitionAdl14,
Component::DefinitionAdl2,
Component::DefinitionQuery,
Component::Query,
Component::Demographic,
Component::Admin,
Component::Messaging,
Component::Content,
Component::SimplifiedFormats,
Component::Security,
Component::Performance,
];
}
impl Family {
pub const ALL: &[Family] = &[Family::Platform, Family::Enterprise, Family::Security];
}
impl Tier {
pub const ALL: &[Tier] = &[
Tier::Core,
Tier::Standard,
Tier::Options,
Tier::SecBasic,
Tier::EnterpriseD,
Tier::EnterpriseM,
Tier::EnterpriseX,
];
}
impl Disposition {
pub const ALL: &[Disposition] = &[
Disposition::LooseAssert,
Disposition::FixedHandling,
Disposition::OptionSelect,
Disposition::ReportOnly,
Disposition::StatementDeclared,
Disposition::Editorial,
];
}
impl FormatName {
pub const ALL: &[FormatName] = &[
FormatName::CanonicalJson,
FormatName::CanonicalXml,
FormatName::WtFlat,
FormatName::WtStructured,
FormatName::Wt,
];
#[must_use]
pub const fn token(self) -> &'static str {
match self {
FormatName::CanonicalJson => "canonical-json",
FormatName::CanonicalXml => "canonical-xml",
FormatName::WtFlat => "wt-flat",
FormatName::WtStructured => "wt-structured",
FormatName::Wt => "wt",
}
}
}
impl CorpusFormat {
pub const ALL: &[CorpusFormat] = &[
CorpusFormat::CanonicalJson,
CorpusFormat::CanonicalXml,
CorpusFormat::WtFlat,
CorpusFormat::WtStructured,
CorpusFormat::OptXml,
CorpusFormat::AqlText,
CorpusFormat::Adl2Text,
CorpusFormat::Adl14Text,
CorpusFormat::RawJson,
];
}
impl HttpMethod {
pub const ALL: &[HttpMethod] = &[
HttpMethod::Get,
HttpMethod::Post,
HttpMethod::Put,
HttpMethod::Delete,
HttpMethod::Head,
HttpMethod::Options,
];
}
impl Iteration {
pub const ALL: &[Iteration] = &[Iteration::ResetPerRow, Iteration::SinglePass];
}
impl ServerState {
pub const ALL: &[ServerState] = &[ServerState::Empty, ServerState::Exclusive, ServerState::Any];
}
impl FixtureVerdict {
pub const ALL: &[FixtureVerdict] = &[FixtureVerdict::Valid, FixtureVerdict::Invalid];
}
impl PlaceholderPolicy {
pub const ALL: &[PlaceholderPolicy] = &[PlaceholderPolicy::RuntimeRandom];
}
impl SpecComponent {
pub const ALL: &[SpecComponent] = &[
SpecComponent::Rm,
SpecComponent::Base,
SpecComponent::Am,
SpecComponent::Aql,
SpecComponent::ItsRest,
SpecComponent::Term,
];
#[must_use]
pub fn token(self) -> &'static str {
match self {
SpecComponent::Rm => "rm",
SpecComponent::Base => "base",
SpecComponent::Am => "am",
SpecComponent::Aql => "aql",
SpecComponent::ItsRest => "its_rest",
SpecComponent::Term => "term",
}
}
}
impl ChangeType {
pub const ALL: &[ChangeType] = &[ChangeType::Create, ChangeType::Modify, ChangeType::Deleted];
}
impl ResultSetMatch {
pub const ALL: &[ResultSetMatch] = &[
ResultSetMatch::Ordered,
ResultSetMatch::Set,
ResultSetMatch::Count,
ResultSetMatch::Contains,
];
}
impl CellComparison {
pub const ALL: &[CellComparison] = &[CellComparison::Lexeme, CellComparison::Instant];
}
impl ItsName {
pub const ALL: &[ItsName] = &[ItsName::ItsRest];
}
#[cfg(test)]
mod all_consts_tests {
use super::*;
#[test]
fn all_lists_are_exhaustive() {
assert_eq!(CaseKind::ALL.len(), 2);
assert_eq!(Component::ALL.len(), 15);
assert_eq!(Tier::ALL.len(), 7);
assert_eq!(Disposition::ALL.len(), 6);
assert_eq!(FormatName::ALL.len(), 5);
assert_eq!(CorpusFormat::ALL.len(), 9);
}
}