1use std::{
2 fmt,
3 path::{Path, PathBuf},
4};
5
6use anyhow::{anyhow, Context, Result};
7use fs_err as fs;
8use serde::Deserialize;
9use serde_json::Value;
10use walkdir::WalkDir;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct ExportFunction {
15 pub name: String,
16 pub parameters: Vec<Param>,
17 pub return_type: Option<TypeRepr>,
18 pub visibility: Visibility,
19 pub source_path: PathBuf,
20}
21
22impl ExportFunction {
23 pub fn signature(&self) -> String {
24 let params = self
25 .parameters
26 .iter()
27 .map(|param| format!("{}: {}", param.name, param.ty))
28 .collect::<Vec<_>>()
29 .join(", ");
30 match &self.return_type {
31 Some(ret) => format!("fn {}({}) -> {}", self.name, params, ret),
32 None => format!("fn {}({})", self.name, params),
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize)]
39#[serde(rename_all = "lowercase")]
40pub enum Visibility {
41 Public,
42 Private,
43}
44
45impl Default for Visibility {
46 fn default() -> Self {
47 Visibility::Private
48 }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Param {
54 pub name: String,
55 pub ty: TypeRepr,
56 pub visibility: Visibility,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Hash)]
61pub enum TypeRepr {
62 Bool,
63 Field,
64 Unsigned(u16),
65 Signed(u16),
66 Array(Box<TypeRepr>, usize),
67 Tuple(Vec<TypeRepr>),
68 Struct(StructType),
69}
70
71impl TypeRepr {
72 pub fn is_struct(&self) -> bool {
73 matches!(self, TypeRepr::Struct(_))
74 }
75}
76
77impl fmt::Display for TypeRepr {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 TypeRepr::Bool => write!(f, "bool"),
81 TypeRepr::Field => write!(f, "Field"),
82 TypeRepr::Unsigned(bits) => write!(f, "u{}", bits),
83 TypeRepr::Signed(bits) => write!(f, "i{}", bits),
84 TypeRepr::Array(elem, len) => write!(f, "[{}; {}]", elem, len),
85 TypeRepr::Tuple(values) => {
86 let repr = values
87 .iter()
88 .map(|v| v.to_string())
89 .collect::<Vec<_>>()
90 .join(", ");
91 write!(f, "({repr})")
92 }
93 TypeRepr::Struct(struct_ty) => struct_ty.fmt(f),
94 }
95 }
96}
97
98#[derive(Clone, Debug, PartialEq, Eq, Hash)]
100pub struct StructType {
101 pub name: Option<String>,
102 pub fields: Vec<StructField>,
103}
104
105impl fmt::Display for StructType {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match &self.name {
108 Some(name) => write!(f, "struct {}", name),
109 None => write!(f, "struct"),
110 }
111 }
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, Hash)]
116pub struct StructField {
117 pub name: String,
118 pub ty: TypeRepr,
119}
120
121pub fn load_export_dir(path: impl AsRef<Path>) -> Result<Vec<ExportFunction>> {
123 let path = path.as_ref();
124
125 if !path.exists() {
126 return Err(anyhow!(
127 "export directory `{}` does not exist",
128 path.display()
129 ));
130 }
131
132 let mut functions = Vec::new();
133
134 for entry in WalkDir::new(path)
135 .follow_links(true)
136 .into_iter()
137 .filter_map(|e| e.ok())
138 {
139 if !entry.file_type().is_file() {
140 continue;
141 }
142 if entry
143 .path()
144 .extension()
145 .and_then(|ext| ext.to_str())
146 .map(|ext| ext != "json")
147 .unwrap_or(true)
148 {
149 continue;
150 }
151
152 let contents = fs::read_to_string(entry.path())
153 .with_context(|| format!("failed to read export JSON `{}`", entry.path().display()))?;
154
155 let artifact: RawExport = serde_json::from_str(&contents)
156 .with_context(|| format!("failed to parse export JSON `{}`", entry.path().display()))?;
157
158 if artifact.abi.parameters.is_empty() && artifact.abi.return_type.is_none() {
159 continue;
160 }
161
162 let params = artifact
163 .abi
164 .parameters
165 .into_iter()
166 .map(|param| -> Result<_> {
167 let ty = parse_type(¶m.r#type).with_context(|| {
168 format!("failed to parse type for parameter `{}`", param.name)
169 })?;
170 Ok(Param {
171 name: param.name,
172 ty,
173 visibility: param.visibility.unwrap_or_default(),
174 })
175 })
176 .collect::<Result<Vec<_>>>()?;
177
178 let return_type = match artifact.abi.return_type {
179 Some(ret) => {
180 let ty_value = ret
181 .abi_type
182 .as_ref()
183 .ok_or_else(|| anyhow!("missing `abi_type` for return type"))?;
184 Some(parse_type(ty_value)?)
185 }
186 None => None,
187 };
188
189 let name = artifact
190 .name
191 .clone()
192 .or_else(|| artifact.function.clone())
193 .unwrap_or_else(|| {
194 entry
195 .path()
196 .file_stem()
197 .and_then(|stem| stem.to_str())
198 .unwrap_or_default()
199 .to_string()
200 });
201
202 let visibility = artifact.visibility.unwrap_or_default();
203
204 functions.push(ExportFunction {
205 name,
206 parameters: params,
207 return_type,
208 visibility,
209 source_path: entry.path().to_path_buf(),
210 });
211 }
212
213 functions.sort_by(|a, b| a.name.cmp(&b.name));
214
215 if functions.is_empty() {
216 return Err(anyhow!(
217 "no export artifacts found in `{}`; run `nargo export` first",
218 path.display()
219 ));
220 }
221
222 Ok(functions)
223}
224
225#[derive(Debug, Deserialize)]
226struct RawExport {
227 #[serde(default)]
228 name: Option<String>,
229 #[serde(default, rename = "function_name")]
230 function: Option<String>,
231 #[serde(default)]
232 visibility: Option<Visibility>,
233 abi: RawAbi,
234}
235
236#[derive(Debug, Deserialize)]
237struct RawAbi {
238 #[serde(default)]
239 parameters: Vec<RawParam>,
240 #[serde(default)]
241 return_type: Option<RawReturn>,
242}
243
244#[derive(Debug, Deserialize)]
245struct RawParam {
246 name: String,
247 #[serde(rename = "type")]
248 r#type: Value,
249 #[serde(default)]
250 visibility: Option<Visibility>,
251}
252
253#[derive(Debug, Deserialize)]
254struct RawReturn {
255 #[serde(default, rename = "abi_type")]
256 abi_type: Option<Value>,
257}
258
259fn parse_type(node: &Value) -> Result<TypeRepr> {
260 let kind = node
261 .get("kind")
262 .and_then(Value::as_str)
263 .ok_or_else(|| anyhow!("missing `kind` in ABI type"))?;
264
265 match kind {
266 "field" => Ok(TypeRepr::Field),
267 "boolean" => Ok(TypeRepr::Bool),
268 "unsigned" | "integer" => {
269 let width = node
270 .get("width")
271 .or_else(|| node.get("bit_size"))
272 .and_then(Value::as_u64)
273 .ok_or_else(|| anyhow!("missing integer width in abi type"))?
274 as u16;
275 let sign = node
276 .get("sign")
277 .and_then(Value::as_str)
278 .unwrap_or("unsigned");
279 match sign {
280 "unsigned" => Ok(TypeRepr::Unsigned(width)),
281 "signed" => Ok(TypeRepr::Signed(width)),
282 other => Err(anyhow!("unknown integer sign `{other}`")),
283 }
284 }
285 "signed" => {
286 let width = node
287 .get("width")
288 .and_then(Value::as_u64)
289 .ok_or_else(|| anyhow!("missing signed width"))? as u16;
290 Ok(TypeRepr::Signed(width))
291 }
292 "array" => {
293 let len = node
294 .get("length")
295 .or_else(|| node.get("len"))
296 .and_then(Value::as_u64)
297 .ok_or_else(|| anyhow!("missing array length"))? as usize;
298 let inner = node
299 .get("type")
300 .or_else(|| node.get("r#type"))
301 .ok_or_else(|| anyhow!("missing array element type"))?;
302 Ok(TypeRepr::Array(Box::new(parse_type(inner)?), len))
303 }
304 "tuple" => {
305 let fields = node
306 .get("fields")
307 .or_else(|| node.get("types"))
308 .and_then(Value::as_array)
309 .ok_or_else(|| anyhow!("tuple `fields` must be an array"))?;
310 let parsed = fields.iter().map(parse_type).collect::<Result<Vec<_>>>()?;
311 Ok(TypeRepr::Tuple(parsed))
312 }
313 "struct" => parse_struct(node),
314 other => Err(anyhow!("unsupported type kind `{other}`")),
315 }
316}
317
318fn parse_struct(node: &Value) -> Result<TypeRepr> {
319 let name = node
320 .get("path")
321 .or_else(|| node.get("name"))
322 .and_then(Value::as_str)
323 .map(|s| s.to_string());
324
325 let mut fields = Vec::new();
326
327 if let Some(array) = node.get("fields").and_then(Value::as_array) {
328 for field in array {
329 let field_name = field
330 .get("name")
331 .and_then(Value::as_str)
332 .ok_or_else(|| anyhow!("struct field missing name"))?;
333 let ty_node = field
334 .get("type")
335 .or_else(|| field.get("abi_type"))
336 .ok_or_else(|| anyhow!("struct field missing type"))?;
337 fields.push(StructField {
338 name: field_name.to_string(),
339 ty: parse_type(ty_node)?,
340 });
341 }
342 } else if let Some(map) = node.get("fields").and_then(Value::as_object) {
343 for (field_name, value) in map {
344 let ty_node = value
345 .get("type")
346 .or_else(|| value.get("abi_type"))
347 .ok_or_else(|| anyhow!("struct field missing type"))?;
348 fields.push(StructField {
349 name: field_name.clone(),
350 ty: parse_type(ty_node)?,
351 });
352 }
353 } else {
354 return Err(anyhow!("struct `fields` must be an array or object"));
355 }
356
357 Ok(TypeRepr::Struct(StructType { name, fields }))
358}