leo_bindings_core/
signature.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Deserialize)]
5struct InitialJson {
6    imports: Option<HashMap<String, serde_json::Value>>,
7    program_scopes: HashMap<String, ProgramScope>,
8}
9
10#[derive(Debug, Deserialize)]
11struct ProgramScope {
12    structs: Vec<(String, StructDef)>,
13    mappings: Vec<(String, MappingDef)>,
14    functions: Vec<(String, FunctionDef)>,
15}
16
17#[derive(Debug, Deserialize)]
18struct Identifier {
19    name: String,
20}
21
22#[derive(Debug, Deserialize)]
23struct StructDef {
24    identifier: Identifier,
25    members: Vec<StructMember>,
26    is_record: bool,
27}
28
29#[derive(Debug, Deserialize)]
30struct StructMember {
31    identifier: Identifier,
32    #[serde(rename = "type_")]
33    type_info: TypeInfo,
34    mode: String, // "None", "Public", "Private", "Constant"
35}
36
37#[derive(Debug, Deserialize)]
38struct FunctionDef {
39    identifier: Identifier,
40    variant: String,
41    input: Vec<Parameter>,
42    output: Vec<OutputParameter>,
43}
44
45#[derive(Debug, Deserialize)]
46struct Parameter {
47    identifier: Identifier,
48    #[serde(rename = "type_")]
49    type_info: TypeInfo,
50    mode: String,
51}
52
53#[derive(Debug, Deserialize)]
54struct OutputParameter {
55    #[serde(rename = "type_")]
56    type_info: TypeInfo,
57    mode: String,
58}
59
60#[derive(Debug, Deserialize)]
61#[serde(untagged)]
62enum TypeInfo {
63    Simple(String),
64    Integer {
65        #[serde(rename = "Integer")]
66        integer: String,
67    },
68    Composite {
69        #[serde(rename = "Composite")]
70        composite: CompositeType,
71    },
72    Array {
73        #[serde(rename = "Array")]
74        array: ArrayType,
75    },
76    Future {
77        #[serde(rename = "Future")]
78        #[allow(dead_code)]
79        future: FutureType,
80    },
81    Tuple {
82        #[serde(rename = "Tuple")]
83        tuple: TupleType,
84    },
85}
86
87#[derive(Debug, Deserialize)]
88struct CompositeType {
89    path: PathType,
90}
91
92#[derive(Debug, Deserialize)]
93struct PathType {
94    identifier: Identifier,
95}
96
97#[derive(Debug, Deserialize)]
98struct ArrayType {
99    element_type: Box<TypeInfo>,
100    length: serde_json::Value,
101}
102
103#[derive(Debug, Deserialize)]
104struct MappingDef {
105    identifier: Identifier,
106    key_type: TypeInfo,
107    value_type: TypeInfo,
108}
109
110#[derive(Debug, Deserialize)]
111struct FutureType {
112    #[allow(dead_code)]
113    inputs: Vec<serde_json::Value>,
114    #[allow(dead_code)]
115    location: Option<serde_json::Value>,
116    #[allow(dead_code)]
117    is_explicit: bool,
118}
119
120#[derive(Debug, Deserialize)]
121struct TupleType {
122    elements: Vec<TypeInfo>,
123}
124
125#[derive(Debug, Serialize, Deserialize)]
126pub struct SimplifiedBindings {
127    pub program_name: String,
128    pub imports: Vec<String>,
129    pub records: Vec<StructBinding>,
130    pub structs: Vec<StructBinding>,
131    pub mappings: Vec<MappingBinding>,
132    pub functions: Vec<FunctionBinding>,
133}
134
135#[derive(Debug, Serialize, Deserialize)]
136pub struct MemberDef {
137    pub name: String,
138    #[serde(rename = "type")]
139    pub type_name: String,
140    pub mode: String,
141}
142
143#[derive(Debug, Serialize, Deserialize)]
144pub struct FunctionBinding {
145    pub name: String,
146    pub inputs: Vec<InputParam>,
147    pub outputs: Vec<OutputType>,
148    pub is_async: bool,
149}
150
151#[derive(Debug, Serialize, Deserialize)]
152pub struct StructBinding {
153    pub name: String,
154    pub members: Vec<MemberDef>,
155    pub is_record: bool,
156}
157
158#[derive(Debug, Serialize, Deserialize)]
159pub struct MappingBinding {
160    pub name: String,
161    pub key_type: String,
162    pub value_type: String,
163}
164
165#[derive(Debug, Serialize, Deserialize)]
166pub struct InputParam {
167    pub name: String,
168    #[serde(rename = "type")]
169    pub type_name: String,
170    pub mode: String,
171}
172
173#[derive(Debug, Serialize, Deserialize)]
174pub struct OutputType {
175    #[serde(rename = "type")]
176    pub type_name: String,
177    pub mode: String,
178}
179
180pub fn get_signatures(input: String) -> String {
181    let initial_json: InitialJson = serde_json::from_str(&input).unwrap();
182
183    let imports: Vec<String> = initial_json
184        .imports
185        .as_ref()
186        .map(|imports_map| imports_map.keys().cloned().collect())
187        .unwrap_or_default();
188
189    let (program_name, program_scope) = initial_json.program_scopes.into_iter().next().unwrap();
190
191    let (records, structs): (Vec<StructBinding>, Vec<StructBinding>) = program_scope
192        .structs
193        .into_iter()
194        .map(|(_, struct_def)| {
195            let members = struct_def
196                .members
197                .into_iter()
198                .map(|member| MemberDef {
199                    name: member.identifier.name,
200                    type_name: normalize_type(&member.type_info),
201                    mode: member.mode,
202                })
203                .collect();
204
205            StructBinding {
206                name: struct_def.identifier.name,
207                members,
208                is_record: struct_def.is_record,
209            }
210        })
211        .partition(|binding| binding.is_record);
212
213    let functions: Vec<FunctionBinding> = program_scope
214        .functions
215        .into_iter()
216        .filter_map(|(_, func_def)| {
217            if func_def.variant == "Transition" || func_def.variant == "AsyncTransition" {
218                let inputs = func_def
219                    .input
220                    .into_iter()
221                    .map(|param| InputParam {
222                        name: param.identifier.name,
223                        type_name: normalize_type(&param.type_info),
224                        mode: param.mode,
225                    })
226                    .collect();
227
228                let outputs: Vec<OutputType> = func_def
229                    .output
230                    .into_iter()
231                    .map(|output| OutputType {
232                        type_name: normalize_type(&output.type_info),
233                        mode: output.mode,
234                    })
235                    .collect();
236
237                let is_async = func_def.variant == "AsyncTransition";
238
239                if is_async {
240                    if outputs.is_empty() {
241                        panic!("Async function '{}' must have at least a Future output", func_def.identifier.name);
242                    }
243                    let last_output = &outputs[outputs.len() - 1];
244                    if last_output.type_name != "Future" {
245                        panic!("Async function '{}' must have Future as the last output, but found '{}'", func_def.identifier.name, last_output.type_name);
246                    }
247                }
248
249                Some(FunctionBinding {
250                    name: func_def.identifier.name,
251                    inputs,
252                    outputs,
253                    is_async,
254                })
255            } else {
256                None
257            }
258        })
259        .collect();
260
261    let mappings: Vec<MappingBinding> = program_scope
262        .mappings
263        .into_iter()
264        .map(|(_, mapping_def)| MappingBinding {
265            name: mapping_def.identifier.name,
266            key_type: normalize_type(&mapping_def.key_type),
267            value_type: normalize_type(&mapping_def.value_type),
268        })
269        .collect();
270
271    let simplified = SimplifiedBindings {
272        program_name,
273        imports,
274        records,
275        structs,
276        mappings,
277        functions,
278    };
279
280    serde_json::to_string_pretty(&simplified).unwrap()
281}
282
283fn normalize_type(type_info: &TypeInfo) -> String {
284    match type_info {
285        TypeInfo::Simple(s) => s.clone(),
286        TypeInfo::Integer { integer: int_type } => match int_type.as_str() {
287            "U8" => "u8".to_string(),
288            "U16" => "u16".to_string(),
289            "U32" => "u32".to_string(),
290            "U64" => "u64".to_string(),
291            "U128" => "u128".to_string(),
292            "I8" => "i8".to_string(),
293            "I16" => "i16".to_string(),
294            "I32" => "i32".to_string(),
295            "I64" => "i64".to_string(),
296            "I128" => "i128".to_string(),
297            _ => format!("Unknown_Integer_{}", int_type),
298        },
299        TypeInfo::Composite { composite: comp } => comp.path.identifier.name.clone(),
300        TypeInfo::Array { array } => {
301            let element_type = normalize_type(&array.element_type);
302            let size = extract_array_size(&array.length);
303            format!("[{}; {}]", element_type, size)
304        }
305        TypeInfo::Future { .. } => "Future".to_string(),
306        TypeInfo::Tuple { tuple } => {
307            let element_types: Vec<String> = tuple.elements.iter().map(normalize_type).collect();
308            format!("({})", element_types.join(", "))
309        }
310    }
311}
312
313fn extract_array_size(length_json: &serde_json::Value) -> String {
314    // Extract size from JSON structure like:
315    // "length": {
316    //   "Literal": {
317    //     "id": 63,
318    //     "variant": {
319    //       "Integer": ["U8", "5"]
320    //     }
321    //   }
322    // }
323
324    if let Some(literal) = length_json.get("Literal") {
325        if let Some(variant) = literal.get("variant") {
326            if let Some(integer_array) = variant.get("Integer") {
327                if let Some(array) = integer_array.as_array() {
328                    if array.len() == 2 {
329                        if let Some(size_str) = array[1].as_str() {
330                            let size = size_str
331                                .trim_end_matches("u8")
332                                .trim_end_matches("u16")
333                                .trim_end_matches("u32")
334                                .trim_end_matches("u64")
335                                .trim_end_matches("u128")
336                                .trim_end_matches("i8")
337                                .trim_end_matches("i16")
338                                .trim_end_matches("i32")
339                                .trim_end_matches("i64")
340                                .trim_end_matches("i128");
341                            return size.to_string();
342                        }
343                    }
344                }
345            } else if let Some(unsuffixed_str) = variant.get("Unsuffixed") {
346                if let Some(size_str) = unsuffixed_str.as_str() {
347                    return size_str.to_string();
348                }
349            }
350        }
351    }
352    "UNKNOWN_SIZE".to_string()
353}