rototo 0.1.0-alpha.2

Control plane for runtime configuration in application code.
Documentation
use serde::Serialize;

#[derive(Debug, Clone, Copy)]
pub struct DiagnosticSpec {
    pub code: &'static str,
    pub title: &'static str,
    pub help: &'static str,
}

#[derive(Debug, Clone, Copy)]
pub struct LintRule {
    pub id: &'static str,
    pub title: &'static str,
    pub help: &'static str,
}

#[derive(Debug, Serialize)]
pub struct DiagnosticCatalogEntry {
    pub code: String,
    pub title: String,
    pub help: String,
    pub source: DiagnosticSource,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kind: Option<String>,
}

pub const WORKSPACE_TOML_FILE_PARSE_FAILED: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/workspace-toml-file-parse-failed",
    title: "Workspace TOML file could not be parsed",
    help: "Fix the TOML syntax in the referenced workspace file.",
};

pub const WORKSPACE_TOML_FILE_INVALID: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/workspace-toml-file-invalid",
    title: "Workspace TOML file is invalid",
    help: "Update the referenced qualifier or variable TOML file so it matches rototo workspace rules.",
};

pub const JSON_SCHEMA_FILE_PARSE_FAILED: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/json-schema-file-parse-failed",
    title: "JSON Schema file could not be parsed",
    help: "Fix the JSON syntax in the referenced schema file.",
};

pub const JSON_SCHEMA_FILE_INVALID: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/json-schema-file-invalid",
    title: "JSON Schema file is invalid",
    help: "Update the referenced schema file so it is valid JSON Schema.",
};

pub const VARIABLE_CUSTOM_LINT_FAILED: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/variable-custom-lint-failed",
    title: "Variable custom lint failed",
    help: "Update the variable or its Lua lint rule so the custom lint passes.",
};

pub const WORKSPACE_NOT_FOUND: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/workspace-not-found",
    title: "Workspace directory was not found",
    help: "Pass a path to an existing rototo workspace directory.",
};

pub const WORKSPACE_MANIFEST_MISSING: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/workspace-manifest-missing",
    title: "Workspace manifest is missing",
    help: "Create rototo-workspace.toml at the workspace root.",
};

pub const WORKSPACE_MANIFEST_PARSE_FAILED: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/workspace-manifest-parse-failed",
    title: "Workspace manifest could not be parsed",
    help: "Fix the TOML syntax in rototo-workspace.toml.",
};

pub const WORKSPACE_MANIFEST_SCHEMA_FAILED: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/workspace-manifest-schema-failed",
    title: "Workspace manifest does not match the rototo workspace schema",
    help: "Declare schema_version = 1 and [environments].values in rototo-workspace.toml.",
};

pub const WORKSPACE_CONTEXT_SCHEMA_FAILED: DiagnosticSpec = DiagnosticSpec {
    code: "rototo/workspace-context-schema-failed",
    title: "Resolve context schema contract failed",
    help: "Update the workspace context schema or qualifier context references.",
};

pub fn kernel_catalog_entries() -> Vec<DiagnosticCatalogEntry> {
    [
        (WORKSPACE_TOML_FILE_PARSE_FAILED, DiagnosticSource::Kernel),
        (WORKSPACE_TOML_FILE_INVALID, DiagnosticSource::Kernel),
        (JSON_SCHEMA_FILE_PARSE_FAILED, DiagnosticSource::Schema),
        (JSON_SCHEMA_FILE_INVALID, DiagnosticSource::Schema),
        (VARIABLE_CUSTOM_LINT_FAILED, DiagnosticSource::Custom),
        (WORKSPACE_CONTEXT_SCHEMA_FAILED, DiagnosticSource::Custom),
        (WORKSPACE_NOT_FOUND, DiagnosticSource::Kernel),
        (WORKSPACE_MANIFEST_MISSING, DiagnosticSource::Kernel),
        (WORKSPACE_MANIFEST_PARSE_FAILED, DiagnosticSource::Kernel),
        (WORKSPACE_MANIFEST_SCHEMA_FAILED, DiagnosticSource::Kernel),
    ]
    .into_iter()
    .map(|(spec, source)| DiagnosticCatalogEntry {
        code: spec.code.to_owned(),
        title: spec.title.to_owned(),
        help: spec.help.to_owned(),
        source,
        kind: None,
    })
    .collect()
}

#[derive(Debug, Serialize)]
pub struct Diagnostic {
    pub code: String,
    pub severity: Severity,
    pub source: DiagnosticSource,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rule: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kind: Option<String>,
    pub path: String,
    pub message: String,
    pub help: String,
    pub details: serde_json::Value,
}

impl Diagnostic {
    pub fn new(
        spec: DiagnosticSpec,
        source: DiagnosticSource,
        path: impl Into<String>,
        message: impl Into<String>,
    ) -> Self {
        Self {
            code: spec.code.to_owned(),
            severity: Severity::Error,
            source,
            rule: None,
            kind: None,
            path: path.into(),
            message: message.into(),
            help: spec.help.to_owned(),
            details: serde_json::json!({ "title": spec.title }),
        }
    }

    pub fn new_rule(
        spec: DiagnosticSpec,
        source: DiagnosticSource,
        path: impl Into<String>,
        message: impl Into<String>,
        rule: LintRule,
    ) -> Self {
        Self {
            code: spec.code.to_owned(),
            severity: Severity::Error,
            source,
            rule: Some(rule.id.to_owned()),
            kind: None,
            path: path.into(),
            message: message.into(),
            help: rule.help.to_owned(),
            details: serde_json::json!({ "title": rule.title }),
        }
    }

    pub fn with_kind(mut self, kind: impl Into<String>) -> Self {
        self.kind = Some(kind.into());
        self
    }

    pub fn with_details(mut self, details: serde_json::Value) -> Self {
        self.details = details;
        self
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
    Error,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticSource {
    Kernel,
    Schema,
    Custom,
}