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)]
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")
}
}
}
}
pub mod codes {
use super::{DwCode, ExitTier};
pub const SCHEMA: DwCode = DwCode::new("DW0100", ExitTier::Build);
pub const STAGE_MISMATCH: DwCode = DwCode::new("DW0101", ExitTier::Build);
pub const DSL_VERSION: DwCode = DwCode::new("DW0102", ExitTier::Build);
pub const CAMPAIGN_ID_MISMATCH: DwCode = DwCode::new("DW0103", ExitTier::Build);
pub const ID_SYNTAX: DwCode = DwCode::new("DW0110", ExitTier::Build);
pub const ID_DUPLICATE: DwCode = DwCode::new("DW0111", ExitTier::Build);
pub const DANGLING_REF: DwCode = DwCode::new("DW0112", ExitTier::Build);
pub const DIALOGUE_UNREACHABLE: DwCode = DwCode::new("DW0120", ExitTier::Build);
pub const DIALOGUE_BAD_REF: DwCode = DwCode::new("DW0121", ExitTier::Build);
pub const DIALOGUE_BAD_OBJECTIVE: DwCode = DwCode::new("DW0122", ExitTier::Build);
pub const DIALOGUE_UNCOVERED: DwCode = DwCode::new("DW0123", ExitTier::Build);
pub const PLAN_CYCLE: DwCode = DwCode::new("DW0130", ExitTier::Build);
pub const FINALE_UNKNOWN: DwCode = DwCode::new("DW0131", ExitTier::Build);
pub const PLAN_NOT_CONVERGENT: DwCode = DwCode::new("DW0132", ExitTier::Build);
pub const OPTIONAL_ON_SPINE: DwCode = DwCode::new("DW0866", ExitTier::Build);
pub const MANDATORY_ON_OPTIONAL: DwCode = DwCode::new("DW0867", ExitTier::Build);
pub const MAINLINE_KEY_OPTIONAL: DwCode = DwCode::new("DW0868", ExitTier::Build);
pub const AFTER_CYCLE: DwCode = DwCode::new("DW0140", ExitTier::Build);
pub const ANCHOR_UNRESOLVED: DwCode = DwCode::new("DW0142", ExitTier::Build);
pub const ITEM_UNKNOWN: DwCode = DwCode::new("DW0143", ExitTier::Build);
pub const ENCHANTMENT_UNKNOWN: DwCode = DwCode::new("DW0433", ExitTier::Build);
pub const ENCHANTMENT_LEVEL: DwCode = DwCode::new("DW0434", ExitTier::Build);
pub const LOOT_DUPLICATE_ANCHOR: DwCode = DwCode::new("DW0435", ExitTier::Build);
pub const LOOT_TOO_MANY_ITEMS: DwCode = DwCode::new("DW0432", ExitTier::Build);
pub const ITEM_COUNT_OVER_STACK: DwCode = DwCode::new("DW0436", ExitTier::Build);
pub const MISSING_ITEM_HINT_WITHOUT_ITEM: DwCode = DwCode::new("DW0437", ExitTier::Build);
pub const QUEST_NOT_EXPANDED: DwCode = DwCode::new("DW0150", ExitTier::Build);
pub const QUEST_NOT_PLANNED: DwCode = DwCode::new("DW0151", ExitTier::Build);
pub const NPC_WITHOUT_TREE: DwCode = DwCode::new("DW0152", ExitTier::Build);
pub const TREE_WITHOUT_NPC: DwCode = DwCode::new("DW0153", ExitTier::Build);
pub const PREFAB_BINDING: DwCode = DwCode::new("DW0160", ExitTier::Build);
pub const POOL_UNKNOWN: DwCode = DwCode::new("DW0161", ExitTier::Build);
pub const PREFAB_UNKNOWN: DwCode = DwCode::new("DW0856", ExitTier::Build);
pub const EDIT_INVALID: DwCode = DwCode::new("DW0162", ExitTier::Build);
pub const WAVE_UNKNOWN: DwCode = DwCode::new("DW0170", ExitTier::Build);
pub const WAVE_NEVER_SPAWNED: DwCode = DwCode::new("DW0171", ExitTier::Build);
pub const FLAG_UNKNOWN: DwCode = DwCode::new("DW0172", ExitTier::Build);
pub const REST_RESEAT_NO_BONFIRE: DwCode = DwCode::new("DW0370", ExitTier::Build);
pub const BONFIRE_NO_FLASK: DwCode = DwCode::new("DW0476", ExitTier::Build);
pub const ITEM_GATE_UNBRINGABLE: DwCode = DwCode::new("DW0849", ExitTier::Build);
pub const KIT_POTION_INVALID: DwCode = DwCode::new("DW0486", ExitTier::Build);
pub const KIT_POTION_MISSING: DwCode = DwCode::new("DW0487", ExitTier::Build);
pub const DROP_SLOT_UNFILLED: DwCode = DwCode::new("DW0490", ExitTier::Build);
pub const DROP_NOT_TIERED: DwCode = DwCode::new("DW0491", ExitTier::Build);
pub const DROP_COLLECT_UNSOURCED: DwCode = DwCode::new("DW0492", ExitTier::Build);
pub const DROP_COLLECT_UNORDERED: DwCode = DwCode::new("DW0493", ExitTier::Build);
pub const LETHAL_MESSAGE_BLANK: DwCode = DwCode::new("DW0512", ExitTier::Build);
pub const LETHAL_INVISIBLE: DwCode = DwCode::new("DW0891", ExitTier::Build);
pub const EFFECT_CLEARED_LIVE: DwCode = DwCode::new("DW0540", ExitTier::Build);
pub const EFFECT_GRANT_BOUNDS: DwCode = DwCode::new("DW0541", ExitTier::Build);
pub const ENTITY_UNKNOWN: DwCode = DwCode::new("DW0173", ExitTier::Build);
pub const L10N_MISSING: DwCode = DwCode::new("DW0180", ExitTier::Build);
pub const L10N_ORPHAN: DwCode = DwCode::new("DW0181", ExitTier::Build);
pub const MARKER_RESERVED: DwCode = DwCode::new("DW0182", ExitTier::Build);
pub const TR_SIGIL_RESERVED: DwCode = DwCode::new("DW0183", ExitTier::Build);
pub const LANG_CODE_UNMAPPED: DwCode = DwCode::new("DW0184", ExitTier::Build);
pub const CHROME_RESERVED: DwCode = DwCode::new("DW0186", ExitTier::Build);
pub const L10N_STALE: DwCode = DwCode::new("DW0187", ExitTier::Build);
pub const L10N_PROVENANCE_MISSING: DwCode = DwCode::new("DW0188", ExitTier::Build);
pub const SKIN_INVALID: DwCode = DwCode::new("DW0190", ExitTier::Build);
pub const DIALOGUE_FLAG_DEADLOCK: DwCode = DwCode::new("DW0191", ExitTier::Build);
pub const EFFECT_UNKNOWN: DwCode = DwCode::new("DW0192", ExitTier::Build);
pub const BLOCK_UNKNOWN: DwCode = DwCode::new("DW0193", ExitTier::Build);
pub const TRIGGER_INVALID: DwCode = DwCode::new("DW0194", ExitTier::Build);
pub const NPC_DESPAWNED_REF: DwCode = DwCode::new("DW0195", ExitTier::Build);
pub const LIGHTING_RANGE: DwCode = DwCode::new("DW0196", ExitTier::Build);
pub const NPC_NEVER_SPAWNED: DwCode = DwCode::new("DW0197", ExitTier::Build);
pub const NPC_SPAWNED_LATE: DwCode = DwCode::new("DW0198", ExitTier::Build);
pub const CUTSCENE_SHAPE: DwCode = DwCode::new("DW0199", ExitTier::Build);
pub const OCEAN_NO_BOUNDARY: DwCode = DwCode::new("DW0320", ExitTier::Build);
pub const BOUNDARY_MARGIN: DwCode = DwCode::new("DW0321", ExitTier::Build);
pub const HORIZON_PARAM: DwCode = DwCode::new("DW0853", ExitTier::Build);
pub const SURROUND_NO_REGION: DwCode = DwCode::new("DW0855", ExitTier::Build);
pub const NESTED_SEQUENCE: DwCode = DwCode::new("DW0329", ExitTier::Build);
pub const TRAP_INVALID: DwCode = DwCode::new("DW0340", ExitTier::Build);
pub const SHORTCUT_INVALID: DwCode = DwCode::new("DW0371", ExitTier::Build);
pub const SHORTCUT_RESEALED: DwCode = DwCode::new("DW0372", ExitTier::Build);
pub const AMBUSH_INVALID: DwCode = DwCode::new("DW0375", ExitTier::Build);
pub const TIMED_GATE_INVALID: DwCode = DwCode::new("DW0377", ExitTier::Build);
pub const TIMED_GATE_REARMED: DwCode = DwCode::new("DW0389", ExitTier::Build);
pub const LANE_INVALID: DwCode = DwCode::new("DW0381", ExitTier::Build);
pub const LANE_NOT_RAIDER: DwCode = DwCode::new("DW0382", ExitTier::Build);
pub const LANE_SQUAD_TOO_SMALL: DwCode = DwCode::new("DW0383", ExitTier::Build);
pub const LANE_UNARMED: DwCode = DwCode::new("DW0384", ExitTier::Build);
pub const AGGRO_EDGE_NO_RANGE: DwCode = DwCode::new("DW0385", ExitTier::Build);
pub const TRAP_PAYLOAD_UNKNOWN: DwCode = DwCode::new("DW0341", ExitTier::Build);
pub const TRAP_NO_CONSEQUENCE: DwCode = DwCode::new("DW0440", ExitTier::Build);
pub const TRAP_VERB_ID_UNKNOWN: DwCode = DwCode::new("DW0441", ExitTier::Build);
pub const VOLLEY_CADENCE: DwCode = DwCode::new("DW0443", ExitTier::Build);
pub const SHOT_STYLE_INVALID: DwCode = DwCode::new("DW0348", ExitTier::Build);
pub const SHOT_SUBJECT_UNMOVED: DwCode = DwCode::new("DW0349", ExitTier::Build);
pub const USE_TRIGGER_ON_NPC: DwCode = DwCode::new("DW0350", ExitTier::Build);
pub const PARTY_SIZE: DwCode = DwCode::new("DW0356", ExitTier::Build);
pub const PARTY_CARRIER_SCHEDULED: DwCode = DwCode::new("DW0357", ExitTier::Build);
pub const DIFFICULTY_INVALID: DwCode = DwCode::new("DW0468", ExitTier::Build);
pub const DIFFICULTY_UNDECLARED_ACTORS: DwCode = DwCode::new("DW0469", ExitTier::Build);
pub const BOSS_RESPAWNS_ON_REST: DwCode = DwCode::new("DW0499", ExitTier::Build);
pub const STATE_UNDECLARED: DwCode = DwCode::new("DW0500", ExitTier::Build);
pub const STATE_NEVER_WRITTEN: DwCode = DwCode::new("DW0501", ExitTier::Build);
pub const STATE_NEVER_READ: DwCode = DwCode::new("DW0502", ExitTier::Build);
pub const STATE_SCOPE_UNREACHABLE: DwCode = DwCode::new("DW0503", ExitTier::Build);
pub const STAKE_STATE_SCOPE: DwCode = DwCode::new("DW0520", ExitTier::Build);
pub const STAKE_UNDECLARED: DwCode = DwCode::new("DW0521", ExitTier::Build);
pub const STAKE_NEVER_DROPPED: DwCode = DwCode::new("DW0522", ExitTier::Build);
pub const SHOP_OFFER_INERT: DwCode = DwCode::new("DW0523", ExitTier::Build);
pub const STAKE_FORFEIT_RANGE: DwCode = DwCode::new("DW0524", ExitTier::Build);
pub const STATE_READ_AFTER_WRITE: DwCode = DwCode::new("DW0527", ExitTier::Build);
pub const TRIGGER_AUDIENCE_UNATTRIBUTABLE: DwCode = DwCode::new("DW0427", ExitTier::Build);
pub const TRIGGER_ID_RESERVED: DwCode = DwCode::new("DW0428", ExitTier::Build);
pub const SEALED_BODY_UNANSWERED: DwCode = DwCode::new("DW0429", ExitTier::Build);
pub const TRAVERSAL_UNPROVABLE: DwCode = DwCode::new("DW0455", ExitTier::Build);
pub const GATE_NEVER_OPENS: DwCode = DwCode::new("DW0847", 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");
}
}