Skip to main content

scratch_test_model/scopes/
target_data.rs

1use std::collections::HashMap;
2
3use svalue::SValue;
4
5use crate::{
6    Id,
7    attrs::data::{List, ListKind, Variable, VariableKind},
8    error::TargetDataError,
9};
10
11pub type TargetVariables = TargetData<VariableKind, (Variable, SValue)>;
12pub type TargetLists = TargetData<ListKind, (List, Vec<SValue>)>;
13
14/// Scratch stores for every variable and list the value inside the file that it
15/// had when the user pressed "Save" in the editor.
16///
17/// For [`Variable`]s this is a single `SValue`.
18/// For [`List`]s this is a collection ([`Vec`]) `SValue`s.
19///
20/// Relying on those values is discouraged as the user could have stored anything there.
21/// This could be considered to be equivalent to reading uninitialized variables.
22#[derive(Debug, PartialEq)]
23pub struct TargetData<Kind, Data> {
24    /// map from data id to name and saved value
25    ///
26    /// (the value the data entry had when pressing save in the editor)
27    map: HashMap<Id, Data>,
28    phantom: std::marker::PhantomData<Kind>,
29}
30
31impl<Kind, Data> TargetData<Kind, Data> {
32    pub fn values(&self) -> impl Iterator<Item = &Data> {
33        self.map.values()
34    }
35    pub fn ids(&self) -> impl Iterator<Item = &Id> {
36        self.map.keys()
37    }
38    pub fn iter(&self) -> impl Iterator<Item = (&Id, &Data)> {
39        self.map.iter()
40    }
41    pub fn get<'a>(&'a self, id: &Id) -> Option<&'a Data> {
42        self.map.get(id)
43    }
44}
45
46impl<Kind: DataEntry> TargetData<Kind, Kind::Data> {
47    pub(crate) fn from_json(value: &serde_json::Value) -> Result<Self, TargetDataError<Kind>> {
48        let dict = value
49            .as_object()
50            .ok_or(TargetDataError::ExpectedObject {})?;
51        let map: Result<_, _> = dict
52            .into_iter()
53            .map(|(id, def)| {
54                let id: Id = id.clone().into();
55                if let Some(parsed) = Kind::parse_entry(id.clone(), def) {
56                    Ok((id, parsed))
57                } else {
58                    Err(TargetDataError::AtLeastOneInvalid(id))
59                }
60            })
61            .collect();
62        Ok(Self {
63            map: map?,
64            phantom: std::marker::PhantomData {},
65        })
66    }
67}
68
69impl DataEntry for VariableKind {
70    type Data = (Variable, SValue);
71    fn parse_entry(id: Id, def: &serde_json::Value) -> Option<(Variable, SValue)> {
72        let name = def.get(0)?.as_str()?.into();
73        // TODO: If the value is invalid it has to be an array or object
74        // Calling TryFrom needs to clone the value before it is validated.
75        // This means a possibly large array/object could get cloned.
76        // A way to mitigate this would be to provide a SValue method taking a reference,
77        // cloning only if it is valid
78        let Ok(initial) = SValue::try_from(def.get(1)?.clone()) else {
79            log::warn!("failed to parse value of variable {name:?}: definition is {def:?}");
80            return None;
81        };
82        Some((Variable::new(name, id), initial))
83    }
84}
85
86impl DataEntry for ListKind {
87    type Data = (List, Vec<SValue>);
88    fn parse_entry(id: Id, def: &serde_json::Value) -> Option<(List, Vec<SValue>)> {
89        let name = def[0].as_str()?.into();
90        let initial = if let Some(arr) = def[1].as_array() {
91            arr.iter()
92                .map(|element| {
93                    if let Some(number) = element.as_number().cloned().map(SValue::from) {
94                        return number;
95                    }
96                    let text = element
97                        .as_str()
98                        .map(|s| s.into())
99                        .unwrap_or_else(|| element.to_string().into());
100                    SValue::Text(text)
101                })
102                .collect()
103        } else {
104            return None;
105        };
106        Some((List::new(name, id), initial))
107    }
108}
109pub trait DataEntry {
110    type Data;
111    fn parse_entry(id: Id, value: &serde_json::Value) -> Option<Self::Data>
112    where
113        Self: Sized;
114}