mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 15:25:16 +00:00
Plug-ins: diffPath is a file key and Slice Options' url is not; the command sweep uses these keys
The sweeps in command_sweep.rs kept their own list of file and folder
keys (path, diffPath, folder, destinationFolder) in testkit's
safe_value and again in the temp-folder test, so a command whose file
key was not on the list was sent raw junk strings, and a key written as
`{path | name ...}` (document.open) was never swept.
- The engine exports the plug-in call rules' file keys as
`cmd::key_location`, `cmd::location_in` and `cmd::location_keys`,
which read every key a params description names, the keys of the
commands its `...` entries name, and the schema. `diffPath` is a file
key, so a plug-in call that carries it is refused; Slice Options'
`url` and Export for Screens' `folders` name part of the document, so
a call to object.slice.options with a top-level `url` is accepted.
- testkit's `param_keys` reads the keys the engine reads, and
`junk_params_for_command` and `safe_value_for_command` send every
string under a file or folder key, in arrays and in objects inside
them, as a location in the sweep's temp folder. Each such key also
gets a list of paths and a list of `{ids, path}` objects.
- Every catalog sweep, the serializability sweep included, uses them,
and the temp-folder test checks every key the catalog names, at any
depth.
This commit is contained in:
@@ -102,7 +102,53 @@ const FILE_WORDS: &[&str] = &[
|
||||
];
|
||||
|
||||
/// Param keys that name a file or a folder.
|
||||
const FILE_KEYS: &[&str] = &["path", "paths", "file", "files", "folder", "folders", "dir", "directory", "destinationFolder", "sourceFolder", "url"];
|
||||
const FILE_KEYS: &[&str] =
|
||||
&["path", "paths", "file", "files", "folder", "folders", "dir", "directory", "destinationFolder", "sourceFolder", "url", "diffPath"];
|
||||
|
||||
/// The keys in [`FILE_KEYS`] that name a folder.
|
||||
const FOLDER_KEYS: &[&str] = &["folder", "folders", "dir", "directory", "destinationFolder", "sourceFolder"];
|
||||
|
||||
/// What a param key that names a place on disk names there.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Location {
|
||||
/// A file (or a list of files).
|
||||
File,
|
||||
/// A folder (or a list of folders).
|
||||
Folder,
|
||||
}
|
||||
|
||||
/// What `key` names on disk when a command takes it ([`FILE_KEYS`]), or `None` when it never
|
||||
/// names a file or a folder.
|
||||
pub fn key_location(key: &str) -> Option<Location> {
|
||||
if FOLDER_KEYS.contains(&key) {
|
||||
Some(Location::Folder)
|
||||
} else if FILE_KEYS.contains(&key) {
|
||||
Some(Location::File)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// What `key` names on disk in command `spec`: [`key_location`], or `None` when [`NOT_FILES`]
|
||||
/// names it for `spec` (it names part of the document there).
|
||||
pub fn location_in(spec: &CommandSpec, key: &str) -> Option<Location> {
|
||||
if NOT_FILES.contains(&(spec.id, key)) { None } else { key_location(key) }
|
||||
}
|
||||
|
||||
/// The param keys of `spec` that name a file or a folder, with what they name: the keys in
|
||||
/// [`FILE_KEYS`] that its params description names ([`super::params::doc_keys`]), with those of the
|
||||
/// commands its `…` entries name ([`super::params::takes_key`]), and its schema's params, less those
|
||||
/// [`NOT_FILES`] names for it. Sorted, each key once.
|
||||
pub fn location_keys(spec: &CommandSpec) -> Vec<(&'static str, Location)> {
|
||||
let described = super::params::doc_keys(spec.params).map(|p| p.keys).unwrap_or_default();
|
||||
let in_schema = spec.schema.map(|s| s.params.iter().map(|p| p.name)).into_iter().flatten();
|
||||
let taken = FILE_KEYS.iter().copied().filter(|k| super::params::takes_key(spec.params, k));
|
||||
let mut keys: Vec<(&'static str, Location)> =
|
||||
described.into_iter().chain(in_schema).chain(taken).filter_map(|k| Some((k, location_in(spec, k)?))).collect();
|
||||
keys.sort_unstable_by_key(|(k, _)| *k);
|
||||
keys.dedup();
|
||||
keys
|
||||
}
|
||||
|
||||
/// Commands and the param key in [`FILE_KEYS`] that, in them, names part of the document (a path of
|
||||
/// its art, a setting, an attribute it stores or a result field), not a file.
|
||||
@@ -119,6 +165,8 @@ pub(crate) const NOT_FILES: &[(&str, &str)] = &[
|
||||
("attributes.set", "url"),
|
||||
("svgInteractivity.info", "files"),
|
||||
("addon.cleanup.rejoin", "paths"),
|
||||
("object.slice.options", "url"),
|
||||
("document.exportForScreens", "folders"),
|
||||
];
|
||||
|
||||
/// Is command id `id` one a plug-in may never call, from its id alone ([`DENIED_PREFIXES`],
|
||||
@@ -466,7 +514,8 @@ mod tests {
|
||||
}
|
||||
for (id, key) in NOT_FILES {
|
||||
let Some(spec) = crate::cmd::find_command(id) else { panic!("no command {id}") };
|
||||
assert!(names_key(spec.params, key), "{id} has no `{key}` param");
|
||||
let described = crate::cmd::params::doc_keys(spec.params).is_ok_and(|p| p.keys.contains(key));
|
||||
assert!(names_key(spec.params, key) || described, "{id} has no `{key}` param");
|
||||
}
|
||||
let leaks: Vec<&str> = specs
|
||||
.iter()
|
||||
@@ -565,6 +614,40 @@ mod tests {
|
||||
read.unwrap();
|
||||
}
|
||||
|
||||
/// [`location_keys`] names every key in [`FILE_KEYS`] that a command's params description or
|
||||
/// schema names, in every way a description writes one (`path | name`, `folder? (…)`), less
|
||||
/// those [`NOT_FILES`] names for it; folder keys name folders. No command a plug-in may call
|
||||
/// has one.
|
||||
#[test]
|
||||
fn location_keys_come_from_the_command_catalog() {
|
||||
let find = |id: &str| crate::cmd::find_command(id).unwrap_or_else(|| panic!("no command {id}"));
|
||||
let keys = |id: &str| location_keys(find(id));
|
||||
assert!(keys("document.open").contains(&("path", Location::File)), "{:?}", keys("document.open"));
|
||||
assert!(keys("document.compare").contains(&("diffPath", Location::File)), "{:?}", keys("document.compare"));
|
||||
assert!(keys("addon.document.saveTimestampedCopy").contains(&("folder", Location::Folder)));
|
||||
let relink = keys("links.relink");
|
||||
for k in [("path", Location::File), ("paths", Location::File), ("folder", Location::Folder)] {
|
||||
assert!(relink.contains(&k), "{relink:?}");
|
||||
}
|
||||
assert!(keys("action.batch").contains(&("destinationFolder", Location::Folder)));
|
||||
for (id, key) in NOT_FILES {
|
||||
assert!(!keys(id).iter().any(|(k, _)| k == key), "{id} `{key}` names a file");
|
||||
}
|
||||
for spec in crate::cmd::command_specs() {
|
||||
let found = location_keys(spec);
|
||||
let described = crate::cmd::params::doc_keys(spec.params).map(|p| p.keys).unwrap_or_default();
|
||||
let in_schema = spec.schema.map(|s| s.params.iter().map(|p| p.name).collect::<Vec<_>>()).unwrap_or_default();
|
||||
for k in described.into_iter().chain(in_schema).filter(|k| FILE_KEYS.contains(k) && !NOT_FILES.contains(&(spec.id, *k))) {
|
||||
assert!(found.iter().any(|(f, _)| *f == k), "{}: `{k}` is missing from {found:?}", spec.id);
|
||||
}
|
||||
assert!(found.iter().all(|(k, l)| (*l == Location::Folder) == FOLDER_KEYS.contains(k)), "{}: {found:?}", spec.id);
|
||||
assert!(denied(spec).is_some() || found.is_empty(), "{} names {found:?} but a plug-in may call it", spec.id);
|
||||
}
|
||||
assert_eq!(key_location("url"), Some(Location::File));
|
||||
assert_eq!(key_location("sourceFolder"), Some(Location::Folder));
|
||||
assert_eq!(key_location("pathData"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_are_found_as_whole_keys() {
|
||||
assert!(names_key("{path: a .wasm file}", "path"));
|
||||
|
||||
Reference in New Issue
Block a user