aboutsummaryrefslogtreecommitdiff
path: root/crates/fparkan-diagnostics/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/fparkan-diagnostics/src/lib.rs')
-rw-r--r--crates/fparkan-diagnostics/src/lib.rs249
1 files changed, 0 insertions, 249 deletions
diff --git a/crates/fparkan-diagnostics/src/lib.rs b/crates/fparkan-diagnostics/src/lib.rs
deleted file mode 100644
index f0228ec..0000000
--- a/crates/fparkan-diagnostics/src/lib.rs
+++ /dev/null
@@ -1,249 +0,0 @@
-#![forbid(unsafe_code)]
-#![cfg_attr(
- test,
- allow(
- clippy::cast_possible_truncation,
- clippy::cast_possible_wrap,
- clippy::cast_precision_loss,
- clippy::expect_used,
- clippy::float_cmp,
- clippy::identity_op,
- clippy::too_many_lines,
- clippy::uninlined_format_args,
- clippy::map_unwrap_or,
- clippy::needless_raw_string_hashes,
- clippy::semicolon_if_nothing_returned,
- clippy::type_complexity,
- clippy::panic,
- clippy::unwrap_used
- )
-)]
-//! Structured diagnostics shared by `FParkan` crates.
-
-use serde::Serialize;
-
-/// Diagnostic severity.
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
-#[serde(rename_all = "lowercase")]
-pub enum Severity {
- /// Informational note.
- Info,
- /// Recoverable warning.
- Warning,
- /// Error for the current operation.
- Error,
- /// Fatal error for the current run.
- Fatal,
-}
-
-/// Evidence level for a contract or interpretation.
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
-#[serde(rename_all = "lowercase")]
-pub enum EvidenceStatus {
- /// Described by project documentation.
- Documented,
- /// Verified by synthetic fixtures.
- SyntheticVerified,
- /// Verified against the licensed corpus.
- CorpusVerified,
- /// Verified by runtime capture.
- RuntimeCaptured,
- /// Working hypothesis; not a runtime contract.
- Hypothesis,
-}
-
-/// Operation phase where a diagnostic was produced.
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
-#[serde(rename_all = "lowercase")]
-pub enum Phase {
- /// Discovery.
- Discover,
- /// Read.
- Read,
- /// Parse.
- Parse,
- /// Validate.
- Validate,
- /// Resolve.
- Resolve,
- /// Prepare.
- Prepare,
- /// Construct.
- Construct,
- /// Register.
- Register,
- /// Simulate.
- Simulate,
- /// Render.
- Render,
-}
-
-/// Byte span in an input source.
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
-pub struct SourceSpan {
- /// Start offset.
- pub offset: u64,
- /// Length in bytes.
- pub length: u64,
-}
-
-/// Stable diagnostic code.
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize)]
-pub struct DiagnosticCode(pub &'static str);
-
-/// Context attached to a diagnostic.
-#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
-pub struct DiagnosticContext {
- /// Phase.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub phase: Option<Phase>,
- /// Redacted or logical path.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub path: Option<String>,
- /// Archive entry name.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub archive_entry: Option<String>,
- /// Object/prototype key.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub object_key: Option<String>,
- /// Input span.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub span: Option<SourceSpan>,
-}
-
-/// Structured diagnostic with cause chain.
-#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
-pub struct Diagnostic {
- /// Stable code.
- pub code: DiagnosticCode,
- /// Severity.
- pub severity: Severity,
- /// Human message.
- pub message: String,
- /// Context.
- pub context: DiagnosticContext,
- /// Causes.
- pub causes: Vec<Diagnostic>,
-}
-
-/// Creates a diagnostic with default error severity.
-#[must_use]
-pub fn diagnostic(code: DiagnosticCode, message: impl Into<String>) -> Diagnostic {
- Diagnostic {
- code,
- severity: Severity::Error,
- message: message.into(),
- context: DiagnosticContext::default(),
- causes: Vec::new(),
- }
-}
-
-impl Diagnostic {
- /// Returns a copy with severity changed.
- #[must_use]
- pub fn with_severity(mut self, severity: Severity) -> Self {
- self.severity = severity;
- self
- }
-
- /// Returns a copy with context changed.
- #[must_use]
- pub fn with_context(mut self, context: DiagnosticContext) -> Self {
- self.context = context;
- self
- }
-
- /// Adds a cause.
- pub fn push_cause(&mut self, cause: Diagnostic) {
- self.causes.push(cause);
- }
-}
-
-/// Renders a compact human-readable diagnostic.
-#[must_use]
-pub fn render_human(diagnostic: &Diagnostic) -> String {
- let mut out = format!(
- "{:?} {}: {}",
- diagnostic.severity, diagnostic.code.0, diagnostic.message
- );
- if let Some(path) = &diagnostic.context.path {
- out.push_str(" [");
- out.push_str(path);
- out.push(']');
- }
- out
-}
-
-/// Renders deterministic JSON using the typed diagnostic schema.
-#[must_use]
-pub fn render_json(diagnostic: &Diagnostic) -> String {
- match serde_json::to_string(diagnostic) {
- Ok(json) => json,
- Err(err) => format!("{{\"error\":\"diagnostic serialization failed: {err}\"}}"),
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn json_is_stable() {
- let d = diagnostic(DiagnosticCode("S0-DIAG-001"), "keeps context").with_context(
- DiagnosticContext {
- phase: Some(Phase::Parse),
- ..DiagnosticContext::default()
- },
- );
- assert_eq!(
- render_json(&d),
- "{\"code\":\"S0-DIAG-001\",\"severity\":\"error\",\"message\":\"keeps context\",\"context\":{\"phase\":\"parse\"},\"causes\":[]}"
- );
- }
-
- #[test]
- fn diagnostic_chain_preserves_context() {
- let mut root = diagnostic(DiagnosticCode("ROOT"), "root").with_context(DiagnosticContext {
- phase: Some(Phase::Resolve),
- path: Some("archives/material.lib".to_string()),
- archive_entry: Some("MATERIAL.MAT0".to_string()),
- object_key: Some("unit/tank".to_string()),
- span: Some(SourceSpan {
- offset: 12,
- length: 4,
- }),
- });
- root.push_cause(diagnostic(DiagnosticCode("CAUSE"), "cause").with_context(
- DiagnosticContext {
- phase: Some(Phase::Parse),
- path: Some("archives/material.lib".to_string()),
- span: Some(SourceSpan {
- offset: 16,
- length: 8,
- }),
- ..DiagnosticContext::default()
- },
- ));
-
- let json = render_json(&root);
-
- assert!(json.contains("\"code\":\"ROOT\""));
- assert!(json.contains("\"phase\":\"resolve\""));
- assert!(json.contains("\"path\":\"archives/material.lib\""));
- assert!(json.contains("\"archive_entry\":\"MATERIAL.MAT0\""));
- assert!(json.contains("\"object_key\":\"unit/tank\""));
- assert!(json.contains("\"span\":{\"offset\":12,\"length\":4}"));
- assert!(json.contains("\"code\":\"CAUSE\""));
- assert!(json.contains("\"span\":{\"offset\":16,\"length\":8}"));
- }
-
- #[test]
- fn json_escapes_all_control_characters() {
- let value = diagnostic(DiagnosticCode("S1-H01"), "quote\"\u{0000}tab\tline\r\n");
- let json = render_json(&value);
- assert!(json.contains("\\u0000"));
- assert!(json.contains("\\t"));
- assert!(!json.contains('\t'));
- assert!(!json.contains('\r'));
- }
-}