use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum ExitTier {
Analysis,
Build,
}
impl ExitTier {
pub const fn exit_status(self) -> u8 {
match self {
ExitTier::Analysis => 2,
ExitTier::Build => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct DwCode {
id: &'static str,
tier: ExitTier,
subject: Subject,
}
impl DwCode {
pub const fn new(id: &'static str, tier: ExitTier) -> DwCode {
DwCode {
id,
tier,
subject: Subject::Campaign,
}
}
pub const fn about_the_engine(self) -> DwCode {
DwCode {
id: self.id,
tier: self.tier,
subject: Subject::Engine,
}
}
pub const fn id(self) -> &'static str {
self.id
}
pub const fn exit_tier(self) -> ExitTier {
self.tier
}
pub const fn subject(self) -> Subject {
self.subject
}
}
impl Serialize for DwCode {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.id)
}
}
impl std::fmt::Display for DwCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.id)
}
}
impl AsRef<str> for DwCode {
fn as_ref(&self) -> &str {
self.id
}
}
impl PartialEq<DwCode> for String {
fn eq(&self, other: &DwCode) -> bool {
self == other.id
}
}
impl PartialEq<String> for DwCode {
fn eq(&self, other: &String) -> bool {
self.id == other
}
}
impl PartialEq<DwCode> for str {
fn eq(&self, other: &DwCode) -> bool {
self == other.id
}
}
impl PartialEq<DwCode> for &str {
fn eq(&self, other: &DwCode) -> bool {
*self == other.id
}
}
impl PartialEq<&str> for DwCode {
fn eq(&self, other: &&str) -> bool {
self.id == *other
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Error,
Warning,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Diagnostic {
pub code: String,
pub severity: Severity,
pub stage: String,
pub path: String,
pub message: String,
#[serde(skip)]
pub subject: Subject,
}
impl Diagnostic {
pub fn error(
code: DwCode,
stage: impl Into<String>,
path: impl Into<String>,
message: impl Into<String>,
) -> Self {
Diagnostic {
code: code.id().to_string(),
severity: Severity::Error,
stage: stage.into(),
path: path.into(),
message: message.into(),
subject: code.subject(),
}
}
pub fn warning(
code: DwCode,
stage: impl Into<String>,
path: impl Into<String>,
message: impl Into<String>,
) -> Self {
Diagnostic {
code: code.id().to_string(),
severity: Severity::Warning,
stage: stage.into(),
path: path.into(),
message: message.into(),
subject: code.subject(),
}
}
#[must_use]
pub fn group(&self) -> Group {
match (self.severity, self.subject) {
(Severity::Error, _) => Group::Refusal,
(Severity::Warning, Subject::Campaign) => Group::AboutTheCampaign,
(Severity::Warning, Subject::Engine) => Group::AboutTheEngine,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize)]
pub enum Subject {
#[default]
Campaign,
Engine,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Group {
Refusal,
AboutTheCampaign,
AboutTheEngine,
}
impl Group {
#[must_use]
pub fn heading(self, n: usize) -> String {
match self {
Group::Refusal => format!("-- {n} refusal(s): these are yours to act on"),
Group::AboutTheCampaign => format!("-- {n} advisory(ies) about this campaign"),
Group::AboutTheEngine => {
format!("-- {n} notice(s) about this engine, true of any campaign")
}
}
}
}
#[doc(hidden)]
pub use linkme as __linkme;
#[linkme::distributed_slice]
pub static DECLARED: [Declared];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct Declared {
pub code: &'static str,
pub tier: Option<ExitTier>,
pub subject: Option<Subject>,
pub name: &'static str,
pub module: &'static str,
}
impl Declared {
#[doc(hidden)]
pub const fn coded(name: &'static str, module: &'static str, code: DwCode) -> Declared {
Declared {
code: code.id,
tier: Some(code.tier),
subject: Some(code.subject),
name,
module,
}
}
#[doc(hidden)]
pub const fn bare(name: &'static str, module: &'static str, code: &'static str) -> Declared {
Declared {
code,
tier: None,
subject: None,
name,
module,
}
}
}
#[must_use]
pub fn declared() -> Vec<Declared> {
let mut all: Vec<Declared> = DECLARED.iter().copied().collect();
all.sort_by(|a, b| (a.code, a.module, a.name).cmp(&(b.code, b.module, b.name)));
all
}
#[macro_export]
macro_rules! dw_code {
(
$(#[$meta:meta])*
$vis:vis const $name:ident : DwCode = DwCode::new($id:literal, ExitTier::$tier:ident)
$(.$chain:ident())? ;
) => {
$(#[$meta])*
$vis const $name: DwCode = DwCode::new($id, ExitTier::$tier)$(.$chain())?;
const _: () = {
#[$crate::diagnostic::__linkme::distributed_slice($crate::diagnostic::DECLARED)]
#[linkme(crate = $crate::diagnostic::__linkme)]
static DECLARED_CODE: $crate::diagnostic::Declared =
$crate::diagnostic::Declared::coded(stringify!($name), module_path!(), $name);
};
};
(
$(#[$meta:meta])*
$vis:vis const $name:ident : &str = $id:literal ;
) => {
$(#[$meta])*
$vis const $name: &str = $id;
const _: () = {
#[$crate::diagnostic::__linkme::distributed_slice($crate::diagnostic::DECLARED)]
#[linkme(crate = $crate::diagnostic::__linkme)]
static DECLARED_CODE: $crate::diagnostic::Declared =
$crate::diagnostic::Declared::bare(stringify!($name), module_path!(), $name);
};
};
}
pub mod codes {
use super::{DwCode, ExitTier};
crate::dw_code! {
pub const SCHEMA: DwCode = DwCode::new("DW0100", ExitTier::Build);
}
crate::dw_code! {
pub const DSL_VERSION: DwCode = DwCode::new("DW0102", ExitTier::Build);
}
crate::dw_code! {
pub const ID_SYNTAX: DwCode = DwCode::new("DW0110", ExitTier::Build);
}
crate::dw_code! {
pub const ID_DUPLICATE: DwCode = DwCode::new("DW0111", ExitTier::Build);
}
crate::dw_code! {
pub const DANGLING_REF: DwCode = DwCode::new("DW0112", ExitTier::Build);
}
crate::dw_code! {
pub const ANCHOR_UNRESOLVED: DwCode = DwCode::new("DW0142", ExitTier::Build);
}
crate::dw_code! {
pub const ITEM_UNKNOWN: DwCode = DwCode::new("DW0143", ExitTier::Build);
}
crate::dw_code! {
pub const LOOT_TOO_MANY_ITEMS: DwCode = DwCode::new("DW0432", ExitTier::Build);
}
crate::dw_code! {
pub const WAVE_UNKNOWN: DwCode = DwCode::new("DW0170", ExitTier::Build);
}
crate::dw_code! {
pub const FLAG_UNKNOWN: DwCode = DwCode::new("DW0172", ExitTier::Build);
}
crate::dw_code! {
pub const EQUIPMENT_UNSHOWN: DwCode = DwCode::new("DW0898", ExitTier::Build);
}
crate::dw_code! {
pub const HEALTH_BAR_STILL: DwCode = DwCode::new("DW0909", ExitTier::Build);
}
crate::dw_code! {
pub const HEALTH_BAR_UNTITLED: DwCode = DwCode::new("DW0910", ExitTier::Build);
}
crate::dw_code! {
pub const HEALTH_BAR_ADVISED: DwCode = DwCode::new("DW0912", ExitTier::Build);
}
crate::dw_code! {
pub const ON_KILL_UNREACHABLE: DwCode = DwCode::new("DW0913", ExitTier::Build);
}
crate::dw_code! {
pub const CELESTIAL_TIME: DwCode = DwCode::new("DW0931", ExitTier::Build);
}
crate::dw_code! {
pub const LETHAL_STAGE_GATE: DwCode = DwCode::new("DW0953", ExitTier::Build);
}
crate::dw_code! {
pub const ENTITY_UNKNOWN: DwCode = DwCode::new("DW0173", ExitTier::Build);
}
crate::dw_code! {
pub const L10N_MISSING: DwCode = DwCode::new("DW0180", ExitTier::Build);
}
crate::dw_code! {
pub const L10N_ORPHAN: DwCode = DwCode::new("DW0181", ExitTier::Build);
}
crate::dw_code! {
pub const MARKER_RESERVED: DwCode = DwCode::new("DW0182", ExitTier::Build);
}
crate::dw_code! {
pub const TR_SIGIL_RESERVED: DwCode = DwCode::new("DW0183", ExitTier::Build);
}
crate::dw_code! {
pub const LANG_CODE_UNMAPPED: DwCode = DwCode::new("DW0184", ExitTier::Build);
}
crate::dw_code! {
pub const CHROME_RESERVED: DwCode = DwCode::new("DW0186", ExitTier::Build);
}
crate::dw_code! {
pub const L10N_STALE: DwCode = DwCode::new("DW0187", ExitTier::Build);
}
crate::dw_code! {
pub const L10N_PROVENANCE_MISSING: DwCode = DwCode::new("DW0188", ExitTier::Build);
}
crate::dw_code! {
pub const SKIN_INVALID: DwCode = DwCode::new("DW0190", ExitTier::Build);
}
crate::dw_code! {
pub const EFFECT_UNKNOWN: DwCode = DwCode::new("DW0192", ExitTier::Build);
}
crate::dw_code! {
pub const BLOCK_UNKNOWN: DwCode = DwCode::new("DW0193", ExitTier::Build);
}
crate::dw_code! {
pub const LIGHTING_RANGE: DwCode = DwCode::new("DW0196", ExitTier::Build);
}
crate::dw_code! {
pub const VIEW_BEYOND_SERVED: DwCode = DwCode::new("DW0956", ExitTier::Build);
}
crate::dw_code! {
pub const STATE_UNDECLARED: DwCode = DwCode::new("DW0500", ExitTier::Build);
}
crate::dw_code! {
pub const LICENSE_REFUSED: DwCode = DwCode::new("DW0741", ExitTier::Build);
}
crate::dw_code! {
pub const TEXTURE_IMAGE: DwCode = DwCode::new("DW0940", ExitTier::Build);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_tier_maps_to_its_published_exit_status() {
assert_eq!(ExitTier::Analysis.exit_status(), 2);
assert_eq!(ExitTier::Build.exit_status(), 3);
}
#[test]
fn a_code_carries_the_tier_it_declares_not_the_one_its_number_spells() {
let analysis_spelt_dw03 = DwCode::new("DW0312", ExitTier::Analysis);
let build_spelt_dw03 = DwCode::new("DW0311", ExitTier::Build);
assert_eq!(analysis_spelt_dw03.exit_tier(), ExitTier::Analysis);
assert_eq!(analysis_spelt_dw03.exit_tier().exit_status(), 2);
assert_eq!(build_spelt_dw03.exit_tier().exit_status(), 3);
}
#[test]
fn marking_a_code_an_engine_notice_keeps_its_tier() {
let engine_notice = DwCode::new("DW0813", ExitTier::Analysis).about_the_engine();
assert_eq!(engine_notice.subject(), Subject::Engine);
assert_eq!(engine_notice.exit_tier(), ExitTier::Analysis);
assert_eq!(engine_notice.id(), "DW0813");
}
}