cuddle-v2/crates/cuddle/src/cli/get_command.rs
kjuulh 02dd805db4
feat: add command get for doing queries
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-08-25 22:13:50 +02:00

125 lines
3.2 KiB
Rust

use crate::{
cuddle_state::Cuddle,
state::{
validated_project::{Project, Value},
ValidatedState,
},
};
pub struct GetCommand {
query_engine: ProjectQueryEngine,
}
impl GetCommand {
pub fn new(cuddle: Cuddle<ValidatedState>) -> Self {
Self {
query_engine: ProjectQueryEngine::new(
&cuddle
.state
.project
.expect("we should always have a project if get command is available"),
),
}
}
pub async fn execute(&self, query: &str) -> anyhow::Result<String> {
let res = self
.query_engine
.query(query)?
.ok_or(anyhow::anyhow!("query was not found in project"))?;
match res {
Value::String(s) => Ok(s),
Value::Bool(b) => Ok(b.to_string()),
Value::Array(value) => {
let val = serde_json::to_string_pretty(&value)?;
Ok(val)
}
Value::Map(value) => {
let val = serde_json::to_string_pretty(&value)?;
Ok(val)
}
}
}
pub fn description() -> String {
"get returns a given variable from the project given a key, following a jq like schema (.project.name, etc.)"
.into()
}
}
pub struct ProjectQueryEngine {
project: Project,
}
impl ProjectQueryEngine {
pub fn new(project: &Project) -> Self {
Self {
project: project.clone(),
}
}
pub fn query(&self, query: &str) -> anyhow::Result<Option<Value>> {
let parts = query
.split('.')
.filter(|i| !i.is_empty())
.collect::<Vec<&str>>();
Ok(self.traverse(&parts, &self.project.value))
}
fn traverse(&self, query: &[&str], value: &Value) -> Option<Value> {
match query.split_first() {
Some((key, rest)) => match value {
Value::Map(items) => {
let item = items.get(*key)?;
self.traverse(rest, item)
}
_ => {
tracing::warn!(
"key: {} doesn't have a corresponding value: {:?}",
key,
value
);
None
}
},
None => Some(value.clone()),
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
#[tokio::test]
async fn test_can_query_item() -> anyhow::Result<()> {
let project = ProjectQueryEngine::new(&Project {
value: Value::Map(
[(
String::from("project"),
Value::Map(
[(
String::from("name"),
Value::String(String::from("something")),
)]
.into(),
),
)]
.into(),
),
root: PathBuf::new(),
});
let res = project.query(".project.name")?;
assert_eq!(Some(Value::String("something".into())), res);
Ok(())
}
}