Skip to main content

noi_core/
export.rs

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/// Parsed metadata for a single Noir `#[export]` function.
13#[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/// Function visibility marker as reported by the Noir compiler.
38#[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/// A single parameter entry in the exported ABI.
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Param {
54    pub name: String,
55    pub ty: TypeRepr,
56    pub visibility: Visibility,
57}
58
59/// Structured representation of Noir ABI types supported by `noi`.
60#[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/// Definition of a Noir struct type.
99#[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/// A single field inside a struct type.
115#[derive(Clone, Debug, PartialEq, Eq, Hash)]
116pub struct StructField {
117    pub name: String,
118    pub ty: TypeRepr,
119}
120
121/// Load every JSON artifact inside `path` and parse it into [`ExportFunction`]s.
122pub 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(&param.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}