Skip to main content

powerio_dist/bmopf/
profile.rs

1//! The BMOPF schema version a document is written against.
2
3use serde::{Deserialize, Serialize};
4
5/// The BMOPF schema version a document is read as or written against.
6///
7/// A schema version fixes which element classes exist and where they live.
8/// `0.1.0` declares the ten element classes and four transformer subtypes the
9/// historical baseline contains, sets `additionalProperties: false` on every
10/// object, and permits free-form `extras` and `meta.provenance`; the classes outside it
11/// travel there. `0.2.0` declares those classes at the top level and gives the
12/// transformer taps, winding neutral impedance, and no load admittance their
13/// own slots. Unsupported malformed records still require diagnostics.
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
15#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
16#[serde(rename_all = "kebab-case")]
17#[non_exhaustive]
18pub enum BmopfSchemaVersion {
19    /// Schema 0.1.0, the historical baseline.
20    Bmopf010,
21    /// Schema 0.2.0, the proposal in
22    /// <https://github.com/distribution-system-opt/dsopt-schema>. The default
23    /// output version, because it states every class the model carries.
24    #[default]
25    Bmopf020,
26}
27
28/// The `$id` of schema 0.1.0.
29const SCHEMA_ID_010: &str = "https://raw.githubusercontent.com/distribution-system-opt/dsopt-schema/main/schema/bmopf/0.1.0/bmopf.schema.json";
30
31/// The `$id` of schema 0.2.0.
32const SCHEMA_ID_020: &str = "https://raw.githubusercontent.com/distribution-system-opt/dsopt-schema/main/schema/bmopf/0.2.0/bmopf.schema.json";
33
34/// Immutable revision of the proposed BMOPF 0.2.0 schema.
35pub const BMOPF_PROPOSAL_COMMIT: &str = "664b494f2ee31ee76f8f78e7852cdb1f1c9a8e7d";
36/// SHA-256 of the exact UTF-8 schema document at the pinned revision.
37pub const BMOPF_PROPOSAL_SHA256: &str =
38    "74d6c6de3637d52e42a26c4cb0584f51df70d69f360b236cf5e23afaf7669462";
39/// Immutable retrieval location, distinct from the schema's canonical `$id`.
40pub const BMOPF_PROPOSAL_URL: &str = "https://raw.githubusercontent.com/distribution-system-opt/dsopt-schema/664b494f2ee31ee76f8f78e7852cdb1f1c9a8e7d/schema/bmopf/0.2.0/bmopf.schema.json";
41
42const ARCHIVE_URL_010: &str = "https://raw.githubusercontent.com/eigenergy/powerio/5234df55cd13ad31455697cffbdc16ca50662667/powerio-dist/schemas/bmopf/0.1.0/bmopf.schema.json";
43const ARCHIVE_URL_020: &str = "https://raw.githubusercontent.com/eigenergy/powerio/5234df55cd13ad31455697cffbdc16ca50662667/powerio-dist/schemas/bmopf/0.2.0/bmopf.schema.json";
44
45impl BmopfSchemaVersion {
46    /// The schema version string, as `meta.schema_version` states it.
47    #[must_use]
48    pub const fn version(self) -> &'static str {
49        match self {
50            Self::Bmopf010 => "0.1.0",
51            Self::Bmopf020 => "0.2.0",
52        }
53    }
54
55    /// The canonical `$id` of the schema document, independent of retrieval revision.
56    #[must_use]
57    pub const fn schema_id(self) -> &'static str {
58        match self {
59            Self::Bmopf010 => SCHEMA_ID_010,
60            Self::Bmopf020 => SCHEMA_ID_020,
61        }
62    }
63
64    /// Retrieval URL for the exact schema archived by PowerIO.
65    #[must_use]
66    pub const fn retrieval_url(self) -> &'static str {
67        match self {
68            Self::Bmopf010 => ARCHIVE_URL_010,
69            Self::Bmopf020 => ARCHIVE_URL_020,
70        }
71    }
72
73    /// The version a `meta.$schema` value names, or `None` when it names none.
74    ///
75    /// A released schema document carries its version in its own `$id`, so the
76    /// version is the directory the schema document sits in. Documents written
77    /// before the schema moved to its own repository name the draft schema of
78    /// the `bmopf-report` repository, which only ever held 0.1.0, so those
79    /// locations resolve to 0.1.0 whatever shape they take. A location naming
80    /// neither answers `None` rather than choosing a version.
81    #[must_use]
82    pub fn from_schema_id(id: &str) -> Option<Self> {
83        for candidate in [Self::Bmopf010, Self::Bmopf020] {
84            if id == candidate.schema_id() || id.contains(&format!("/{}/", candidate.version())) {
85                return Some(candidate);
86            }
87        }
88        if id.contains("bmopf-report") || id.contains("draft_bmopf_schema") {
89            return Some(Self::Bmopf010);
90        }
91        None
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn the_default_output_version_is_the_one_that_states_every_class() {
101        assert_eq!(BmopfSchemaVersion::default(), BmopfSchemaVersion::Bmopf020);
102        assert_eq!(BmopfSchemaVersion::default().version(), "0.2.0");
103    }
104
105    #[test]
106    fn a_version_resolves_from_its_own_schema_id() {
107        for expected in [BmopfSchemaVersion::Bmopf010, BmopfSchemaVersion::Bmopf020] {
108            assert_eq!(
109                BmopfSchemaVersion::from_schema_id(expected.schema_id()),
110                Some(expected)
111            );
112        }
113    }
114
115    #[test]
116    fn the_draft_schema_locations_resolve_to_the_version_that_repository_held() {
117        // The two locations the published examples state.
118        for id in [
119            "https://github.com/frederikgeth/bmopf-report/draft_schema_and_networks",
120            "https://raw.githubusercontent.com/frederikgeth/bmopf-report/main/draft_schema_and_networks/draft_bmopf_schema.json",
121        ] {
122            assert_eq!(
123                BmopfSchemaVersion::from_schema_id(id),
124                Some(BmopfSchemaVersion::Bmopf010),
125                "{id}"
126            );
127        }
128    }
129
130    #[test]
131    fn a_schema_location_naming_no_version_resolves_to_none() {
132        assert_eq!(BmopfSchemaVersion::from_schema_id(""), None);
133        assert_eq!(
134            BmopfSchemaVersion::from_schema_id("https://example.org/bmopf/schema/v1/bmopf.json"),
135            None
136        );
137    }
138
139    #[test]
140    fn a_version_resolves_from_a_relocated_schema_of_the_same_version() {
141        assert_eq!(
142            BmopfSchemaVersion::from_schema_id(
143                "file:///cases/schema/bmopf/0.2.0/bmopf.schema.json"
144            ),
145            Some(BmopfSchemaVersion::Bmopf020)
146        );
147    }
148}