1use serde::{Deserialize, Serialize};
7use surrealdb_types::Kind;
8use surrealism_types::err::{PrefixErr, SurrealismResult};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ExportsManifest {
12 pub functions: Vec<FunctionExport>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct FunctionExport {
17 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub name: Option<String>,
20 #[serde(with = "hex_argument_list")]
22 pub args: Vec<(String, Kind)>,
23 #[serde(with = "hex_kind")]
24 pub returns: Kind,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub args_text: Option<Vec<String>>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub returns_text: Option<String>,
29 #[serde(default)]
32 pub writeable: bool,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub comment: Option<String>,
37}
38
39impl FunctionExport {
40 pub fn args_display(&self) -> String {
41 self.args_text.as_ref().map(|v| v.join(", ")).unwrap_or_else(|| {
42 self.args
43 .iter()
44 .map(|(name, kind)| format!("{name}: {kind}"))
45 .collect::<Vec<_>>()
46 .join(", ")
47 })
48 }
49
50 pub fn returns_display(&self) -> String {
51 self.returns_text
52 .as_deref()
53 .map(|s| s.to_string())
54 .unwrap_or_else(|| format!("{}", self.returns))
55 }
56}
57
58impl ExportsManifest {
59 pub fn empty() -> Self {
62 Self {
63 functions: Vec::new(),
64 }
65 }
66
67 pub fn parse(s: &str) -> SurrealismResult<Self> {
68 toml::from_str(s).prefix_err(|| "Failed to parse exports manifest")
69 }
70
71 pub fn to_toml(&self) -> SurrealismResult<String> {
72 toml::to_string(self).prefix_err(|| "Failed to serialize exports manifest")
73 }
74
75 pub fn get_signature(&self, name: Option<&str>) -> Option<&FunctionExport> {
77 self.functions.iter().find(|f| f.name.as_deref() == name)
78 }
79}
80
81mod hex_kind {
82 use serde::{Deserialize, Deserializer, Serializer};
83 use surrealdb_types::Kind;
84
85 pub fn serialize<S: Serializer>(kind: &Kind, serializer: S) -> Result<S::Ok, S::Error> {
86 let bytes = surrealdb_types::encode_kind(kind).map_err(serde::ser::Error::custom)?;
87 serializer.serialize_str(&hex::encode(bytes))
88 }
89
90 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Kind, D::Error> {
91 let s = String::deserialize(deserializer)?;
92 let bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
93 surrealdb_types::decode_kind(&bytes).map_err(serde::de::Error::custom)
94 }
95}
96
97mod hex_argument_list {
98 use serde::{Deserialize, Deserializer, Serializer};
99 use surrealdb_types::Kind;
100
101 pub fn serialize<S: Serializer>(
102 args: &[(String, Kind)],
103 serializer: S,
104 ) -> Result<S::Ok, S::Error> {
105 let pairs: Vec<(&str, Kind)> = args.iter().map(|(n, k)| (n.as_str(), k.clone())).collect();
106 let bytes =
107 surrealdb_types::encode_argument_list(&pairs).map_err(serde::ser::Error::custom)?;
108 serializer.serialize_str(&hex::encode(bytes))
109 }
110
111 pub fn deserialize<'de, D: Deserializer<'de>>(
112 deserializer: D,
113 ) -> Result<Vec<(String, Kind)>, D::Error> {
114 let s = String::deserialize(deserializer)?;
115 let bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
116 surrealdb_types::decode_argument_list(&bytes).map_err(serde::de::Error::custom)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn roundtrip_manifest() {
126 let manifest = ExportsManifest {
127 functions: vec![
128 FunctionExport {
129 name: None,
130 args: vec![("value".to_string(), Kind::Int)],
131 returns: Kind::Bool,
132 args_text: Some(vec!["value: int".to_string()]),
133 returns_text: Some("bool".to_string()),
134 writeable: false,
135 comment: Some("Checks whether a value is valid.".to_string()),
136 },
137 FunctionExport {
138 name: Some("foo::bar".to_string()),
139 args: vec![
140 ("input".to_string(), Kind::String),
141 ("tags".to_string(), Kind::Array(Box::new(Kind::String), None)),
142 ],
143 returns: Kind::Object,
144 args_text: None,
145 returns_text: None,
146 writeable: true,
147 comment: None,
148 },
149 ],
150 };
151
152 let toml_str = manifest.to_toml().unwrap();
153 let parsed = ExportsManifest::parse(&toml_str).unwrap();
154 assert_eq!(manifest.functions.len(), parsed.functions.len());
155
156 assert!(parsed.functions[0].name.is_none());
157 assert_eq!(parsed.functions[0].args, vec![("value".to_string(), Kind::Int)]);
158 assert_eq!(parsed.functions[0].returns, Kind::Bool);
159 assert!(!parsed.functions[0].writeable);
160 assert_eq!(
161 parsed.functions[0].comment.as_deref(),
162 Some("Checks whether a value is valid.")
163 );
164
165 assert_eq!(parsed.functions[1].name.as_deref(), Some("foo::bar"));
166 assert_eq!(parsed.functions[1].args.len(), 2);
167 assert_eq!(parsed.functions[1].args[0].0, "input");
168 assert_eq!(parsed.functions[1].args[1].0, "tags");
169 assert_eq!(parsed.functions[1].returns, Kind::Object);
170 assert!(parsed.functions[1].writeable);
171 assert!(parsed.functions[1].comment.is_none());
172 }
173
174 #[test]
175 fn get_signature_default() {
176 let manifest = ExportsManifest {
177 functions: vec![FunctionExport {
178 name: None,
179 args: vec![("n".to_string(), Kind::Int)],
180 returns: Kind::Bool,
181 args_text: None,
182 returns_text: None,
183 writeable: false,
184 comment: None,
185 }],
186 };
187
188 assert!(manifest.get_signature(None).is_some());
189 assert!(manifest.get_signature(Some("nonexistent")).is_none());
190 }
191
192 #[test]
193 fn hex_roundtrip() {
194 let bytes = vec![0x0c, 0x00, 0xff, 0xab];
195 let encoded = hex::encode(&bytes);
196 assert_eq!(encoded, "0c00ffab");
197 let decoded = hex::decode(&encoded).unwrap();
198 assert_eq!(bytes, decoded);
199 }
200}