aion-worker 0.27.1

Rust remote-worker SDK for executing Aion activities over the gRPC worker protocol.
Documentation
//! Resolving a declared command's working directory against a host's
//! workspace root.
//!
//! A `command` declaration states its `cwd` verbatim, `{workspace_root}`
//! placeholder and all, because where workspaces live is a property of the box
//! that runs the command and not of the document that declares it. Two hosts
//! answer that question — the server, from its aion home, and `aion worker
//! awl`, from the directory the document was pointed at — and both then have
//! the SAME job: splice the root in, and refuse a root that cannot be spliced.
//!
//! This is that job, once. The two hosts differ in how they find the root and
//! in nothing else; a second copy of the rules would be two executors
//! disagreeing about the world a process runs in, which is exactly the defect
//! the one-render rule exists to prevent.

use std::path::{Path, PathBuf};

use thiserror::Error;

/// The placeholder a declared `cwd` may carry, defined by the LANGUAGE crate.
///
/// Restated here as a private constant rather than imported: `aion-worker`
/// deliberately does not depend on `aion-awl` (the dependency is test-only and
/// one-directional), and the bytes are a format constant the estate agrees on.
/// `crates/aion-awl/src/workspace_root.rs` is where it is defined; a
/// differential test in that crate pins the two spellings equal.
const WORKSPACE_ROOT_PLACEHOLDER: &str = "{workspace_root}";

/// Why a declared working directory could not be resolved against a root.
///
/// Every variant is TERMINAL: the root is a property of this box's
/// configuration and filesystem, so a second attempt reads the same answer.
#[derive(Clone, Debug, PartialEq, Eq, Error)]
pub enum WorkingDirectoryError {
    /// The root is not an absolute path.
    ///
    /// Refused because a relative root names a different place depending on
    /// where the host process was launched from, which is not something a
    /// document's author can see or predict.
    #[error(
        "the workspace root `{path}` is not absolute, so the directory a declared command \
         runs in would depend on where this process was started"
    )]
    NotAbsolute {
        /// The offending root, rendered lossily for the refusal.
        path: String,
    },
    /// The root is not valid UTF-8, so it has no faithful spelling to splice
    /// into the declared path.
    #[error(
        "the workspace root `{path}` is not valid UTF-8, so it cannot be spliced into a \
         declared working directory"
    )]
    NotUnicode {
        /// The offending root, rendered lossily for the refusal.
        path: String,
    },
    /// The root contains a NUL byte, so no process can be launched in it.
    #[error(
        "the workspace root `{path}` contains a NUL byte, which cannot cross `execve`, so no \
         process can be launched in it"
    )]
    NotSpawnable {
        /// The offending root, rendered for the refusal.
        path: String,
    },
    /// The root directory does not exist and could not be created.
    #[error("the workspace root directory `{path}` could not be created: {error}")]
    CreationFailed {
        /// The directory that could not be created.
        path: String,
        /// The io error's own diagnosis.
        error: String,
    },
}

/// Whether `declared` asks where the workspace is.
///
/// A caller that must FIND its root before it can splice one — the server,
/// whose root resolution may itself have failed — asks this first, so a
/// command that never wanted the answer is not refused for it.
#[must_use]
pub fn needs_workspace_root(declared: &str) -> bool {
    declared.contains(WORKSPACE_ROOT_PLACEHOLDER)
}

/// Resolve `declared` — a command's `cwd`, verbatim — against `root`.
///
/// A declared path with no placeholder is returned untouched and `root` is
/// never consulted, so a command that does not ask where the workspace is
/// cannot be refused for an answer it never wanted.
///
/// A path that DOES carry the placeholder gets the root validated and created
/// before the splice, because a working directory that does not exist is a
/// spawn failure on the first dispatch rather than a refusal at the desk.
///
/// # Errors
///
/// Returns [`WorkingDirectoryError`] when the root is relative, is not valid
/// UTF-8, carries a NUL byte, or names a directory that cannot be created.
pub fn resolve_working_directory(
    declared: &str,
    root: &Path,
) -> Result<PathBuf, WorkingDirectoryError> {
    if !needs_workspace_root(declared) {
        return Ok(PathBuf::from(declared));
    }
    if !root.is_absolute() {
        return Err(WorkingDirectoryError::NotAbsolute {
            path: root.to_string_lossy().into_owned(),
        });
    }
    let text = root
        .to_str()
        .ok_or_else(|| WorkingDirectoryError::NotUnicode {
            path: root.to_string_lossy().into_owned(),
        })?;
    if text.contains('\0') {
        return Err(WorkingDirectoryError::NotSpawnable {
            path: text.to_owned(),
        });
    }
    create_root_directory(root).map_err(|error| WorkingDirectoryError::CreationFailed {
        path: text.to_owned(),
        error: error.to_string(),
    })?;
    Ok(PathBuf::from(
        declared.replace(WORKSPACE_ROOT_PLACEHOLDER, text),
    ))
}

/// Create the root directory if it is missing, owner-only.
///
/// Idempotent: an existing directory is not an error. The mode matters because
/// a workspace holds whatever a declared command puts there, and this crate's
/// portability gate is target-cfg, so the unix concept is gated the same way.
fn create_root_directory(root: &Path) -> std::io::Result<()> {
    let mut builder = std::fs::DirBuilder::new();
    builder.recursive(true);
    #[cfg(unix)]
    {
        use std::os::unix::fs::DirBuilderExt as _;
        builder.mode(0o700);
    }
    builder.create(root)
}

#[cfg(test)]
mod tests {
    use super::{WorkingDirectoryError, resolve_working_directory};
    use std::path::{Path, PathBuf};

    /// What a test returns. Every fallible step is carried rather than
    /// unwrapped, because the workspace denies panicking accessors in test
    /// code as firmly as in library code.
    type TestResult = Result<(), Box<dyn std::error::Error>>;

    #[test]
    fn a_path_without_the_placeholder_never_consults_the_root() -> TestResult {
        // A relative root would be refused if it were read at all, so this
        // passing proves the root was not read.
        assert_eq!(
            resolve_working_directory("/srv/app", Path::new("relative"))?,
            PathBuf::from("/srv/app")
        );
        Ok(())
    }

    #[test]
    fn the_placeholder_splices_the_root_and_creates_it() -> TestResult {
        let scratch = tempfile::tempdir()?;
        let root = scratch.path().join("clones");
        let resolved = resolve_working_directory("{workspace_root}/repo", &root)?;
        assert_eq!(resolved, root.join("repo"));
        assert!(root.is_dir(), "the root must be created before the splice");
        Ok(())
    }

    #[test]
    fn a_relative_root_refuses_rather_than_naming_a_place_nobody_chose() {
        assert_eq!(
            resolve_working_directory("{workspace_root}", Path::new("clones")),
            Err(WorkingDirectoryError::NotAbsolute {
                path: "clones".to_owned(),
            })
        );
    }

    #[test]
    fn an_uncreatable_root_refuses_by_name() -> TestResult {
        let scratch = tempfile::tempdir()?;
        let file = scratch.path().join("occupied");
        std::fs::write(&file, b"not a directory")?;
        let Err(error) = resolve_working_directory("{workspace_root}", &file.join("clones")) else {
            return Err("a root beneath a regular file cannot be created".into());
        };
        assert!(
            matches!(error, WorkingDirectoryError::CreationFailed { .. }),
            "{error}"
        );
        Ok(())
    }
}