Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Planner and its supporting data structures #5

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ edition = "2021"
description = "An automated job orchestration library to build and execute dynamic workflows"

[dependencies]
json-patch = "2.0.0"
json-patch = "3"
jsonptr = "0.6.0"
matchit = "0.8.4"
serde = { version = "1.0.197" }
serde_json = "1.0.120"
thiserror = "1.0.63"
thiserror = "2"

[dev-dependencies]
tokio = { version = "1.36.0", features = ["rt", "macros", "time"] }
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ While the ideas behind planning systems go back to the 1970s, they have seen ver
Let's create a system controller for a simple counting system. Let's define a Job that operates on i32

```rust
use gustav::*;
use gustav::task::*;
use gustav::extract::{Target, Update};

/// Plus one is a job that updates a counter if it is below some target
Expand Down Expand Up @@ -126,7 +126,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// The state model
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
struct State {
counters: HashMap<String, i32>,
}
Expand Down Expand Up @@ -166,7 +166,7 @@ async fn main() {
let agent = Worker::new()
// The plus_one job is applicable to an UPDATE operation
// on any given counter
.job("/counters/:name", update(plus_one))
.job("/counters/{name}", update(plus_one))
// Initialize two counters "a" and "b" to 0
.with_state(State {counters: HashMap::from([("a".to_string(), 0), ("b".to_string(), 0)])})

Expand All @@ -192,9 +192,7 @@ On the above example, the job definition is practically identical to the one in
As programmers, we want to be able to build code by composing simpler behaviors into more complex ones. We might want to guide the planner towards a specific solution, using the primitives we already have. For instance, let's say we want to help the planner get to a solution faster as adding tasks one by one takes too much time. We want to define a `plus_two` task, that increases the counter by 2. We could create another primitive task to update the counter by two, but as programmers, we would like to reuse the code we have already defined. We can do that using methods.

```rust
use gustav::system::Context;

fn plus_two(counter: Update<State, i32>, tgt: Target<State, i32>) -> Vec<Task<i32>> {
fn plus_two(counter: Update<State, i32>, tgt: Target<State, i32>, Path(name): Path<String>) -> Vec<Task<i32>> {
if *tgt - *counter < 2 {
// Returning an empty result tells the planner
// the task is not applicable to reach the target
Expand All @@ -204,17 +202,17 @@ fn plus_two(counter: Update<State, i32>, tgt: Target<State, i32>) -> Vec<Task<i3
// A compound job returns a list of tasks that need to be executed
// to achieve a certain goal
vec![
plus_one.into_task(Context::from_target(*tgt)),
plus_one.into_task(Context::from_target(*tgt)),
plus_one.into_task(Context::new().target(*tgt).arg("name", &name)),
plus_one.into_task(Context::new().target(*tgt).arg("name", &name)),
]
}

#[tokio::main]
async fn main() {
// build our agent using the plus one job
let agent = Worker::new()
.job("/counters/:name", update(plus_one))
.job("/counters/:name", update(plus_two))
.job("/counters/{name}", update(plus_one))
.job("/counters/{name}", update(plus_two))
// Initialize two counters "a" and "b" to 0
.with_state(State {counters: HashMap::from([("a".to_string(), 0), ("b".to_string(), 0)])})

Expand Down
43 changes: 14 additions & 29 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,26 @@ use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
SerializationError(#[from] serde_json::error::Error),
#[error("cannot read system state: ${0}")]
StateReadFailed(#[from] super::system::SystemReadError),

#[error(transparent)]
PatchFailed(#[from] json_patch::PatchError),
#[error("cannot write system state: ${0}")]
StateWriteFailed(#[from] super::system::SystemWriteError),

#[error("cannot extract path: ${0}")]
PathExtractFailed(#[from] super::extract::PathDeserializationError),

#[error("the string `{0}` is not a valid path")]
InvalidPath(String),
#[error("cannot extract target: ${0}")]
TargetExtractFailed(#[from] super::extract::TargetExtractError),

#[error("no target available on the context")]
TargetIsNone,
#[error("cannot extract view: ${0}")]
ViewExtractFailed(#[from] super::extract::ViewExtractError),

#[error("cannot expand an atom task")]
CannotExpandTask,
#[error("cannot calculate view result: ${0}")]
ViewResultFailed(#[from] super::extract::ViewResultError),

#[error("condition failed: ${0}")]
ConditionFailed(String),

#[error("cannot resolve state path `{path}`: ${reason}")]
TargetResolveFailed {
path: String,
reason: jsonptr::resolve::ResolveError,
},

#[error("cannot resolve path `{path}` on system state: ${reason}")]
PointerResolveFailed {
path: String,
reason: jsonptr::resolve::ResolveError,
},

#[error("cannot assign path `{path}` on system state: ${reason}")]
PointerAssignFailed {
path: String,
reason: jsonptr::assign::AssignError,
},
TaskConditionFailed(#[from] super::task::ConditionFailed),

#[error(transparent)]
Other(#[from] Box<dyn std::error::Error>),
Expand Down
2 changes: 2 additions & 0 deletions src/extract/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod path;
mod target;
mod view;

pub use path::*;
pub use target::*;
pub use view::*;
Loading
Loading