transformation_file/
build.rs

1use crate::state::Transformation;
2use oca_ast_transformation::ast;
3
4#[derive(Debug, Clone, serde::Serialize)]
5pub struct FromASTError {
6    pub line_number: usize,
7    pub raw_line: String,
8    pub message: String,
9}
10
11#[derive(thiserror::Error, Debug, Clone, serde::Serialize)]
12#[serde(untagged)]
13pub enum Error {
14    #[error("Error at line {line_number} ({raw_line}): {message}")]
15    FromASTError {
16        #[serde(rename = "ln")]
17        line_number: usize,
18        #[serde(rename = "c")]
19        raw_line: String,
20        #[serde(rename = "e")]
21        message: String,
22    },
23}
24
25pub fn from_ast(ast: &ast::TransformationAST) -> Result<Transformation, Vec<Error>> {
26    let mut errors = vec![];
27
28    let mut base: Option<Transformation> = None;
29    if !ast.meta.is_empty() {
30        let source = ast.meta.get("source").expect("missing source meta");
31        let source_said = source.replace("refs:", "");
32        let target = ast.meta.get("target").expect("missing target meta");
33        let target_said = target.replace("refs:", "");
34        let mut transformation = Transformation::new();
35        transformation.set_source(source_said);
36        transformation.set_target(target_said);
37        base = Some(transformation)
38    }
39
40    let default_command_meta = ast::CommandMeta {
41        line_number: 0,
42        raw_line: "unknown".to_string(),
43    };
44    for (i, command) in ast.commands.iter().enumerate() {
45        let command_index = i;
46        // todo pass the references
47        let command_meta = ast
48            .commands_meta
49            .get(&command_index)
50            .unwrap_or(&default_command_meta);
51        match apply_command(base.clone(), command.clone()) {
52            Ok(transformation) => {
53                base = Some(transformation);
54            }
55            Err(mut err) => {
56                errors.extend(err.iter_mut().map(|e| Error::FromASTError {
57                    line_number: command_meta.line_number,
58                    raw_line: command_meta.raw_line.clone(),
59                    message: e.clone(),
60                }));
61            }
62        }
63    }
64    if errors.is_empty() {
65        let mut transformation = base.unwrap().clone();
66        transformation.fill_said();
67        Ok(transformation)
68    } else {
69        Err(errors)
70    }
71}
72
73pub fn apply_command(
74    base: Option<Transformation>,
75    op: ast::Command,
76) -> Result<Transformation, Vec<String>> {
77    let errors = vec![];
78    let mut transformation: Transformation = match base {
79        Some(transformation) => transformation,
80        None => Transformation::new(),
81    };
82
83    match (op.kind, op.object_kind) {
84        (ast::CommandType::Rename, ast::ObjectKind::Rename(content)) => {
85            if let Some(attributes) = content.attributes {
86                transformation.rename(attributes);
87            }
88        }
89        (ast::CommandType::Link, ast::ObjectKind::Link(content)) => {
90            if let Some(attributes) = content.attributes {
91                transformation.link(attributes);
92            }
93        }
94        _ => {}
95    }
96
97    if errors.is_empty() {
98        Ok(transformation)
99    } else {
100        Err(errors)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use std::collections::HashMap;
107
108    use super::*;
109    use indexmap::IndexMap;
110    use said::{derivation::HashFunctionCode, sad::SerializationFormats, version::Encode};
111
112    #[test]
113    fn build_from_ast() {
114        let mut commands = vec![];
115
116        let mut attributes = IndexMap::new();
117        attributes.insert("digest".to_string(), "d".to_string());
118
119        commands.push(ast::Command {
120            kind: ast::CommandType::Rename,
121            object_kind: ast::ObjectKind::Rename(ast::RenameContent {
122                attributes: Some(attributes),
123            }),
124        });
125
126        let ast = ast::TransformationAST {
127            version: "1.0".to_string(),
128            commands,
129            commands_meta: IndexMap::new(),
130            meta: HashMap::new(),
131        };
132
133        let build_result = from_ast(&ast);
134        match build_result {
135            Ok(transformation) => {
136                let code = HashFunctionCode::Blake3_256;
137                let format = SerializationFormats::JSON;
138                let transformation_encoded = transformation.encode(&code, &format).unwrap();
139                let transformation_json = String::from_utf8(transformation_encoded).unwrap();
140                println!("{}", transformation_json);
141            }
142            Err(e) => {
143                println!("{:?}", e);
144            }
145        }
146    }
147}