lux_lib/lua_rockspec/
serde_util.rs

1use std::{collections::HashMap, fmt::Display};
2
3use itertools::Itertools;
4use serde::{de, Deserialize, Deserializer};
5use thiserror::Error;
6
7#[derive(Hash, Debug, Eq, PartialEq, Clone)]
8pub enum LuaTableKey {
9    IntKey(u64),
10    StringKey(String),
11}
12
13impl<'de> Deserialize<'de> for LuaTableKey {
14    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
15    where
16        D: serde::Deserializer<'de>,
17    {
18        let value = serde_json::Value::deserialize(deserializer)?;
19        if value.is_u64() {
20            Ok(LuaTableKey::IntKey(value.as_u64().unwrap()))
21        } else if value.is_string() {
22            Ok(LuaTableKey::StringKey(value.as_str().unwrap().into()))
23        } else {
24            Err(de::Error::custom(format!(
25                "Could not parse Lua table key. Expected an integer or string, but got {}",
26                value
27            )))
28        }
29    }
30}
31
32/// Deserialize a json value into a Vec<T>, treating empty json objects as empty lists
33/// If the json value is a string, this returns a singleton vector containing that value.
34/// This is needed to be able to deserialise RockSpec tables that luarocks
35/// also allows to be strings.
36pub fn deserialize_vec_from_lua_array_or_string<'de, D, T>(
37    deserializer: D,
38) -> std::result::Result<Vec<T>, D::Error>
39where
40    D: Deserializer<'de>,
41    T: From<String>,
42{
43    let values = serde_json::Value::deserialize(deserializer)?;
44    if values.is_string() {
45        let value = T::from(values.as_str().unwrap().into());
46        Ok(vec![value])
47    } else {
48        mlua_json_value_to_vec(values).map_err(de::Error::custom)
49    }
50}
51
52#[derive(Error, Debug)]
53#[error("expected list of strings")]
54pub struct ExpectedListOfStrings;
55
56#[derive(Error, Debug)]
57#[error("expected table with strings as keys")]
58pub struct ExpectedTableOfStrings;
59
60/// Convert a json value into a HashMap<String, T>.
61/// This is needed to be able to deserialise Lua tables.
62pub fn mlua_json_value_to_map<T>(
63    values: serde_json::Value,
64) -> Result<HashMap<String, T>, ExpectedTableOfStrings>
65where
66    T: From<String>,
67{
68    values
69        .as_object()
70        .ok_or(ExpectedTableOfStrings)?
71        .clone()
72        .into_iter()
73        .map(|(key, value)| {
74            let value: String = value
75                .as_str()
76                .map(|s| s.into())
77                .ok_or(ExpectedTableOfStrings)?;
78
79            Ok((key, value.into()))
80        })
81        .try_collect()
82}
83
84/// Convert a json value into a Vec<T>, treating empty json objects as empty lists
85/// This is needed to be able to deserialise Lua tables.
86pub fn mlua_json_value_to_vec<T>(values: serde_json::Value) -> Result<Vec<T>, ExpectedListOfStrings>
87where
88    T: From<String>,
89{
90    // If we deserialise an empty Lua table, mlua treats it as a dictionary.
91    // This case is handled here.
92    if let Some(values_as_obj) = values.as_object() {
93        if values_as_obj.is_empty() {
94            return Ok(Vec::default());
95        }
96    }
97    values
98        .as_array()
99        .ok_or(ExpectedListOfStrings)?
100        .iter()
101        .map(|val| {
102            let str: String = val
103                .as_str()
104                .map(|s| s.into())
105                .ok_or(ExpectedListOfStrings)?;
106            Ok(str.into())
107        })
108        .try_collect()
109}
110
111pub(crate) enum DisplayLuaValue {
112    // NOTE(vhyrro): these are not used in the current implementation
113    // Nil,
114    // Number(f64),
115    Boolean(bool),
116    String(String),
117    List(Vec<Self>),
118    Table(Vec<DisplayLuaKV>),
119}
120
121pub(crate) struct DisplayLuaKV {
122    pub(crate) key: String,
123    pub(crate) value: DisplayLuaValue,
124}
125
126impl Display for DisplayLuaValue {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        match self {
129            //DisplayLuaValue::Nil => write!(f, "nil"),
130            //DisplayLuaValue::Number(n) => write!(f, "{}", n),
131            DisplayLuaValue::Boolean(b) => write!(f, "{}", b),
132            DisplayLuaValue::String(s) => write!(f, "\"{}\"", s),
133            DisplayLuaValue::List(l) => {
134                writeln!(f, "{{")?;
135
136                for item in l {
137                    writeln!(f, "{},", item)?;
138                }
139
140                write!(f, "}}")?;
141
142                Ok(())
143            }
144            DisplayLuaValue::Table(t) => {
145                writeln!(f, "{{")?;
146
147                for item in t {
148                    writeln!(f, "{},", item)?;
149                }
150
151                write!(f, "}}")?;
152
153                Ok(())
154            }
155        }
156    }
157}
158
159impl Display for DisplayLuaKV {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        if !self
162            .key
163            .chars()
164            .all(|c| c == '_' || c.is_ascii_alphanumeric())
165        {
166            write!(f, "['{}'] = {}", self.key, self.value)
167        } else {
168            write!(f, "{} = {}", self.key, self.value)
169        }
170    }
171}
172
173/// Trait for serializing a Lua structure from a rockspec into a `key = value` pair.
174pub(crate) trait DisplayAsLuaKV {
175    fn display_lua(&self) -> DisplayLuaKV;
176}
177
178pub(crate) trait DisplayAsLuaValue {
179    fn display_lua_value(&self) -> DisplayLuaValue;
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn display_lua_value() {
188        let value = DisplayLuaValue::String("hello".to_string());
189        assert_eq!(format!("{}", value), "\"hello\"");
190
191        let value = DisplayLuaValue::Boolean(true);
192        assert_eq!(format!("{}", value), "true");
193
194        let value = DisplayLuaValue::List(vec![
195            DisplayLuaValue::String("hello".to_string()),
196            DisplayLuaValue::Boolean(true),
197        ]);
198        assert_eq!(format!("{}", value), "{\n\"hello\",\ntrue,\n}");
199
200        let value = DisplayLuaValue::Table(vec![
201            DisplayLuaKV {
202                key: "key".to_string(),
203                value: DisplayLuaValue::String("value".to_string()),
204            },
205            DisplayLuaKV {
206                key: "key2".to_string(),
207                value: DisplayLuaValue::Boolean(true),
208            },
209            DisplayLuaKV {
210                key: "key3.key4".to_string(),
211                value: DisplayLuaValue::Boolean(true),
212            },
213        ]);
214        assert_eq!(
215            format!("{}", value),
216            "{\nkey = \"value\",\nkey2 = true,\n['key3.key4'] = true,\n}"
217        );
218    }
219}