Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clippy.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
excessive-nesting-threshold = 3
too-many-arguments-threshold = 10
allowed-prefixes = ["..", "GPU"]
allowed-prefixes = ["..", "GPU", "Orca"]
min-ident-chars-threshold = 2
allowed-idents-below-min-chars = ["..", "k", "f", "re", "id"]
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ indoc = "2.0.5"

[lints.rust]
non_ascii_idents = "deny"
# missing_docs = "warn" # temporary: add once we have docs
missing_docs = "warn"

[lints.clippy]
correctness = "deny"
Expand Down Expand Up @@ -96,6 +96,5 @@ todo = { level = "warn", priority = 127 } # warn to
use_debug = { level = "warn", priority = 127 } # debug print

# temporary
missing_errors_doc = { level = "allow", priority = 127 } # remove once we have docs
single_call_fn = { level = "allow", priority = 127 } # remove once other models are in
tests_outside_test_module = { level = "allow", priority = 127 } # for now due to false-positive for integration tests: https://github.com/rust-lang/rust-clippy/pull/13038
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@
set -e # stop early on non-zero exit
cargo clippy --all-targets -- -D warnings # syntax and style tests
cargo fmt --check # formatting test
cargo llvm-cov -- --nocapture # integration tests w/ coverage report
cargo llvm-cov -- --nocapture # integration tests w/ coverage summary
cargo llvm-cov --html -- --nocapture # integration tests w/ coverage report (target/llvm-cov/html/index.html)
```

## Docs

```bash
cargo doc --no-deps # gen api docs (target/doc/orcapod/index.html)
```

## Project Management
Expand Down
189 changes: 88 additions & 101 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,129 +1,116 @@
use colored::Colorize;
use glob;
use regex;
use serde_yaml;
use std::{
error::Error,
fmt,
fmt::{Display, Formatter},
io,
path::PathBuf,
result,
};
/// Shorthand for a Result that returns an `OrcaError`.
pub type Result<T> = result::Result<T, OrcaError>;

/// Wrapper around `serde_yaml::from_str`
/// Possible errors you may encounter.
#[derive(Debug)]
pub struct DeserializeFailure {
pub path: PathBuf,
pub error: serde_yaml::Error,
}
impl Error for DeserializeFailure {}
impl Display for DeserializeFailure {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}{}{}{}",
"Failed to deserialize with error ".bright_red(),
self.error.to_string().bright_red(),
" for ".bright_red(),
self.path.to_string_lossy().bright_cyan()
)
}
enum Kind {
/// Returned if a file is not expected to exist.
FileExists(PathBuf),
/// Returned if a file is expected to have a parent.
FileHasNoParent(PathBuf),
/// Returned if an annotation was expected to exist.
NoAnnotationFound(String, String, String),
/// Returned if a regular expression was expected to match.
NoRegexMatch,
/// Wrapper around `glob::GlobError`
GlobError(glob::GlobError),
/// Wrapper around `glob::PatternError`
GlobPaternError(glob::PatternError),
/// Wrapper around `regex::Error`
RegexError(regex::Error),
/// Wrapper around `serde_yaml::Error`
SerdeYamlError(serde_yaml::Error),
/// Wrapper around `io::Error`
IoError(io::Error),
}

/// Wrapper around getting None when trying to find parent
/// A stable error API interface.
#[derive(Debug)]
pub struct FileHasNoParent {
pub path: PathBuf,
}
impl Error for FileHasNoParent {}
impl Display for FileHasNoParent {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"File `{}` has no parent.",
self.path.to_string_lossy().bright_red()
)
pub struct OrcaError(Kind);
impl Error for OrcaError {}
impl OrcaError {
/// Error constructor for `FileExists`.
pub(crate) const fn file_exists(path: PathBuf) -> Self {
Self(Kind::FileExists(path))
}
}

/// Wrapper around `serde_yaml::to_string`
#[derive(Debug)]
pub struct SerializeFailure {
pub item_debug_string: String,
pub error: serde_yaml::Error,
}
impl Error for SerializeFailure {}
impl Display for SerializeFailure {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}{}{}{}",
"Failed to seralize ".bright_red(),
self.item_debug_string.bright_cyan(),
" with error ".bright_red(),
self.error.to_string().bright_red(),
)
/// Error constructor for `FileHasNoParent`.
pub const fn file_has_no_parent(path: PathBuf) -> Self {
Self(Kind::FileHasNoParent(path))
}
/// Error constructor for `NoAnnotationFound`.
pub(crate) const fn no_annotation_found(class: String, name: String, version: String) -> Self {
Self(Kind::NoAnnotationFound(class, name, version))
}
/// Error constructor for `NoRegexMatch`.
pub(crate) const fn no_regex_match() -> Self {
Self(Kind::NoRegexMatch)
}
}

/// Wrapper around `fs::read_to_string` and `fs::write`
#[derive(Debug)]
pub struct IOFailure {
pub path: PathBuf,
pub error: io::Error,
}
impl Error for IOFailure {}
impl Display for IOFailure {
impl Display for OrcaError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}{}{}{}",
"IO Error: ".bright_red(),
&self.error.to_string().bright_red(),
" at ".bright_red(),
&self.path.to_string_lossy().cyan(),
)
match &self.0 {
Kind::FileExists(path) => {
write!(
f,
"File `{}` already exists.",
path.to_string_lossy().bright_cyan()
)
}
Kind::FileHasNoParent(path) => {
write!(
f,
"File `{}` has no parent.",
path.to_string_lossy().bright_red()
)
}
Kind::NoAnnotationFound(class, name, version) => {
write!(f, "No annotation found for `{name}:{version}` {class}.")
}
Kind::NoRegexMatch => {
write!(f, "No match for regex.")
}
Kind::GlobError(error) => write!(f, "{error}"),
Kind::GlobPaternError(error) => write!(f, "{error}"),
Kind::SerdeYamlError(error) => write!(f, "{error}"),
Kind::RegexError(error) => write!(f, "{error}"),
Kind::IoError(error) => write!(f, "{error}"),
}
}
}

/// Raise error when file exists but unexpected
#[derive(Debug)]
pub struct FileExists {
pub path: PathBuf,
impl From<glob::GlobError> for OrcaError {
fn from(error: glob::GlobError) -> Self {
Self(Kind::GlobError(error))
}
}
impl Error for FileExists {}
impl Display for FileExists {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"File `{}` already exists.",
self.path.to_string_lossy().bright_red()
)
impl From<glob::PatternError> for OrcaError {
fn from(error: glob::PatternError) -> Self {
Self(Kind::GlobPaternError(error))
}
}

/// Raise error when glob doesn't match on an annotation
#[derive(Debug)]
pub struct NoAnnotationFound {
pub class: String,
pub name: String,
pub version: String,
impl From<serde_yaml::Error> for OrcaError {
fn from(error: serde_yaml::Error) -> Self {
Self(Kind::SerdeYamlError(error))
}
}
impl Error for NoAnnotationFound {}
impl Display for NoAnnotationFound {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"No annotation found for `{}:{}` {}.",
self.name, self.version, self.class
)
impl From<regex::Error> for OrcaError {
fn from(error: regex::Error) -> Self {
Self(Kind::RegexError(error))
}
}

/// Raise error when regex doesn't match
#[derive(Debug)]
pub struct NoRegexMatch;
impl Error for NoRegexMatch {}
impl Display for NoRegexMatch {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "No match for regex.")
impl From<io::Error> for OrcaError {
fn from(error: io::Error) -> Self {
Self(Kind::IoError(error))
}
}
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
//! Intuitive compute pipeline orchestration with reproducibility, performance, and scalability in
//! mind.

/// Error handling based on enumeration.
pub mod error;
/// Components of the data model.
pub mod model;
/// Data persistence is provided by using a store backend.
pub mod store;
mod util;
56 changes: 44 additions & 12 deletions src/model.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use crate::util::{get_type_name, hash};
use crate::{
error::Result,
util::{get_type_name, hash},
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_yaml::{Mapping, Value};
use std::{
collections::BTreeMap,
error::Error,
fs,
io::{BufRead, BufReader},
path::{Path, PathBuf},
result,
};

pub fn to_yaml<T: Serialize>(instance: &T) -> Result<String, Box<dyn Error>> {
/// Converts a model instance into a consistent yaml.
///
/// # Errors
///
/// Will return `Err` if there is an issue converting an `instance` into YAML (w/o annotation).
pub fn to_yaml<T: Serialize>(instance: &T) -> Result<String> {
let mapping: BTreeMap<String, Value> = serde_yaml::from_str(&serde_yaml::to_string(instance)?)?; // sort
let mut yaml = serde_yaml::to_string(
&mapping
Expand All @@ -21,17 +28,22 @@ pub fn to_yaml<T: Serialize>(instance: &T) -> Result<String, Box<dyn Error>> {

Ok(yaml)
}

/// Instantiates a model from from yaml content and its unique hash.
///
/// # Errors
///
/// Will return `Err` if there is an issue converting YAML files for spec+annotation into a model
/// instance.
pub fn from_yaml<T: DeserializeOwned>(
annotation_file: &Path,
spec_file: &Path,
hash: &str,
) -> Result<T, Box<dyn Error>> {
) -> Result<T> {
let annotation: Mapping = serde_yaml::from_str(&fs::read_to_string(annotation_file)?)?;
let spec_yaml = BufReader::new(fs::File::open(spec_file)?)
.lines()
.skip(1)
.collect::<Result<Vec<_>, _>>()?
.collect::<result::Result<Vec<_>, _>>()?
.join("\n");

let mut spec_mapping: BTreeMap<String, Value> = serde_yaml::from_str(&spec_yaml)?;
Expand All @@ -44,9 +56,12 @@ pub fn from_yaml<T: DeserializeOwned>(

// --- core model structs ---

/// A reusable, containerized computational unit.
#[derive(Serialize, Deserialize, Debug)]
pub struct Pod {
/// Metadata that doesn't affect reproducibility.
pub annotation: Annotation,
/// Unique id based on reproducibility.
pub hash: String,
source_commit_url: String,
image: String,
Expand All @@ -60,6 +75,11 @@ pub struct Pod {
}

impl Pod {
/// Construct a new pod instance.
///
/// # Errors
///
/// Will return `Err` if there is an issue initializing a `Pod` instance.
pub fn new(
annotation: Annotation,
source_commit_url: String,
Expand All @@ -71,7 +91,7 @@ impl Pod {
recommended_cpus: f32,
recommended_memory: u64,
required_gpu: Option<GPURequirement>,
) -> Result<Self, Box<dyn Error>> {
) -> Result<Self> {
let pod_no_hash = Self {
annotation,
hash: String::new(),
Expand All @@ -94,28 +114,40 @@ impl Pod {

// --- util types ---

/// Standard metadata structure for all model instances.
#[derive(Serialize, Deserialize, Debug)]
pub struct Annotation {
/// A unique name.
pub name: String,
/// A unique semantic version.
pub version: String,
/// A long form description.
pub description: String,
}

/// Specification for GPU requirements in computation.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GPURequirement {
/// GPU model specification.
pub model: GPUModel,
/// Manufacturer recommended memory.
pub recommended_memory: u64,
/// Number of GPU cards required.
pub count: u16,
}

/// GPU model specification.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum GPUModel {
NVIDIA(String), // String will be the specific model of the gpu
/// NVIDIA-manufactured card where `String` is the specific model e.g. ???
NVIDIA(String),
/// AMD-manufactured card where `String` is the specific model e.g. ???
AMD(String),
}

/// Streams are named and represent an abstration for the file(s) that represent some particular
/// data.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StreamInfo {
/// Path to stream file.
pub path: PathBuf,
/// Naming pattern for the stream.
pub match_pattern: String,
}
Loading