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