Skip to main content

powerio_core/
lib.rs

1//! Dependency neutral compiler infrastructure shared by PowerIO crates.
2//!
3//! This crate owns source buffers, diagnostics, operation errors, the generic
4//! [`PioModule`], repeated value containers, and output destinations. It does
5//! not own electrical network, calculation, matrix, or dynamic value types.
6
7mod bounded;
8mod codes;
9mod diagnostic;
10mod error;
11mod module;
12pub(crate) mod nonfinite;
13mod output;
14mod records;
15mod scenario;
16mod source;
17mod time_series;
18mod validation;
19
20pub use codes::CORE_DIAGNOSTIC_CODES;
21pub use diagnostic::{
22    CodeStatus, Diagnostic, DiagnosticCode, DiagnosticInfo, DiagnosticSeverity, DiagnosticStage,
23    ErrorCategory, check_registry, check_scope_ownership, code_is_well_formed, render_diagnostic,
24    render_diagnostics,
25};
26pub use error::Error;
27pub use module::PioModule;
28pub use output::{ArtifactPath, Destination, MemoryArtifact, WriteResult, WrittenOutput};
29pub use records::{
30    DiagnosticId, Digest, DigestAlgorithm, HistoryEntry, HistoryId, HistoryKind, Producer,
31    SourceDescriptor, SourceId, SourceMapEntry, SourceRelation, SourceSpan,
32};
33pub use scenario::{SCENARIO_PROBABILITY_TOLERANCE, Scenario, ScenarioId, ScenarioSet};
34pub use source::{FormatId, Source, SourceBuffer};
35pub use time_series::{TimePoint, TimeSeries};
36
37/// Decode time record limits, shared by every PowerIO wire.
38///
39/// The stored `.pio.json` record wire and the core record wire must refuse the
40/// same hostile inputs: every sequence, map, and string is bounded while it is
41/// decoded, before the full collection has been retained. The helpers here run
42/// inside serde visitors (`#[serde(deserialize_with = ...)]`), so the only
43/// transient allocation is the JSON scanner's own token buffer.
44pub mod limits {
45    pub use crate::bounded::{BoundedStr, TruncatedStr, bounded_json_map, bounded_vec};
46    pub use crate::validation::{
47        MAX_DIAGNOSTIC_CODE_BYTES, MAX_DIAGNOSTIC_DETAIL_KEYS, MAX_DIAGNOSTIC_MESSAGE_BYTES,
48        MAX_DIAGNOSTIC_MESSAGE_DECODE_BYTES, MAX_DIAGNOSTIC_RELATED, MAX_DIAGNOSTIC_SPANS,
49        MAX_DIAGNOSTIC_TARGET_BYTES, MAX_HISTORY_NOTES, MAX_HISTORY_PARAMETERS,
50        MAX_IDENTIFIER_BYTES, MAX_MODULE_DIAGNOSTICS, MAX_MODULE_EXTENSION_KEYS,
51        MAX_MODULE_HISTORY_ENTRIES, MAX_MODULE_SOURCE_MAP_ENTRIES, MAX_MODULE_SOURCES,
52        MAX_SOURCE_MAP_SPANS,
53    };
54}
55
56/// Cross-crate implementation support.
57///
58/// Audit outcome for every `#[doc(hidden)]` `pub` item this crate exposes:
59/// the mutable diagnostic collector and the checked dimension helper are
60/// crate private; each emitting sibling crate carries its own byte identical
61/// crate-private collector copy instead of importing one through a hidden
62/// path. Two items remain, both re-exported here. The nonfinite serde
63/// adapter pair wraps a whole serializer or deserializer inside the network
64/// types' serde trait impls; duplicating that machinery per crate would let
65/// the one shared float spelling diverge. `__commit_staged_file` commits a
66/// file a streaming writer outside this crate already staged itself, for a
67/// writer whose artifact must never be materialized in memory; every other
68/// commit goes through [`Destination`]. Both stay a single hidden seam:
69/// unstable, never re-exported by the facade, and not accepted or returned
70/// by any public PowerIO operation.
71#[doc(hidden)]
72pub mod __implementation {
73    /// The serde adapters that spell nonfinite floats for JSON.
74    pub mod nonfinite {
75        pub use crate::nonfinite::*;
76    }
77
78    /// Commit an already staged file onto its destination without
79    /// materializing the artifact in memory first.
80    pub use crate::output::__commit_staged_file;
81}
82
83/// Declare one crate's diagnostic registry.
84///
85/// Each code literal appears once in the declaration and the generated `ALL`
86/// slice drives registry checks and reference generation.
87#[macro_export]
88macro_rules! diagnostic_codes {
89    ($(
90        $(#[$attr:meta])*
91        $name:ident = $code:literal, $severity:ident, $summary:literal
92        $(, category = $category:ident)?
93        $(, retired = $since:literal)? ;
94    )*) => {
95        $(
96            $(#[$attr])*
97            pub const $name: $crate::DiagnosticInfo = $crate::DiagnosticInfo::new(
98                $code,
99                $crate::DiagnosticSeverity::$severity,
100                $summary,
101            )
102            $(.with_category($crate::ErrorCategory::$category))?
103            $(.retired($since))?;
104        )*
105
106        /// Every code declared by this registry.
107        pub const ALL: &[&$crate::DiagnosticInfo] = &[$(&$name),*];
108    };
109}
110
111#[cfg(test)]
112mod tests {
113    /// The doc comment on [`__implementation`] claims to enumerate every
114    /// `#[doc(hidden)]` `pub` item this crate re-exports from its root. A
115    /// future edit that adds another one, or that adds an item inside
116    /// `__implementation` the comment does not name, would go unnoticed
117    /// without this: `__implementation` must be the crate's only top level
118    /// `#[doc(hidden)]` item, and its own direct items must be exactly the
119    /// two the comment names.
120    #[test]
121    fn hidden_root_items_match_the_implementation_module_note() {
122        let source = include_str!("lib.rs");
123
124        let top_level_hidden = source
125            .lines()
126            .filter(|line| line.trim() == "#[doc(hidden)]")
127            .count();
128        assert_eq!(
129            top_level_hidden, 1,
130            "exactly one #[doc(hidden)] item is expected at the crate root: __implementation"
131        );
132
133        let start = source
134            .find("pub mod __implementation {")
135            .expect("the __implementation module must exist");
136        let mut depth = 0i64;
137        let mut direct_items = Vec::new();
138        for (index, line) in source[start..].lines().enumerate() {
139            if index == 0 {
140                depth += i64::try_from(line.matches('{').count()).unwrap();
141                depth -= i64::try_from(line.matches('}').count()).unwrap();
142                continue;
143            }
144            if depth == 1 {
145                let trimmed = line.trim();
146                if trimmed.starts_with("pub mod ") || trimmed.starts_with("pub use ") {
147                    direct_items.push(trimmed.to_owned());
148                }
149            }
150            depth += i64::try_from(line.matches('{').count()).unwrap();
151            depth -= i64::try_from(line.matches('}').count()).unwrap();
152            if depth <= 0 {
153                break;
154            }
155        }
156
157        assert_eq!(
158            direct_items,
159            vec![
160                "pub mod nonfinite {".to_owned(),
161                "pub use crate::output::__commit_staged_file;".to_owned(),
162            ],
163            "__implementation's direct items no longer match the audit note above it"
164        );
165    }
166}