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

use std::{path::{Path, PathBuf}, error::Error, collections::{VecDeque, HashSet}, fs, fmt::format};
use anyhow::{bail, Result};
use cronus_spec::RawSpec;

pub mod api_parse;
pub mod api_parser;

pub fn from_yaml(file: &Path) -> Result<RawSpec> {
    let contents = fs::read_to_string(file)?;
    from_yaml_str(&contents)
}

pub fn from_api(file: &Path) -> Result<RawSpec> {
    let contents = fs::read_to_string(file)?;
    api_parse::parse(PathBuf::from(file), &contents)
}

#[tracing::instrument]
pub fn from_file(file: &Path, resolve_import: bool, search_paths: Option<&Vec<PathBuf>>) -> Result<RawSpec> {
    match file.extension() {
        Some(ext) => {
            match ext.to_str() {
                Some(ext_str) => {
                    let mut spec: RawSpec;
                    if ext_str.ends_with("yaml") || ext_str.ends_with("yml") {
                        spec = from_yaml(file)?;
                    } 
                    else if ext_str.ends_with("api")  {
                        spec = from_api(file)?;
                    } else {
                        bail!("unsupported file extension '{:?}', expect .yaml, .yml or .api", ext)
                    }
                    
                    if resolve_import {
                        let mut explored: HashSet<String> = Default::default();
                        resolve_imports(&mut spec, &mut explored, file.parent().unwrap(), search_paths)?;
                    }
                    
                    Ok(spec)
                },
                None => bail!("unsupported file extension '{:?}', expect .yaml, .yml or .api", ext),
            }
            
        },
        None => bail!("no file extension, expect .yaml, .yml or .api"),
    }
    
}

pub fn from_yaml_str(str: &str) -> Result<RawSpec> {
    let spec: RawSpec = serde_yaml::from_str(&str)?;
    Ok(spec)
}


pub fn resolve_imports(spec: &mut RawSpec, explored: &mut HashSet<String>, spec_parent:&Path, search_paths: Option<&Vec<PathBuf>>) -> Result<()> {

    for import in spec.imports.clone().into_iter().flatten() {
        if explored.contains(&import) {
            continue
        }
        explored.insert(import.clone());
        let import_path = get_import_path(&import, spec_parent,search_paths)?;
        let imported_spec = from_file(&import_path, true, search_paths)?;

        spec.merge(imported_spec)?
    }

    Ok(())
}

#[tracing::instrument]
fn get_import_path(import: &str, default_path:&Path, available_paths: Option<&Vec<PathBuf>>) -> Result<PathBuf> {
    let defualt_relative =  default_path.join(import);
    if defualt_relative.exists() {
        return Ok(defualt_relative)
    }

    if let Some(paths) = available_paths {
        for p in paths {
            let candidate = p.join(import);
            if candidate.exists() {
                return Ok(candidate)
            }
        }
    }

    bail!("no available file found for import '{}'", import)
}