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
123
use std::{fs::File, io::Read, path::Path, process::Command};
use anyhow::Result;
use serde::{Deserialize, Serialize};

use crate::{error::ParserError, MercuryClient};


impl Config {
    fn tables(&self) -> Vec<Table> {
        self.tables.clone().unwrap_or(vec![])
    }
}


#[derive(Deserialize, Serialize, Clone)]
pub struct Config {
    pub name: String,

    /// Tables that the poject is writing or reading.
    pub tables: Option<Vec<Table>>,
}

#[derive(Deserialize, Serialize, Clone)]
pub struct Table {
    pub name: String,
    pub columns: Vec<Column>
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Column {
    pub name: String,
    pub col_type: String
}

pub struct ZephyrProjectParser {
    config: Config,
    client: MercuryClient
}

impl ZephyrProjectParser {
    pub fn from_path<P: AsRef<Path>>(client: MercuryClient, path: P) -> Result<Self> {
        let project_definition = {
            let mut content = String::new();
            File::open(path)?.read_to_string(&mut content)?;

            content
        };

        let parser = Self {
            client,
            config: toml::from_str(&project_definition)?
        };

        Ok(parser)
    }

    pub fn build_wasm(&self) -> Result<()> {
        let output = Command::new("cargo")
        .args(&["build", "--release", "--target=wasm32-unknown-unknown"])
        .output()?;

        if !output.status.success() {
            let error = if !output.stdout.is_empty() {
                String::from_utf8_lossy(&output.stdout).to_string()
            } else {
                String::new()
            };

            return Err(ParserError::WasmBuildError(error).into())
        }

        Ok(())
    }

    pub async fn deploy_tables(&self) -> Result<()> {
        for table in self.config.tables() {
            if let Err(_) = self.client.new_table(table).await {
                return Err(ParserError::TableCreationError.into())
            };
        }

        Ok(())
    }

    pub async fn deploy_wasm(&self, target: Option<String>) -> Result<()> {
        let project_name = &self.config.name;
        let path = if let Some(target_dir) = target {
            format!("{}/{}.wasm", target_dir, project_name.replace('-', "_"))
        } else {
            format!("./target/wasm32-unknown-unknown/release/{}.wasm", project_name.replace('-', "_"))
        };
        
        if let Err(_) = self.client.deploy(path, true).await {
            return Err(ParserError::WasmDeploymentError.into());
        };

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::{Column, Config, Table};

    #[test]
    pub fn sample_config() {
        let config = Config {
            name: "zephyr-soroban-op-ratio".into(),
            tables: Some(vec![Table {
                name: "opratio".into(),
                columns: vec![Column {
                    name: "soroban".into(),
                    col_type: "BYTEA".into() // only supported type as of now 
                }, Column {
                    name: "ratio".into(),
                    col_type: "BYTEA".into() // only supported type as of now 
                }]
            }])
        };

        println!("{}", toml::to_string(&config).unwrap());
    }
}