1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::{collections::HashMap, fmt::Display, path::Path};

use cache::Cache;

pub(crate) mod binding;
pub(crate) mod cache;
pub(crate) mod codegen;
pub mod jsonrpc;
pub(crate) mod openrpc;
pub(crate) mod renders;

pub trait AsPath: AsRef<Path> + Display {}

impl AsPath for String {}
impl AsPath for &str {}

fn parse<P: AsPath>(path: &P) -> openrpc::OpenRpc {
    log::info!("Processing file: {path}");
    let json = std::fs::read_to_string(path).expect("JSON file exists and is readable.");
    serde_json::from_str(&json).expect("json")
}

pub fn gen_json<P: AsPath>(path: &P) -> String {
    let spec = parse(path);
    serde_json::to_string(&spec).expect("json")
}

pub fn gen_tree<P: AsPath>(paths: &[P]) -> String {
    let mut cache = Cache::new();
    let mut trace = Vec::with_capacity(32);

    let contracts = paths
        .iter()
        .map(|path| parse(path))
        .flat_map(|spec| binding::extract_contracts(&spec, &mut cache, &mut trace))
        .collect::<Vec<_>>();

    let mut target = String::new();
    use std::fmt::Write;

    cache
        .data
        .iter()
        .for_each(|(name, binding)| writeln!(target, "---\n{name}: {binding:#?}").unwrap());

    contracts
        .iter()
        .for_each(|contract| writeln!(target, "---\n{contract:#?}").unwrap());

    target
}

pub fn gen_code<P: AsPath>(paths: &[P]) -> String {
    let mut cache = Cache::new();
    let mut trace = Vec::with_capacity(32);

    let specs = paths.iter().map(|path| parse(path)).collect::<Vec<_>>();

    let errors = specs
        .iter()
        .flat_map(|spec| {
            spec.components
                .as_ref()
                .map(|comps| comps.errors.clone())
                .unwrap_or_default()
        })
        .collect::<HashMap<_, _>>();

    let contracts = specs
        .iter()
        .flat_map(|spec| binding::extract_contracts(spec, &mut cache, &mut trace))
        .collect::<Vec<_>>();

    let mut target = String::new();
    use std::fmt::Write;

    writeln!(target, "// vvv GENERATED CODE BELOW vvv").unwrap();
    writeln!(target, "#[allow(dead_code)]").unwrap();
    writeln!(target, "#[allow(non_snake_case)]").unwrap();
    writeln!(target, "#[allow(unused_variables)]").unwrap();
    writeln!(target, "#[allow(clippy::enum_variant_names)]").unwrap();
    writeln!(target, "pub mod gen {{").unwrap();
    writeln!(target, "use serde::{{Deserialize, Serialize}};").unwrap();
    writeln!(target, "use serde_json::Value;").unwrap();
    writeln!(target, "\nuse iamgroot::jsonrpc;").unwrap();

    let mut ordered = cache.data.iter().collect::<Vec<_>>();
    ordered.sort_by_key(|(name, _)| *name);

    for (name, binding) in ordered {
        let code = renders::render_object(name, binding)
            .unwrap_or_else(|e| format!("// ERROR: Rendering object '{name}' failed. {e}"));

        if !code.is_empty() {
            writeln!(target, "\n// object: '{name}'\n{code}").unwrap();
        }
    }

    writeln!(target, "\npub trait Rpc {{").unwrap();
    for contract in &contracts {
        let code = renders::render_method(&contract.name, contract);
        writeln!(target, "\n{code}").unwrap();
    }
    writeln!(target, "}}").unwrap();

    for contract in &contracts {
        let code = renders::render_method_handler(&contract.name, contract);
        writeln!(target, "{code}").unwrap();
    }

    let handler = renders::render_handle_function(&contracts);
    writeln!(target, "{handler}").unwrap();

    writeln!(target, "{}", renders::render_errors(errors)).unwrap();

    writeln!(target, "{}", renders::render_client(&contracts)).unwrap();

    writeln!(target, "}}").unwrap();
    writeln!(target, "// ^^^ GENERATED CODE ABOVE ^^^").unwrap();

    target
}