Skip to main content

powerio_pkg/
provenance.rs

1//! Producer, origin, source descriptors, and source maps.
2//!
3//! These records identify the producing tool, source artifacts, and the source
4//! record mapped to each model field.
5
6use std::collections::BTreeMap;
7
8use serde::{Deserialize, Serialize};
9
10/// The tool and build that produced the package.
11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
13pub struct Producer {
14    pub tool: String,
15    pub version: String,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub git_commit: Option<String>,
18    #[serde(default, skip_serializing_if = "Vec::is_empty")]
19    pub features: Vec<String>,
20}
21
22impl Producer {
23    /// The producer for packages built by this crate version of PowerIO.
24    pub fn powerio() -> Self {
25        Self {
26            tool: "powerio".to_owned(),
27            version: env!("CARGO_PKG_VERSION").to_owned(),
28            git_commit: None,
29            features: Vec::new(),
30        }
31    }
32}
33
34/// Where the package came from. Internally tagged on `kind` in JSON, so a reader
35/// distinguishes an in-memory model, a single text file (with or without
36/// retained source), a folder dataset, a partially decoded binary, a derived
37/// product of a lowering pass, or a composite of several sources.
38///
39/// The `hash` field is named consistently across all `Origin` variants.
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
42#[serde(tag = "kind", rename_all = "snake_case")]
43#[non_exhaustive]
44pub enum Origin {
45    /// Built in process, no source artifact.
46    InMemory,
47    File {
48        path: String,
49        format: String,
50        #[serde(default, skip_serializing_if = "Option::is_none")]
51        hash: Option<String>,
52        /// Whether the original source text was retained for a byte-exact
53        /// same-format echo. The retained text itself is not embedded in the
54        /// package; this only records that it exists at the frontend.
55        #[serde(default)]
56        retained_source: bool,
57    },
58    Folder {
59        path: String,
60        format: String,
61        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
62        file_hashes: BTreeMap<String, String>,
63    },
64    BinaryFile {
65        path: String,
66        format: String,
67        #[serde(default, skip_serializing_if = "Option::is_none")]
68        hash: Option<String>,
69        #[serde(default, skip_serializing_if = "Vec::is_empty")]
70        decoded_sections: Vec<String>,
71    },
72    /// A model produced by a lowering/normalization pass from another package.
73    Derived {
74        #[serde(default, skip_serializing_if = "Option::is_none")]
75        parent_package_id: Option<String>,
76        pass: String,
77        #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
78        options: serde_json::Map<String, serde_json::Value>,
79    },
80    /// Several sources combined, e.g. a static case plus a profile set.
81    Composite { sources: Vec<String> },
82}
83
84/// A declared source artifact, referenced from source maps and diagnostics by
85/// its `id`. (`sources[]` in the package.)
86#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
87#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
88pub struct SourceDescriptor {
89    pub id: String,
90    pub kind: String,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub path: Option<String>,
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub format: Option<String>,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub hash: Option<String>,
97}
98
99/// A pointer into one source artifact: where a canonical field came from.
100#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
101#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
102pub struct SourceRef {
103    pub source_id: String,
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub line: Option<u32>,
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub column: Option<u32>,
108    /// Byte offset, for binary sources.
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub byte_offset: Option<u64>,
111    /// Record / section / object type, e.g. `bus`.
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub record: Option<String>,
114    /// Canonical field / property name, e.g. `vm`.
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub field: Option<String>,
117    /// Raw token / value, when safe to embed.
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub raw_token: Option<String>,
120}
121
122impl SourceRef {
123    /// Create a reference to a declared source artifact.
124    pub fn new(source_id: impl Into<String>) -> Self {
125        Self {
126            source_id: source_id.into(),
127            line: None,
128            column: None,
129            byte_offset: None,
130            record: None,
131            field: None,
132            raw_token: None,
133        }
134    }
135
136    /// Set the field or property name.
137    #[must_use]
138    pub fn with_field(mut self, field: impl Into<String>) -> Self {
139        self.field = Some(field.into());
140        self
141    }
142
143    /// Set the source record, section, or object type.
144    #[must_use]
145    pub fn with_record(mut self, record: impl Into<String>) -> Self {
146        self.record = Some(record.into());
147        self
148    }
149
150    /// Set the source line number.
151    #[must_use]
152    pub fn with_line(mut self, line: u32) -> Self {
153        self.line = Some(line);
154        self
155    }
156}
157
158/// How a canonical field relates to its source value.
159#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
160#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
161#[serde(rename_all = "snake_case")]
162#[non_exhaustive]
163pub enum MappingKind {
164    /// Copied verbatim from the source.
165    Exact,
166    /// Materialized from a format default rather than the source text.
167    Defaulted,
168    /// Inferred from other source data.
169    Inferred,
170    /// Converted into canonical units (e.g. ohms to per unit).
171    ConvertedUnits,
172    /// Produced by a lowering pass (e.g. positive-sequence equivalent).
173    Lowered,
174    /// One canonical field aggregated from several source records.
175    Aggregated,
176    /// One source record split into several canonical fields/elements.
177    Split,
178    /// Synthesized with no direct source (e.g. a generated bus id).
179    Synthetic,
180    /// Extra data from the source preserved verbatim.
181    RetainedExtra,
182}
183
184/// How confident the source map entry is.
185#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
186#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
187#[serde(rename_all = "snake_case")]
188pub enum Confidence {
189    Exact,
190    High,
191    Medium,
192    Low,
193}
194
195/// One `element_path -> source` mapping.
196#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
197#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
198pub struct SourceMapEntry {
199    /// JSON pointer (or best-effort locator) into the package payload.
200    pub element_path: String,
201    pub source_ref: SourceRef,
202    pub mapping_kind: MappingKind,
203    pub confidence: Confidence,
204}