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
124
125
126
127
128
129
130
131
use crate::state::Transformation;
use oca_ast_transformation::ast;

#[derive(Debug, Clone, serde::Serialize)]
pub struct FromASTError {
    pub line_number: usize,
    pub raw_line: String,
    pub message: String,
}

#[derive(thiserror::Error, Debug, Clone, serde::Serialize)]
#[serde(untagged)]
pub enum Error {
    #[error("Error at line {line_number} ({raw_line}): {message}")]
    FromASTError {
        #[serde(rename = "ln")]
        line_number: usize,
        #[serde(rename = "c")]
        raw_line: String,
        #[serde(rename = "e")]
        message: String,
    },
}

pub fn from_ast(ast: &ast::TransformationAST) -> Result<Transformation, Vec<Error>> {
    let mut errors = vec![];

    let mut base: Option<Transformation> = None;
    let default_command_meta = ast::CommandMeta {
        line_number: 0,
        raw_line: "unknown".to_string(),
    };
    for (i, command) in ast.commands.iter().enumerate() {
        let command_index = i;
        // todo pass the references
        let command_meta = ast
            .commands_meta
            .get(&command_index)
            .unwrap_or(&default_command_meta);
        match apply_command(base.clone(), command.clone()) {
            Ok(transformation) => {
                base = Some(transformation);
            }
            Err(mut err) => {
                errors.extend(err.iter_mut().map(|e| Error::FromASTError {
                    line_number: command_meta.line_number,
                    raw_line: command_meta.raw_line.clone(),
                    message: e.clone(),
                }));
            }
        }
    }
    if errors.is_empty() {
        let mut transformation = base.unwrap().clone();
        transformation.fill_said();
        Ok(transformation)
    } else {
        Err(errors)
    }
}

pub fn apply_command(
    base: Option<Transformation>,
    op: ast::Command,
) -> Result<Transformation, Vec<String>> {
    let errors = vec![];
    let mut transformation: Transformation = match base {
        Some(transformation) => transformation,
        None => Transformation::new(),
    };

    match (op.kind, op.object_kind) {
        (ast::CommandType::Rename, ast::ObjectKind::Rename(content)) => {
            if let Some(attributes) = content.attributes {
                transformation.rename(attributes);
            }
        },
    }

    if errors.is_empty() {
        Ok(transformation)
    } else {
        Err(errors)
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use super::*;
    use indexmap::IndexMap;
    use said::{derivation::HashFunctionCode, sad::SerializationFormats, version::Encode};

    #[test]
    fn build_from_ast() {
        let mut commands = vec![];

        let mut attributes = IndexMap::new();
        attributes.insert("digest".to_string(), "d".to_string());

        commands.push(ast::Command {
            kind: ast::CommandType::Rename,
            object_kind: ast::ObjectKind::Rename(ast::RenameContent {
                attributes: Some(attributes),
            }),
        });

        let ast = ast::TransformationAST {
            version: "1.0".to_string(),
            commands,
            commands_meta: IndexMap::new(),
            meta: HashMap::new(),
        };

        let build_result = from_ast(&ast);
        match build_result {
            Ok(transformation) => {
                let code = HashFunctionCode::Blake3_256;
                let format = SerializationFormats::JSON;
                let transformation_encoded = transformation.encode(&code, &format).unwrap();
                let transformation_json =
                    String::from_utf8(transformation_encoded).unwrap();
                println!("{}", transformation_json);
            }
            Err(e) => {
                println!("{:?}", e);
            }
        }
    }
}