Skip to main content

callisto_format/
pre.rs

1use callisto_model::{Version, VersionGrammar};
2use indexmap::IndexMap;
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
7#[serde(rename_all = "camelCase")]
8pub struct PreState {
9    pub mode: PreMode,
10    pub tag: String,
11    #[schemars(with = "std::collections::BTreeMap<String, Version>")]
12    pub initial_versions: IndexMap<String, Version>,
13    pub changesets: Vec<String>,
14}
15
16impl PreState {
17    pub fn entering(
18        tag: impl Into<String>,
19        initial_versions: impl IntoIterator<Item = (String, Version)>,
20    ) -> Self {
21        let mut map = IndexMap::new();
22        for (pkg, ver) in initial_versions {
23            map.entry(pkg).or_insert(ver);
24        }
25        PreState {
26            mode: PreMode::Pre,
27            tag: tag.into(),
28            initial_versions: map,
29            changesets: Vec::new(),
30        }
31    }
32
33    pub fn exiting(mut self) -> Self {
34        self.mode = PreMode::Exit;
35        self
36    }
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
40#[serde(rename_all = "lowercase")]
41pub enum PreMode {
42    Pre,
43    Exit,
44}
45
46pub fn parse_pre_json(input: &str) -> Result<PreState, PreJsonError> {
47    let clean_input = input.strip_prefix('\u{FEFF}').unwrap_or(input);
48    let val: serde_json::Value =
49        serde_json::from_str(clean_input).map_err(|e| PreJsonError::Malformed {
50            message: e.to_string(),
51        })?;
52
53    let obj = val.as_object().ok_or_else(|| PreJsonError::Malformed {
54        message: "expected a JSON object".to_string(),
55    })?;
56
57    let mode_val = obj
58        .get("mode")
59        .ok_or(PreJsonError::MissingField { field: "mode" })?;
60    let mode_str = mode_val
61        .as_str()
62        .ok_or(PreJsonError::WrongFieldType { field: "mode" })?;
63    let mode = match mode_str {
64        "pre" => PreMode::Pre,
65        "exit" => PreMode::Exit,
66        _ => {
67            return Err(PreJsonError::InvalidMode {
68                found: mode_str.to_string(),
69            })
70        }
71    };
72
73    let tag_val = obj
74        .get("tag")
75        .ok_or(PreJsonError::MissingField { field: "tag" })?;
76    let tag = tag_val
77        .as_str()
78        .ok_or(PreJsonError::WrongFieldType { field: "tag" })?
79        .to_string();
80
81    let init_val = obj
82        .get("initialVersions")
83        .ok_or(PreJsonError::MissingField {
84            field: "initialVersions",
85        })?;
86    let init_obj = init_val.as_object().ok_or(PreJsonError::WrongFieldType {
87        field: "initialVersions",
88    })?;
89
90    let mut initial_versions = IndexMap::new();
91    for (pkg, v_val) in init_obj {
92        let v_str = v_val.as_str().ok_or(PreJsonError::WrongFieldType {
93            field: "initialVersions",
94        })?;
95        let ver = Version::parse(v_str, VersionGrammar::SemVer).map_err(|source| {
96            PreJsonError::InvalidInitialVersion {
97                package: pkg.clone(),
98                raw: v_str.to_string(),
99                source,
100            }
101        })?;
102        initial_versions.insert(pkg.clone(), ver);
103    }
104
105    let cs_val = obj.get("changesets").ok_or(PreJsonError::MissingField {
106        field: "changesets",
107    })?;
108    let cs_arr = cs_val.as_array().ok_or(PreJsonError::WrongFieldType {
109        field: "changesets",
110    })?;
111
112    let mut changesets = Vec::new();
113    for (index, c_val) in cs_arr.iter().enumerate() {
114        let c_str = c_val
115            .as_str()
116            .ok_or(PreJsonError::InvalidChangesetId { index })?;
117        changesets.push(c_str.to_string());
118    }
119
120    Ok(PreState {
121        mode,
122        tag,
123        initial_versions,
124        changesets,
125    })
126}
127
128pub fn write_pre_json(state: &PreState) -> String {
129    let mut map = IndexMap::new();
130    map.insert(
131        "mode".to_string(),
132        serde_json::to_value(state.mode).unwrap(),
133    );
134    map.insert("tag".to_string(), serde_json::to_value(&state.tag).unwrap());
135
136    let mut init_map = IndexMap::new();
137    for (pkg, ver) in &state.initial_versions {
138        init_map.insert(pkg.clone(), serde_json::to_value(ver).unwrap());
139    }
140    map.insert(
141        "initialVersions".to_string(),
142        serde_json::to_value(init_map).unwrap(),
143    );
144    map.insert(
145        "changesets".to_string(),
146        serde_json::to_value(&state.changesets).unwrap(),
147    );
148
149    let mut out = serde_json::to_string_pretty(&map).unwrap();
150    out.push('\n');
151    out
152}
153
154#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
155#[non_exhaustive]
156pub enum PreJsonError {
157    #[error("pre.json is not a valid JSON object: {message}")]
158    Malformed { message: String },
159
160    #[error("pre.json is missing required field {field:?}")]
161    MissingField { field: &'static str },
162
163    #[error("pre.json field {field:?} has the wrong type")]
164    WrongFieldType { field: &'static str },
165
166    #[error("pre.json has mode {found:?}, expected \"pre\" or \"exit\"")]
167    InvalidMode { found: String },
168
169    #[error("pre.json initialVersions[{package:?}] = {raw:?} is not a valid version: {source}")]
170    InvalidInitialVersion {
171        package: String,
172        raw: String,
173        #[source]
174        source: callisto_model::VersionParseError,
175    },
176
177    #[error("pre.json changesets[{index}] is not a string")]
178    InvalidChangesetId { index: usize },
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn pre_json_round_trips() {
187        let json = r#"{
188  "mode": "pre",
189  "tag": "next",
190  "initialVersions": {
191    "foo": "1.0.0"
192  },
193  "changesets": [
194    "cool-dragons-fly"
195  ]
196}
197"#;
198        let state = parse_pre_json(json).unwrap();
199        assert_eq!(state.tag, "next");
200        let written = write_pre_json(&state);
201        assert_eq!(written, json);
202    }
203}