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