artifact_app/api/
utils.rs

1use dev_prefix::*;
2use jsonrpc_core::{Error as RpcError, ErrorCode, Params};
3use serde_json;
4
5use types::*;
6use export::ArtifactData;
7use user;
8use api::constants;
9
10pub fn invalid_params(desc: &str) -> RpcError {
11    RpcError {
12        code: ErrorCode::InvalidParams,
13        message: desc.to_string(),
14        data: None,
15    }
16}
17
18pub fn parse_error(desc: &str) -> RpcError {
19    RpcError {
20        code: ErrorCode::ParseError,
21        message: desc.to_string(),
22        data: None,
23    }
24}
25
26pub fn readonly_error() -> RpcError {
27    RpcError {
28        code: ErrorCode::MethodNotFound,
29        message: "method not available when readonly=true".to_string(),
30        data: None,
31    }
32}
33
34/// convert an artifact from it's data representation
35/// to it's internal artifact representation
36pub fn convert_artifact(
37    origin: &Path,
38    artifact_data: &ArtifactData,
39) -> result::Result<(NameRc, Artifact), String> {
40    Artifact::from_data(origin, artifact_data).map_err(|err| err.to_string())
41}
42
43/// pull out the artifacts from the params
44pub fn get_artifacts(params: Params) -> result::Result<Vec<ArtifactData>, RpcError> {
45    match params {
46        Params::Map(mut dict) => match dict.remove("artifacts") {
47            Some(value) => match serde_json::from_value::<Vec<ArtifactData>>(value) {
48                Ok(a) => Ok(a),
49                Err(e) => Err(parse_error(&format!("{}", e))),
50            },
51            None => Err(invalid_params("missing 'artifacts' param")),
52        },
53        _ => Err(invalid_params("params must have 'artifacts' key")),
54    }
55}
56
57pub fn from_data(
58    origin: &Path,
59    data: &ArtifactData,
60) -> result::Result<(NameRc, Artifact), RpcError> {
61    match convert_artifact(origin, data) {
62        Ok(v) => Ok(v),
63        Err(msg) => {
64            let e = RpcError {
65                code: constants::SERVER_ERROR,
66                message: format!(
67                    "Could not convert artifact back {:?}, GOT ERROR: {}",
68                    data,
69                    msg
70                ),
71                data: None,
72            };
73            Err(e)
74        }
75    }
76}
77
78pub fn dump_artifacts(project: &Project) -> result::Result<(), RpcError> {
79    // get the raw ProjectText for saving to disk
80    let text = match user::ProjectText::from_project(project) {
81        Ok(t) => t,
82        Err(e) => {
83            return Err(RpcError {
84                code: ErrorCode::InternalError,
85                message: format!("{}", e.display()),
86                data: None,
87            })
88        }
89    };
90
91    // save the ProjectText to files
92    if let Err(e) = text.dump() {
93        return Err(RpcError {
94            code: ErrorCode::InternalError,
95            message: format!("{}", e.display()),
96            data: None,
97        });
98    }
99    Ok(())
100}
101
102/// split artifacts into artifacts which are unchanged and
103/// artifacts which are changed.
104///
105/// Also do lots of error checking and validation
106///
107/// If create flag is true, this is for the Create command
108/// (only expect new artifacts). Otherwise it is for the
109/// Update command (only expect artifacts that exist).
110#[allow(useless_let_if_seq)]
111pub fn split_artifacts(
112    project: &Project,
113    data_artifacts: &[ArtifactData],
114    new_artifacts: &[ArtifactData],
115    for_create: bool,
116) -> result::Result<(HashMap<u64, ArtifactData>, Artifacts), RpcError> {
117    let mut unchanged_artifacts: HashMap<u64, ArtifactData> =
118        data_artifacts.iter().map(|a| (a.id, a.clone())).collect();
119
120    let mut save_artifacts: Artifacts = Artifacts::new();
121
122    // buffer errors to give better error messages
123    let mut files_not_found: Vec<PathBuf> = Vec::new();
124    let mut invalid_ids: Vec<u64> = Vec::new();
125    let mut invalid_revisions: Vec<u64> = Vec::new();
126    let mut name_errors: Vec<String> = Vec::new();
127    let mut name_overlap: Vec<String> = Vec::new();
128
129    for new_artifact in new_artifacts {
130        let path = project.origin.join(&new_artifact.def);
131        if !project.files.contains(&path) {
132            files_not_found.push(path);
133        }
134        if for_create {
135            // when creating, id and revision must == 0
136            if new_artifact.id != 0 {
137                invalid_ids.push(new_artifact.id)
138            }
139            if new_artifact.revision != 0 {
140                invalid_revisions.push(new_artifact.revision)
141            }
142        } else if let Some(a) = unchanged_artifacts.remove(&new_artifact.id) {
143            // Artifact exists but revision must also be identical.
144            // This ensures that the artifact didn't "change out from under" the user.
145            if new_artifact.revision != a.revision {
146                invalid_revisions.push(new_artifact.revision)
147            }
148        } else {
149            // must update only existing ids
150            invalid_ids.push(new_artifact.id)
151        }
152        let (name, a) = match convert_artifact(&project.origin, new_artifact) {
153            Ok(v) => v,
154            Err(err) => {
155                name_errors.push(err);
156                continue;
157            }
158        };
159        if save_artifacts.insert(name.clone(), a).is_some() {
160            name_overlap.push(format!("{}", name));
161        }
162    }
163
164    // check that there are no name collisions
165    {
166        let mut existing_names: HashSet<NameRc> = HashSet::new();
167        for name in unchanged_artifacts
168            .values()
169            .map(
170                |a| NameRc::from_str(&a.name).unwrap(), /* already validated */
171            )
172            .chain(save_artifacts.keys().cloned())
173        {
174            if !existing_names.insert(name.clone()) {
175                name_overlap.push(format!("{}", name));
176            }
177        }
178    }
179
180    // craft the error based on all the errors
181    let mut data: HashMap<&str, Vec<String>> = HashMap::new();
182    let mut err = None;
183    if !name_errors.is_empty() {
184        data.insert(constants::X_INVALID_NAME, name_errors);
185        err = Some(constants::X_INVALID_NAME);
186    }
187    if !files_not_found.is_empty() {
188        data.insert(
189            constants::X_FILES_NOT_FOUND,
190            files_not_found
191                .iter()
192                .map(|f| format!("{}", f.display()))
193                .collect(),
194        );
195        err = Some(constants::X_FILES_NOT_FOUND);
196    }
197    if !invalid_ids.is_empty() {
198        let id_strs = invalid_ids.iter().map(u64::to_string).collect();
199        if for_create {
200            data.insert(constants::X_IDS_EXIST, id_strs);
201            err = Some(constants::X_IDS_EXIST);
202        } else {
203            data.insert(constants::X_IDS_NOT_FOUND, id_strs);
204            err = Some(constants::X_IDS_NOT_FOUND);
205        }
206    }
207    if !invalid_revisions.is_empty() {
208        let revision_strs = invalid_revisions.iter().map(u64::to_string).collect();
209        data.insert(constants::X_INVALID_REVISIONS, revision_strs);
210        err = Some(constants::X_INVALID_REVISIONS);
211    }
212    if !name_overlap.is_empty() {
213        data.insert(constants::X_NAMES_OVERLAP, name_overlap);
214        err = Some(constants::X_NAMES_OVERLAP);
215    }
216    if data.len() > 1 {
217        err = Some(constants::X_MULTIPLE_ERRORS);
218    }
219    if let Some(msg) = err {
220        return Err(RpcError {
221            code: constants::SERVER_ERROR,
222            message: msg.to_string(),
223            data: Some(serde_json::to_value(data).unwrap()),
224        });
225    }
226
227    Ok((unchanged_artifacts, save_artifacts))
228}