Skip to main content

lingora_core/audit/
result.rs

1use std::collections::HashMap;
2
3use fluent4rs::ast::Entry;
4
5use crate::{
6    audit::{AuditIssue, Workspace},
7    domain::{HasLocale, Locale},
8    fluent::{FluentDocument, QualifiedIdentifier},
9};
10
11/// The classification of a Fluent document's role within the workspace during analysis.
12#[derive(Clone, Copy, Debug)]
13pub enum DocumentRole {
14    /// The reference document against which all others are compared.
15    Canonical,
16
17    /// A primary (non-variant) translation document for a language root.
18    Primary,
19
20    /// A regional or script variant that depends on a primary for fallback.
21    Variant,
22
23    /// A document whose locale was not expected or configured in the workspace.
24    Orphan,
25}
26
27/// A Fluent document that has been parsed, assigned a role, and is ready for analysis.
28#[derive(Clone, Debug)]
29pub struct AuditedDocument {
30    document: FluentDocument,
31    role: DocumentRole,
32}
33
34impl AuditedDocument {
35    /// Constructs an `AuditedDocument` from a parsed `FluentDocument` and its assigned role.
36    pub fn from_fluent_document(role: DocumentRole, document: &FluentDocument) -> Self {
37        let document = document.clone();
38        Self { document, role }
39    }
40
41    /// Returns the role assigned to this document in the audit.
42    pub fn role(&self) -> DocumentRole {
43        self.role
44    }
45
46    /// Returns an iterator over all **qualified identifiers** defined in this document.
47    pub fn identifiers(&self) -> impl Iterator<Item = QualifiedIdentifier> {
48        self.document.entry_identifiers()
49    }
50
51    /// Returns an iterator over all AST `Entry` nodes matching the given identifier.
52    pub fn entries(&self, identifier: &QualifiedIdentifier) -> impl Iterator<Item = &Entry> {
53        self.document.entries(identifier)
54    }
55}
56
57impl HasLocale for AuditedDocument {
58    fn locale(&self) -> &Locale {
59        self.document.locale()
60    }
61}
62
63/// The complete result of running an audit over a workspace.
64///
65/// Contains:
66/// - All discovered localization issues
67/// - The set of parsed and classified documents, indexed by locale
68/// - A reference to the original workspace configuration
69///
70/// This type is what `AuditEngine` returns and what CLI/TUI renderers consume.
71#[derive(Debug)]
72pub struct AuditResult {
73    issues: Vec<AuditIssue>,
74    documents: HashMap<Locale, AuditedDocument>,
75    workspace: Workspace,
76}
77
78impl AuditResult {
79    pub(crate) fn new(
80        issues: Vec<AuditIssue>,
81        nodes: Vec<AuditedDocument>,
82        workspace: &Workspace,
83    ) -> Self {
84        let documents = nodes
85            .iter()
86            .cloned()
87            .map(|node| (node.locale().clone(), node))
88            .collect::<HashMap<_, _>>();
89        let workspace = workspace.clone();
90
91        Self {
92            issues,
93            documents,
94            workspace,
95        }
96    }
97
98    /// Returns `true` if no issues were found (clean audit).
99    pub fn is_ok(&self) -> bool {
100        self.issues.is_empty()
101    }
102
103    /// Returns the workspace configuration used for this audit.
104    pub fn workspace(&self) -> &Workspace {
105        &self.workspace
106    }
107
108    /// Returns an iterator over all discovered issues.
109    pub fn issues(&self) -> impl Iterator<Item = &AuditIssue> {
110        self.issues.iter()
111    }
112
113    /// Returns the canonical (reference) locale used as the baseline.
114    pub fn canonical_locale(&self) -> &Locale {
115        self.workspace.canonical_locale()
116    }
117
118    /// Returns an iterator over all locales for which a document was found.
119    pub fn document_locales(&self) -> impl Iterator<Item = &Locale> {
120        self.documents.keys()
121    }
122
123    /// Returns the `AuditedDocument` for the given locale, if one was parsed.
124    pub fn document(&self, locale: &Locale) -> Option<&AuditedDocument> {
125        self.documents.get(locale)
126    }
127}