blob: 65bb2f75fec0297673f260f927b3d94f5b0f4275 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::HostError;
use maka_fs_tools::workspace::directory::PublishedDirectory;
use std::path::PathBuf;
#[derive(
Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub struct DirectoryRootSpec {
pub label: String,
pub path: PathBuf,
}
impl DirectoryRootSpec {
/// Validate the published directory set and capture canonical paths once.
pub fn normalize(specs: Vec<Self>) -> Result<Vec<Self>, HostError> {
let mut roots: Vec<Self> = Vec::new();
for spec in specs {
let label = spec
.label
.trim_matches(|c| {
matches!(c,
'\u{0009}'..='\u{000d}' | '\u{0020}' | '\u{00a0}' | '\u{1680}' |
'\u{2000}'..='\u{200a}' | '\u{2028}' | '\u{2029}' | '\u{202f}' |
'\u{205f}' | '\u{3000}' | '\u{feff}')
})
.to_owned();
if label.is_empty()
|| label.len() > 128
|| label.chars().any(|c| c <= '\u{001f}' || c == '\u{007f}')
{
return Err("invalid project root label".into());
}
let directory = PublishedDirectory::open(&spec.path)?;
if roots
.iter()
.any(|r| r.label == label || r.path == directory.path())
{
return Err("duplicate project root".into());
}
roots.push(Self {
label,
path: directory.path().to_owned(),
});
}
Ok(roots)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explicit_roots_have_no_count_cap_and_still_reject_duplicate_paths() {
let directory = tempfile::tempdir().unwrap();
let specs: Vec<_> = (0..12)
.map(|i| {
let path = directory.path().join(format!("project-{i}"));
std::fs::create_dir(&path).unwrap();
DirectoryRootSpec {
label: format!("Project {i}"),
path,
}
})
.collect();
assert_eq!(
DirectoryRootSpec::normalize(specs.clone()).unwrap().len(),
12
);
let mut duplicate = specs;
duplicate.push(DirectoryRootSpec {
label: "Another label".into(),
path: duplicate[0].path.clone(),
});
assert!(DirectoryRootSpec::normalize(duplicate).is_err());
}
}