formatting
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -70,8 +70,6 @@ ENV/
|
|||||||
|
|
||||||
# Node (if used for tooling)
|
# Node (if used for tooling)
|
||||||
node_modules/
|
node_modules/
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
||||||
tests/pids/*
|
tests/pids/*
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
//! Webhook security helpers for HMAC verification and validation
|
//! Webhook security helpers for HMAC verification and validation
|
||||||
|
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use sha2::{Sha256, Sha512};
|
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
|
use sha2::{Sha256, Sha512};
|
||||||
|
|
||||||
/// Verify HMAC signature for webhook payload
|
/// Verify HMAC signature for webhook payload
|
||||||
pub fn verify_hmac_signature(
|
pub fn verify_hmac_signature(
|
||||||
@@ -33,8 +33,8 @@ pub fn verify_hmac_signature(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode hex signature
|
// Decode hex signature
|
||||||
let expected_signature = hex::decode(hex_signature)
|
let expected_signature =
|
||||||
.map_err(|e| format!("Invalid hex signature: {}", e))?;
|
hex::decode(hex_signature).map_err(|e| format!("Invalid hex signature: {}", e))?;
|
||||||
|
|
||||||
// Compute HMAC based on algorithm
|
// Compute HMAC based on algorithm
|
||||||
let is_valid = match algorithm {
|
let is_valid = match algorithm {
|
||||||
@@ -91,7 +91,11 @@ fn verify_hmac_sha1(payload: &[u8], expected: &[u8], secret: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generate HMAC signature for testing
|
/// Generate HMAC signature for testing
|
||||||
pub fn generate_hmac_signature(payload: &[u8], secret: &str, algorithm: &str) -> Result<String, String> {
|
pub fn generate_hmac_signature(
|
||||||
|
payload: &[u8],
|
||||||
|
secret: &str,
|
||||||
|
algorithm: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
let signature = match algorithm {
|
let signature = match algorithm {
|
||||||
"sha256" => {
|
"sha256" => {
|
||||||
type HmacSha256 = Hmac<Sha256>;
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
@@ -127,12 +131,14 @@ pub fn generate_hmac_signature(payload: &[u8], secret: &str, algorithm: &str) ->
|
|||||||
pub fn check_ip_in_cidr(ip: &str, cidr: &str) -> Result<bool, String> {
|
pub fn check_ip_in_cidr(ip: &str, cidr: &str) -> Result<bool, String> {
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
let ip_addr: IpAddr = ip.parse()
|
let ip_addr: IpAddr = ip
|
||||||
|
.parse()
|
||||||
.map_err(|e| format!("Invalid IP address: {}", e))?;
|
.map_err(|e| format!("Invalid IP address: {}", e))?;
|
||||||
|
|
||||||
// If CIDR doesn't contain '/', treat it as a single IP
|
// If CIDR doesn't contain '/', treat it as a single IP
|
||||||
if !cidr.contains('/') {
|
if !cidr.contains('/') {
|
||||||
let cidr_addr: IpAddr = cidr.parse()
|
let cidr_addr: IpAddr = cidr
|
||||||
|
.parse()
|
||||||
.map_err(|e| format!("Invalid CIDR notation: {}", e))?;
|
.map_err(|e| format!("Invalid CIDR notation: {}", e))?;
|
||||||
return Ok(ip_addr == cidr_addr);
|
return Ok(ip_addr == cidr_addr);
|
||||||
}
|
}
|
||||||
@@ -143,9 +149,11 @@ pub fn check_ip_in_cidr(ip: &str, cidr: &str) -> Result<bool, String> {
|
|||||||
return Err("Invalid CIDR format".to_string());
|
return Err("Invalid CIDR format".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let network_addr: IpAddr = parts[0].parse()
|
let network_addr: IpAddr = parts[0]
|
||||||
|
.parse()
|
||||||
.map_err(|e| format!("Invalid network address: {}", e))?;
|
.map_err(|e| format!("Invalid network address: {}", e))?;
|
||||||
let prefix_len: u8 = parts[1].parse()
|
let prefix_len: u8 = parts[1]
|
||||||
|
.parse()
|
||||||
.map_err(|e| format!("Invalid prefix length: {}", e))?;
|
.map_err(|e| format!("Invalid prefix length: {}", e))?;
|
||||||
|
|
||||||
// Convert to bytes for comparison
|
// Convert to bytes for comparison
|
||||||
@@ -156,7 +164,11 @@ pub fn check_ip_in_cidr(ip: &str, cidr: &str) -> Result<bool, String> {
|
|||||||
}
|
}
|
||||||
let ip_bits = u32::from(ip);
|
let ip_bits = u32::from(ip);
|
||||||
let network_bits = u32::from(network);
|
let network_bits = u32::from(network);
|
||||||
let mask = if prefix_len == 0 { 0 } else { !0u32 << (32 - prefix_len) };
|
let mask = if prefix_len == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
!0u32 << (32 - prefix_len)
|
||||||
|
};
|
||||||
Ok((ip_bits & mask) == (network_bits & mask))
|
Ok((ip_bits & mask) == (network_bits & mask))
|
||||||
}
|
}
|
||||||
(IpAddr::V6(ip), IpAddr::V6(network)) => {
|
(IpAddr::V6(ip), IpAddr::V6(network)) => {
|
||||||
@@ -165,7 +177,11 @@ pub fn check_ip_in_cidr(ip: &str, cidr: &str) -> Result<bool, String> {
|
|||||||
}
|
}
|
||||||
let ip_bits = u128::from(ip);
|
let ip_bits = u128::from(ip);
|
||||||
let network_bits = u128::from(network);
|
let network_bits = u128::from(network);
|
||||||
let mask = if prefix_len == 0 { 0 } else { !0u128 << (128 - prefix_len) };
|
let mask = if prefix_len == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
!0u128 << (128 - prefix_len)
|
||||||
|
};
|
||||||
Ok((ip_bits & mask) == (network_bits & mask))
|
Ok((ip_bits & mask) == (network_bits & mask))
|
||||||
}
|
}
|
||||||
_ => Err("IP address and CIDR must be same version (IPv4 or IPv6)".to_string()),
|
_ => Err("IP address and CIDR must be same version (IPv4 or IPv6)".to_string()),
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ async fn handle_login(
|
|||||||
|
|
||||||
// If a URL was provided and the target profile doesn't exist yet, create it.
|
// If a URL was provided and the target profile doesn't exist yet, create it.
|
||||||
if !config.profiles.contains_key(&target_profile_name) {
|
if !config.profiles.contains_key(&target_profile_name) {
|
||||||
let url = api_url.clone().unwrap_or_else(|| "http://localhost:8080".to_string());
|
let url = api_url
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "http://localhost:8080".to_string());
|
||||||
use crate::config::Profile;
|
use crate::config::Profile;
|
||||||
config.set_profile(
|
config.set_profile(
|
||||||
target_profile_name.clone(),
|
target_profile_name.clone(),
|
||||||
@@ -155,7 +157,10 @@ async fn handle_login(
|
|||||||
config.save()?;
|
config.save()?;
|
||||||
} else {
|
} else {
|
||||||
// Fallback: set_auth writes to the current profile.
|
// Fallback: set_auth writes to the current profile.
|
||||||
config.set_auth(response.access_token.clone(), response.refresh_token.clone())?;
|
config.set_auth(
|
||||||
|
response.access_token.clone(),
|
||||||
|
response.refresh_token.clone(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
match output_format {
|
match output_format {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ pub async fn handle_config_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_list(output_format: OutputFormat) -> Result<()> {
|
async fn handle_list(output_format: OutputFormat) -> Result<()> {
|
||||||
let config = CliConfig::load()?; // Config commands always use default profile
|
let config = CliConfig::load()?; // Config commands always use default profile
|
||||||
let all_config = config.list_all();
|
let all_config = config.list_all();
|
||||||
|
|
||||||
match output_format {
|
match output_format {
|
||||||
@@ -105,7 +105,7 @@ async fn handle_list(output_format: OutputFormat) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_get(key: String, output_format: OutputFormat) -> Result<()> {
|
async fn handle_get(key: String, output_format: OutputFormat) -> Result<()> {
|
||||||
let config = CliConfig::load()?; // Config commands always use default profile
|
let config = CliConfig::load()?; // Config commands always use default profile
|
||||||
let value = config.get_value(&key)?;
|
let value = config.get_value(&key)?;
|
||||||
|
|
||||||
match output_format {
|
match output_format {
|
||||||
@@ -125,7 +125,7 @@ async fn handle_get(key: String, output_format: OutputFormat) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_profiles(output_format: OutputFormat) -> Result<()> {
|
async fn handle_profiles(output_format: OutputFormat) -> Result<()> {
|
||||||
let config = CliConfig::load()?; // Config commands always use default profile
|
let config = CliConfig::load()?; // Config commands always use default profile
|
||||||
let profiles = config.list_profiles();
|
let profiles = config.list_profiles();
|
||||||
let current = &config.current_profile;
|
let current = &config.current_profile;
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ async fn handle_profiles(output_format: OutputFormat) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_current(output_format: OutputFormat) -> Result<()> {
|
async fn handle_current(output_format: OutputFormat) -> Result<()> {
|
||||||
let config = CliConfig::load()?; // Config commands always use default profile
|
let config = CliConfig::load()?; // Config commands always use default profile
|
||||||
|
|
||||||
match output_format {
|
match output_format {
|
||||||
OutputFormat::Json | OutputFormat::Yaml => {
|
OutputFormat::Json | OutputFormat::Yaml => {
|
||||||
@@ -266,7 +266,7 @@ async fn handle_remove_profile(name: String, output_format: OutputFormat) -> Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_show_profile(name: String, output_format: OutputFormat) -> Result<()> {
|
async fn handle_show_profile(name: String, output_format: OutputFormat) -> Result<()> {
|
||||||
let config = CliConfig::load()?; // Config commands always use default profile
|
let config = CliConfig::load()?; // Config commands always use default profile
|
||||||
let profile = config
|
let profile = config
|
||||||
.get_profile(&name)
|
.get_profile(&name)
|
||||||
.context(format!("Profile '{}' not found", name))?;
|
.context(format!("Profile '{}' not found", name))?;
|
||||||
|
|||||||
@@ -554,9 +554,7 @@ async fn handle_create(
|
|||||||
("Label", label.clone()),
|
("Label", label.clone()),
|
||||||
(
|
(
|
||||||
"Description",
|
"Description",
|
||||||
description
|
description.clone().unwrap_or_else(|| "(none)".to_string()),
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| "(none)".to_string()),
|
|
||||||
),
|
),
|
||||||
("Version", version.clone()),
|
("Version", version.clone()),
|
||||||
(
|
(
|
||||||
@@ -873,8 +871,8 @@ async fn handle_upload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read pack ref from pack.yaml so we can display it
|
// Read pack ref from pack.yaml so we can display it
|
||||||
let pack_yaml_content = std::fs::read_to_string(&pack_yaml_path)
|
let pack_yaml_content =
|
||||||
.context("Failed to read pack.yaml")?;
|
std::fs::read_to_string(&pack_yaml_path).context("Failed to read pack.yaml")?;
|
||||||
let pack_yaml: serde_yaml_ng::Value =
|
let pack_yaml: serde_yaml_ng::Value =
|
||||||
serde_yaml_ng::from_str(&pack_yaml_content).context("Failed to parse pack.yaml")?;
|
serde_yaml_ng::from_str(&pack_yaml_content).context("Failed to parse pack.yaml")?;
|
||||||
let pack_ref = pack_yaml
|
let pack_ref = pack_yaml
|
||||||
@@ -884,10 +882,7 @@ async fn handle_upload(
|
|||||||
|
|
||||||
match output_format {
|
match output_format {
|
||||||
OutputFormat::Table => {
|
OutputFormat::Table => {
|
||||||
output::print_info(&format!(
|
output::print_info(&format!("Uploading pack '{}' from: {}", pack_ref, path));
|
||||||
"Uploading pack '{}' from: {}",
|
|
||||||
pack_ref, path
|
|
||||||
));
|
|
||||||
output::print_info("Creating archive...");
|
output::print_info("Creating archive...");
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -995,9 +990,7 @@ fn append_dir_to_tar<W: std::io::Write>(
|
|||||||
append_dir_to_tar(tar, base, &entry_path)?;
|
append_dir_to_tar(tar, base, &entry_path)?;
|
||||||
} else if entry_path.is_file() {
|
} else if entry_path.is_file() {
|
||||||
tar.append_path_with_name(&entry_path, relative_path)
|
tar.append_path_with_name(&entry_path, relative_path)
|
||||||
.with_context(|| {
|
.with_context(|| format!("Failed to add {} to archive", entry_path.display()))?;
|
||||||
format!("Failed to add {} to archive", entry_path.display())
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
// symlinks are intentionally skipped
|
// symlinks are intentionally skipped
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,10 +219,7 @@ async fn handle_upload(
|
|||||||
(resolved from workflow_file: '{}' relative to '{}')",
|
(resolved from workflow_file: '{}' relative to '{}')",
|
||||||
workflow_path.display(),
|
workflow_path.display(),
|
||||||
workflow_file_rel,
|
workflow_file_rel,
|
||||||
action_path
|
action_path.parent().unwrap_or(Path::new(".")).display()
|
||||||
.parent()
|
|
||||||
.unwrap_or(Path::new("."))
|
|
||||||
.display()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,8 +227,8 @@ async fn handle_upload(
|
|||||||
let workflow_yaml_content =
|
let workflow_yaml_content =
|
||||||
std::fs::read_to_string(&workflow_path).context("Failed to read workflow YAML file")?;
|
std::fs::read_to_string(&workflow_path).context("Failed to read workflow YAML file")?;
|
||||||
|
|
||||||
let workflow_definition: serde_json::Value =
|
let workflow_definition: serde_json::Value = serde_yaml_ng::from_str(&workflow_yaml_content)
|
||||||
serde_yaml_ng::from_str(&workflow_yaml_content).context(format!(
|
.context(format!(
|
||||||
"Failed to parse workflow YAML file: {}",
|
"Failed to parse workflow YAML file: {}",
|
||||||
workflow_path.display()
|
workflow_path.display()
|
||||||
))?;
|
))?;
|
||||||
@@ -411,7 +408,15 @@ async fn handle_list(
|
|||||||
let mut table = output::create_table();
|
let mut table = output::create_table();
|
||||||
output::add_header(
|
output::add_header(
|
||||||
&mut table,
|
&mut table,
|
||||||
vec!["ID", "Reference", "Pack", "Label", "Version", "Enabled", "Tags"],
|
vec![
|
||||||
|
"ID",
|
||||||
|
"Reference",
|
||||||
|
"Pack",
|
||||||
|
"Label",
|
||||||
|
"Version",
|
||||||
|
"Enabled",
|
||||||
|
"Tags",
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
for wf in &workflows {
|
for wf in &workflows {
|
||||||
@@ -512,14 +517,8 @@ async fn handle_show(
|
|||||||
output::add_header(&mut table, vec!["#", "Name", "Action", "Transitions"]);
|
output::add_header(&mut table, vec!["#", "Name", "Action", "Transitions"]);
|
||||||
|
|
||||||
for (i, task) in arr.iter().enumerate() {
|
for (i, task) in arr.iter().enumerate() {
|
||||||
let name = task
|
let name = task.get("name").and_then(|v| v.as_str()).unwrap_or("?");
|
||||||
.get("name")
|
let action = task.get("action").and_then(|v| v.as_str()).unwrap_or("-");
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.unwrap_or("?");
|
|
||||||
let action = task
|
|
||||||
.get("action")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.unwrap_or("-");
|
|
||||||
|
|
||||||
let transition_count = task
|
let transition_count = task
|
||||||
.get("next")
|
.get("next")
|
||||||
@@ -589,7 +588,8 @@ async fn handle_delete(
|
|||||||
|
|
||||||
match output_format {
|
match output_format {
|
||||||
OutputFormat::Json | OutputFormat::Yaml => {
|
OutputFormat::Json | OutputFormat::Yaml => {
|
||||||
let msg = serde_json::json!({"message": format!("Workflow '{}' deleted", workflow_ref)});
|
let msg =
|
||||||
|
serde_json::json!({"message": format!("Workflow '{}' deleted", workflow_ref)});
|
||||||
output::print_output(&msg, output_format)?;
|
output::print_output(&msg, output_format)?;
|
||||||
}
|
}
|
||||||
OutputFormat::Table => {
|
OutputFormat::Table => {
|
||||||
@@ -635,9 +635,7 @@ fn split_action_ref(action_ref: &str) -> Result<(String, String)> {
|
|||||||
/// YAML is typically at `<pack>/actions/<name>.yaml`, the workflow path is
|
/// YAML is typically at `<pack>/actions/<name>.yaml`, the workflow path is
|
||||||
/// resolved relative to the action YAML's parent directory.
|
/// resolved relative to the action YAML's parent directory.
|
||||||
fn resolve_workflow_path(action_yaml_path: &Path, workflow_file: &str) -> Result<PathBuf> {
|
fn resolve_workflow_path(action_yaml_path: &Path, workflow_file: &str) -> Result<PathBuf> {
|
||||||
let action_dir = action_yaml_path
|
let action_dir = action_yaml_path.parent().unwrap_or(Path::new("."));
|
||||||
.parent()
|
|
||||||
.unwrap_or(Path::new("."));
|
|
||||||
|
|
||||||
let resolved = action_dir.join(workflow_file);
|
let resolved = action_dir.join(workflow_file);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
//! Integration tests for CLI action commands
|
//! Integration tests for CLI action commands
|
||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
|
|
||||||
|
|
||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
use predicates::prelude::*;
|
use predicates::prelude::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|||||||
@@ -546,8 +546,7 @@ async fn test_workflow_upload_success() {
|
|||||||
let fixture = TestFixture::new().await;
|
let fixture = TestFixture::new().await;
|
||||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||||
|
|
||||||
let wf_fixture =
|
let wf_fixture = WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
||||||
WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
|
||||||
|
|
||||||
mock_workflow_save(&fixture.mock_server, "mypack").await;
|
mock_workflow_save(&fixture.mock_server, "mypack").await;
|
||||||
|
|
||||||
@@ -571,8 +570,7 @@ async fn test_workflow_upload_json_output() {
|
|||||||
let fixture = TestFixture::new().await;
|
let fixture = TestFixture::new().await;
|
||||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||||
|
|
||||||
let wf_fixture =
|
let wf_fixture = WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
||||||
WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
|
||||||
|
|
||||||
mock_workflow_save(&fixture.mock_server, "mypack").await;
|
mock_workflow_save(&fixture.mock_server, "mypack").await;
|
||||||
|
|
||||||
@@ -597,8 +595,7 @@ async fn test_workflow_upload_conflict_without_force() {
|
|||||||
let fixture = TestFixture::new().await;
|
let fixture = TestFixture::new().await;
|
||||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||||
|
|
||||||
let wf_fixture =
|
let wf_fixture = WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
||||||
WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
|
||||||
|
|
||||||
mock_workflow_save_conflict(&fixture.mock_server, "mypack").await;
|
mock_workflow_save_conflict(&fixture.mock_server, "mypack").await;
|
||||||
|
|
||||||
@@ -622,8 +619,7 @@ async fn test_workflow_upload_conflict_with_force() {
|
|||||||
let fixture = TestFixture::new().await;
|
let fixture = TestFixture::new().await;
|
||||||
fixture.write_authenticated_config("valid_token", "refresh_token");
|
fixture.write_authenticated_config("valid_token", "refresh_token");
|
||||||
|
|
||||||
let wf_fixture =
|
let wf_fixture = WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
||||||
WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");
|
|
||||||
|
|
||||||
mock_workflow_save_conflict(&fixture.mock_server, "mypack").await;
|
mock_workflow_save_conflict(&fixture.mock_server, "mypack").await;
|
||||||
mock_workflow_update(&fixture.mock_server, "mypack.deploy").await;
|
mock_workflow_update(&fixture.mock_server, "mypack.deploy").await;
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ impl RegistryClient {
|
|||||||
|
|
||||||
let http_client = reqwest::Client::builder()
|
let http_client = reqwest::Client::builder()
|
||||||
.timeout(timeout)
|
.timeout(timeout)
|
||||||
.user_agent(format!("attune-registry-client/{}", env!("CARGO_PKG_VERSION")))
|
.user_agent(format!(
|
||||||
|
"attune-registry-client/{}",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
))
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| Error::Internal(format!("Failed to create HTTP client: {}", e)))?;
|
.map_err(|e| Error::Internal(format!("Failed to create HTTP client: {}", e)))?;
|
||||||
|
|
||||||
@@ -69,7 +72,9 @@ impl RegistryClient {
|
|||||||
|
|
||||||
/// Get all enabled registries sorted by priority (lower number = higher priority)
|
/// Get all enabled registries sorted by priority (lower number = higher priority)
|
||||||
pub fn get_registries(&self) -> Vec<RegistryIndexConfig> {
|
pub fn get_registries(&self) -> Vec<RegistryIndexConfig> {
|
||||||
let mut registries: Vec<_> = self.config.indices
|
let mut registries: Vec<_> = self
|
||||||
|
.config
|
||||||
|
.indices
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.enabled)
|
.filter(|r| r.enabled)
|
||||||
.cloned()
|
.cloned()
|
||||||
@@ -156,7 +161,8 @@ impl RegistryClient {
|
|||||||
|
|
||||||
/// Fetch index from file:// URL
|
/// Fetch index from file:// URL
|
||||||
async fn fetch_index_from_file(&self, url: &str) -> Result<PackIndex> {
|
async fn fetch_index_from_file(&self, url: &str) -> Result<PackIndex> {
|
||||||
let path = url.strip_prefix("file://")
|
let path = url
|
||||||
|
.strip_prefix("file://")
|
||||||
.ok_or_else(|| Error::Configuration(format!("Invalid file URL: {}", url)))?;
|
.ok_or_else(|| Error::Configuration(format!("Invalid file URL: {}", url)))?;
|
||||||
|
|
||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
@@ -209,11 +215,7 @@ impl RegistryClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(
|
tracing::warn!("Failed to fetch registry {}: {}", registry.url, e);
|
||||||
"Failed to fetch registry {}: {}",
|
|
||||||
registry.url,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,7 +238,10 @@ impl RegistryClient {
|
|||||||
let matches = pack.pack_ref.to_lowercase().contains(&keyword_lower)
|
let matches = pack.pack_ref.to_lowercase().contains(&keyword_lower)
|
||||||
|| pack.label.to_lowercase().contains(&keyword_lower)
|
|| pack.label.to_lowercase().contains(&keyword_lower)
|
||||||
|| pack.description.to_lowercase().contains(&keyword_lower)
|
|| pack.description.to_lowercase().contains(&keyword_lower)
|
||||||
|| pack.keywords.iter().any(|k| k.to_lowercase().contains(&keyword_lower));
|
|| pack
|
||||||
|
.keywords
|
||||||
|
.iter()
|
||||||
|
.any(|k| k.to_lowercase().contains(&keyword_lower));
|
||||||
|
|
||||||
if matches {
|
if matches {
|
||||||
results.push((pack, registry.url.clone()));
|
results.push((pack, registry.url.clone()));
|
||||||
@@ -244,11 +249,7 @@ impl RegistryClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(
|
tracing::warn!("Failed to fetch registry {}: {}", registry.url, e);
|
||||||
"Failed to fetch registry {}: {}",
|
|
||||||
registry.url,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,7 +265,9 @@ impl RegistryClient {
|
|||||||
registry_name: &str,
|
registry_name: &str,
|
||||||
) -> Result<Option<PackIndexEntry>> {
|
) -> Result<Option<PackIndexEntry>> {
|
||||||
// Find registry by name
|
// Find registry by name
|
||||||
let registry = self.config.indices
|
let registry = self
|
||||||
|
.config
|
||||||
|
.indices
|
||||||
.iter()
|
.iter()
|
||||||
.find(|r| r.name.as_deref() == Some(registry_name))
|
.find(|r| r.name.as_deref() == Some(registry_name))
|
||||||
.ok_or_else(|| Error::not_found("registry", "name", registry_name))?;
|
.ok_or_else(|| Error::not_found("registry", "name", registry_name))?;
|
||||||
|
|||||||
@@ -23,15 +23,9 @@ pub type ProgressCallback = Arc<dyn Fn(ProgressEvent) + Send + Sync>;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ProgressEvent {
|
pub enum ProgressEvent {
|
||||||
/// Started a new step
|
/// Started a new step
|
||||||
StepStarted {
|
StepStarted { step: String, message: String },
|
||||||
step: String,
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
/// Step completed
|
/// Step completed
|
||||||
StepCompleted {
|
StepCompleted { step: String, message: String },
|
||||||
step: String,
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
/// Download progress
|
/// Download progress
|
||||||
Downloading {
|
Downloading {
|
||||||
url: String,
|
url: String,
|
||||||
@@ -39,21 +33,13 @@ pub enum ProgressEvent {
|
|||||||
total_bytes: Option<u64>,
|
total_bytes: Option<u64>,
|
||||||
},
|
},
|
||||||
/// Extraction progress
|
/// Extraction progress
|
||||||
Extracting {
|
Extracting { file: String },
|
||||||
file: String,
|
|
||||||
},
|
|
||||||
/// Verification progress
|
/// Verification progress
|
||||||
Verifying {
|
Verifying { message: String },
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
/// Warning message
|
/// Warning message
|
||||||
Warning {
|
Warning { message: String },
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
/// Info message
|
/// Info message
|
||||||
Info {
|
Info { message: String },
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pack installer for handling various installation sources
|
/// Pack installer for handling various installation sources
|
||||||
@@ -151,12 +137,15 @@ impl PackInstaller {
|
|||||||
/// Install a pack from the given source
|
/// Install a pack from the given source
|
||||||
pub async fn install(&self, source: PackSource) -> Result<InstalledPack> {
|
pub async fn install(&self, source: PackSource) -> Result<InstalledPack> {
|
||||||
match source {
|
match source {
|
||||||
PackSource::Git { url, git_ref } => self.install_from_git(&url, git_ref.as_deref()).await,
|
PackSource::Git { url, git_ref } => {
|
||||||
|
self.install_from_git(&url, git_ref.as_deref()).await
|
||||||
|
}
|
||||||
PackSource::Archive { url } => self.install_from_archive_url(&url, None).await,
|
PackSource::Archive { url } => self.install_from_archive_url(&url, None).await,
|
||||||
PackSource::LocalDirectory { path } => self.install_from_local_directory(&path).await,
|
PackSource::LocalDirectory { path } => self.install_from_local_directory(&path).await,
|
||||||
PackSource::LocalArchive { path } => self.install_from_local_archive(&path).await,
|
PackSource::LocalArchive { path } => self.install_from_local_archive(&path).await,
|
||||||
PackSource::Registry { pack_ref, version } => {
|
PackSource::Registry { pack_ref, version } => {
|
||||||
self.install_from_registry(&pack_ref, version.as_deref()).await
|
self.install_from_registry(&pack_ref, version.as_deref())
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +256,11 @@ impl PackInstaller {
|
|||||||
|
|
||||||
// Verify source exists and is a directory
|
// Verify source exists and is a directory
|
||||||
if !source_path.exists() {
|
if !source_path.exists() {
|
||||||
return Err(Error::not_found("directory", "path", source_path.display().to_string()));
|
return Err(Error::not_found(
|
||||||
|
"directory",
|
||||||
|
"path",
|
||||||
|
source_path.display().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !source_path.is_dir() {
|
if !source_path.is_dir() {
|
||||||
@@ -301,7 +294,11 @@ impl PackInstaller {
|
|||||||
|
|
||||||
// Verify file exists
|
// Verify file exists
|
||||||
if !archive_path.exists() {
|
if !archive_path.exists() {
|
||||||
return Err(Error::not_found("file", "path", archive_path.display().to_string()));
|
return Err(Error::not_found(
|
||||||
|
"file",
|
||||||
|
"path",
|
||||||
|
archive_path.display().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !archive_path.is_file() {
|
if !archive_path.is_file() {
|
||||||
@@ -369,9 +366,7 @@ impl PackInstaller {
|
|||||||
git_ref,
|
git_ref,
|
||||||
checksum,
|
checksum,
|
||||||
} => {
|
} => {
|
||||||
let mut installed = self
|
let mut installed = self.install_from_git(&url, git_ref.as_deref()).await?;
|
||||||
.install_from_git(&url, git_ref.as_deref())
|
|
||||||
.await?;
|
|
||||||
installed.checksum = Some(checksum);
|
installed.checksum = Some(checksum);
|
||||||
Ok(installed)
|
Ok(installed)
|
||||||
}
|
}
|
||||||
@@ -426,11 +421,7 @@ impl PackInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine filename from URL
|
// Determine filename from URL
|
||||||
let filename = url
|
let filename = url.split('/').last().unwrap_or("archive.zip").to_string();
|
||||||
.split('/')
|
|
||||||
.last()
|
|
||||||
.unwrap_or("archive.zip")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let archive_path = self.temp_dir.join(&filename);
|
let archive_path = self.temp_dir.join(&filename);
|
||||||
|
|
||||||
@@ -483,7 +474,10 @@ impl PackInstaller {
|
|||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
return Err(Error::internal(format!("Failed to extract zip: {}", stderr)));
|
return Err(Error::internal(format!(
|
||||||
|
"Failed to extract zip: {}",
|
||||||
|
stderr
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -502,22 +496,23 @@ impl PackInstaller {
|
|||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
return Err(Error::internal(format!("Failed to extract tar.gz: {}", stderr)));
|
return Err(Error::internal(format!(
|
||||||
|
"Failed to extract tar.gz: {}",
|
||||||
|
stderr
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify archive checksum
|
/// Verify archive checksum
|
||||||
async fn verify_archive_checksum(
|
async fn verify_archive_checksum(&self, archive_path: &Path, checksum_str: &str) -> Result<()> {
|
||||||
&self,
|
|
||||||
archive_path: &Path,
|
|
||||||
checksum_str: &str,
|
|
||||||
) -> Result<()> {
|
|
||||||
let checksum = Checksum::parse(checksum_str)
|
let checksum = Checksum::parse(checksum_str)
|
||||||
.map_err(|e| Error::validation(format!("Invalid checksum: {}", e)))?;
|
.map_err(|e| Error::validation(format!("Invalid checksum: {}", e)))?;
|
||||||
|
|
||||||
let computed = self.compute_checksum(archive_path, &checksum.algorithm).await?;
|
let computed = self
|
||||||
|
.compute_checksum(archive_path, &checksum.algorithm)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if computed != checksum.hash {
|
if computed != checksum.hash {
|
||||||
return Err(Error::validation(format!(
|
return Err(Error::validation(format!(
|
||||||
@@ -553,7 +548,10 @@ impl PackInstaller {
|
|||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
return Err(Error::internal(format!("Checksum computation failed: {}", stderr)));
|
return Err(Error::internal(format!(
|
||||||
|
"Checksum computation failed: {}",
|
||||||
|
stderr
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
@@ -611,9 +609,9 @@ impl PackInstaller {
|
|||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
// Create destination directory if it doesn't exist
|
// Create destination directory if it doesn't exist
|
||||||
fs::create_dir_all(dst)
|
fs::create_dir_all(dst).await.map_err(|e| {
|
||||||
.await
|
Error::internal(format!("Failed to create destination directory: {}", e))
|
||||||
.map_err(|e| Error::internal(format!("Failed to create destination directory: {}", e)))?;
|
})?;
|
||||||
|
|
||||||
// Read source directory
|
// Read source directory
|
||||||
let mut entries = fs::read_dir(src)
|
let mut entries = fs::read_dir(src)
|
||||||
|
|||||||
@@ -145,7 +145,8 @@ impl PackStorage {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let entry = entry.map_err(|e| Error::io(format!("Failed to read directory entry: {}", e)))?;
|
let entry =
|
||||||
|
entry.map_err(|e| Error::io(format!("Failed to read directory entry: {}", e)))?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
||||||
@@ -209,13 +210,21 @@ pub fn calculate_directory_checksum<P: AsRef<Path>>(path: P) -> Result<String> {
|
|||||||
|
|
||||||
// Hash file contents
|
// Hash file contents
|
||||||
let mut file = fs::File::open(&file_path).map_err(|e| {
|
let mut file = fs::File::open(&file_path).map_err(|e| {
|
||||||
Error::io(format!("Failed to open file {}: {}", file_path.display(), e))
|
Error::io(format!(
|
||||||
|
"Failed to open file {}: {}",
|
||||||
|
file_path.display(),
|
||||||
|
e
|
||||||
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut buffer = [0u8; 8192];
|
let mut buffer = [0u8; 8192];
|
||||||
loop {
|
loop {
|
||||||
let n = file.read(&mut buffer).map_err(|e| {
|
let n = file.read(&mut buffer).map_err(|e| {
|
||||||
Error::io(format!("Failed to read file {}: {}", file_path.display(), e))
|
Error::io(format!(
|
||||||
|
"Failed to read file {}: {}",
|
||||||
|
file_path.display(),
|
||||||
|
e
|
||||||
|
))
|
||||||
})?;
|
})?;
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
break;
|
break;
|
||||||
@@ -255,15 +264,14 @@ pub fn calculate_file_checksum<P: AsRef<Path>>(path: P) -> Result<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
let mut file = fs::File::open(path).map_err(|e| {
|
let mut file = fs::File::open(path)
|
||||||
Error::io(format!("Failed to open file {}: {}", path.display(), e))
|
.map_err(|e| Error::io(format!("Failed to open file {}: {}", path.display(), e)))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut buffer = [0u8; 8192];
|
let mut buffer = [0u8; 8192];
|
||||||
loop {
|
loop {
|
||||||
let n = file.read(&mut buffer).map_err(|e| {
|
let n = file
|
||||||
Error::io(format!("Failed to read file {}: {}", path.display(), e))
|
.read(&mut buffer)
|
||||||
})?;
|
.map_err(|e| Error::io(format!("Failed to read file {}: {}", path.display(), e)))?;
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -291,7 +299,8 @@ fn copy_dir_all(src: &Path, dst: &Path) -> Result<()> {
|
|||||||
e
|
e
|
||||||
))
|
))
|
||||||
})? {
|
})? {
|
||||||
let entry = entry.map_err(|e| Error::io(format!("Failed to read directory entry: {}", e)))?;
|
let entry =
|
||||||
|
entry.map_err(|e| Error::io(format!("Failed to read directory entry: {}", e)))?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let file_name = entry.file_name();
|
let file_name = entry.file_name();
|
||||||
let dest_path = dst.join(&file_name);
|
let dest_path = dst.join(&file_name);
|
||||||
|
|||||||
@@ -332,9 +332,8 @@ impl ExecutionRepository {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
let select_clause = format!(
|
let select_clause =
|
||||||
"{prefixed_select}, enf.rule_ref AS rule_ref, enf.trigger_ref AS trigger_ref"
|
format!("{prefixed_select}, enf.rule_ref AS rule_ref, enf.trigger_ref AS trigger_ref");
|
||||||
);
|
|
||||||
|
|
||||||
let from_clause = "FROM execution e LEFT JOIN enforcement enf ON e.enforcement = enf.id";
|
let from_clause = "FROM execution e LEFT JOIN enforcement enf ON e.enforcement = enf.id";
|
||||||
|
|
||||||
@@ -425,10 +424,7 @@ impl ExecutionRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── COUNT query ──────────────────────────────────────────────────
|
// ── COUNT query ──────────────────────────────────────────────────
|
||||||
let total: i64 = count_qb
|
let total: i64 = count_qb.build_query_scalar().fetch_one(db).await?;
|
||||||
.build_query_scalar()
|
|
||||||
.fetch_one(db)
|
|
||||||
.await?;
|
|
||||||
let total = total.max(0) as u64;
|
let total = total.max(0) as u64;
|
||||||
|
|
||||||
// ── Data query with ORDER BY + pagination ────────────────────────
|
// ── Data query with ORDER BY + pagination ────────────────────────
|
||||||
@@ -438,10 +434,7 @@ impl ExecutionRepository {
|
|||||||
qb.push(" OFFSET ");
|
qb.push(" OFFSET ");
|
||||||
qb.push_bind(filters.offset as i64);
|
qb.push_bind(filters.offset as i64);
|
||||||
|
|
||||||
let rows: Vec<ExecutionWithRefs> = qb
|
let rows: Vec<ExecutionWithRefs> = qb.build_query_as().fetch_all(db).await?;
|
||||||
.build_query_as()
|
|
||||||
.fetch_all(db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(ExecutionSearchResult { rows, total })
|
Ok(ExecutionSearchResult { rows, total })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -556,11 +556,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_context_without_event_metadata() {
|
fn test_context_without_event_metadata() {
|
||||||
// Context with only a payload — no id, trigger, or created
|
// Context with only a payload — no id, trigger, or created
|
||||||
let context = TemplateContext::new(
|
let context = TemplateContext::new(json!({"service": "test"}), json!({}), json!({}));
|
||||||
json!({"service": "test"}),
|
|
||||||
json!({}),
|
|
||||||
json!({}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let template = json!({
|
let template = json!({
|
||||||
"service": "{{ event.payload.service }}",
|
"service": "{{ event.payload.service }}",
|
||||||
|
|||||||
@@ -87,26 +87,14 @@ pub enum Expr {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/// Unary operation: `op operand`
|
/// Unary operation: `op operand`
|
||||||
UnaryOp {
|
UnaryOp { op: UnaryOp, operand: Box<Expr> },
|
||||||
op: UnaryOp,
|
|
||||||
operand: Box<Expr>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Property access: `expr.field`
|
/// Property access: `expr.field`
|
||||||
DotAccess {
|
DotAccess { object: Box<Expr>, field: String },
|
||||||
object: Box<Expr>,
|
|
||||||
field: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Index/bracket access: `expr[index_expr]`
|
/// Index/bracket access: `expr[index_expr]`
|
||||||
IndexAccess {
|
IndexAccess { object: Box<Expr>, index: Box<Expr> },
|
||||||
object: Box<Expr>,
|
|
||||||
index: Box<Expr>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Function call: `name(arg1, arg2, ...)`
|
/// Function call: `name(arg1, arg2, ...)`
|
||||||
FunctionCall {
|
FunctionCall { name: String, args: Vec<Expr> },
|
||||||
name: String,
|
|
||||||
args: Vec<Expr>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -741,7 +741,9 @@ fn to_int(v: &JsonValue) -> EvalResult<JsonValue> {
|
|||||||
} else if let Some(f) = n.as_f64() {
|
} else if let Some(f) = n.as_f64() {
|
||||||
Ok(json!(f as i64))
|
Ok(json!(f as i64))
|
||||||
} else {
|
} else {
|
||||||
Err(EvalError::TypeError("Cannot convert number to int".to_string()))
|
Err(EvalError::TypeError(
|
||||||
|
"Cannot convert number to int".to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JsonValue::String(s) => {
|
JsonValue::String(s) => {
|
||||||
@@ -958,9 +960,7 @@ fn fn_join(arr: &JsonValue, sep: &JsonValue) -> EvalResult<JsonValue> {
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let sep = require_string("join", sep)?;
|
let sep = require_string("join", sep)?;
|
||||||
let strings: Result<Vec<String>, _> = arr.iter().map(|v| {
|
let strings: Result<Vec<String>, _> = arr.iter().map(|v| Ok(value_to_string(v))).collect();
|
||||||
Ok(value_to_string(v))
|
|
||||||
}).collect();
|
|
||||||
Ok(json!(strings?.join(sep)))
|
Ok(json!(strings?.join(sep)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,8 +986,7 @@ fn fn_ends_with(s: &JsonValue, suffix: &JsonValue) -> EvalResult<JsonValue> {
|
|||||||
fn fn_match(pattern: &JsonValue, s: &JsonValue) -> EvalResult<JsonValue> {
|
fn fn_match(pattern: &JsonValue, s: &JsonValue) -> EvalResult<JsonValue> {
|
||||||
let pattern = require_string("match", pattern)?;
|
let pattern = require_string("match", pattern)?;
|
||||||
let s = require_string("match", s)?;
|
let s = require_string("match", s)?;
|
||||||
let re = Regex::new(pattern)
|
let re = Regex::new(pattern).map_err(|e| EvalError::RegexError(format!("{}", e)))?;
|
||||||
.map_err(|e| EvalError::RegexError(format!("{}", e)))?;
|
|
||||||
Ok(json!(re.is_match(s)))
|
Ok(json!(re.is_match(s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,9 +1011,7 @@ fn fn_reversed(v: &JsonValue) -> EvalResult<JsonValue> {
|
|||||||
rev.reverse();
|
rev.reverse();
|
||||||
Ok(JsonValue::Array(rev))
|
Ok(JsonValue::Array(rev))
|
||||||
}
|
}
|
||||||
JsonValue::String(s) => {
|
JsonValue::String(s) => Ok(json!(s.chars().rev().collect::<String>())),
|
||||||
Ok(json!(s.chars().rev().collect::<String>()))
|
|
||||||
}
|
|
||||||
_ => Err(EvalError::TypeError(format!(
|
_ => Err(EvalError::TypeError(format!(
|
||||||
"reversed() requires array or string, got {}",
|
"reversed() requires array or string, got {}",
|
||||||
type_name(v)
|
type_name(v)
|
||||||
@@ -1095,7 +1092,10 @@ fn fn_flat(v: &JsonValue) -> EvalResult<JsonValue> {
|
|||||||
|
|
||||||
fn fn_zip(a: &JsonValue, b: &JsonValue) -> EvalResult<JsonValue> {
|
fn fn_zip(a: &JsonValue, b: &JsonValue) -> EvalResult<JsonValue> {
|
||||||
let a_arr = a.as_array().ok_or_else(|| {
|
let a_arr = a.as_array().ok_or_else(|| {
|
||||||
EvalError::TypeError(format!("zip() first argument must be array, got {}", type_name(a)))
|
EvalError::TypeError(format!(
|
||||||
|
"zip() first argument must be array, got {}",
|
||||||
|
type_name(a)
|
||||||
|
))
|
||||||
})?;
|
})?;
|
||||||
let b_arr = b.as_array().ok_or_else(|| {
|
let b_arr = b.as_array().ok_or_else(|| {
|
||||||
EvalError::TypeError(format!(
|
EvalError::TypeError(format!(
|
||||||
@@ -1114,37 +1114,38 @@ fn fn_zip(a: &JsonValue, b: &JsonValue) -> EvalResult<JsonValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fn_range_1(end: &JsonValue) -> EvalResult<JsonValue> {
|
fn fn_range_1(end: &JsonValue) -> EvalResult<JsonValue> {
|
||||||
let n = end.as_i64().ok_or_else(|| {
|
let n = end
|
||||||
EvalError::TypeError("range() requires integer argument".to_string())
|
.as_i64()
|
||||||
})?;
|
.ok_or_else(|| EvalError::TypeError("range() requires integer argument".to_string()))?;
|
||||||
let arr: Vec<JsonValue> = (0..n).map(|i| json!(i)).collect();
|
let arr: Vec<JsonValue> = (0..n).map(|i| json!(i)).collect();
|
||||||
Ok(JsonValue::Array(arr))
|
Ok(JsonValue::Array(arr))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn_range_2(start: &JsonValue, end: &JsonValue) -> EvalResult<JsonValue> {
|
fn fn_range_2(start: &JsonValue, end: &JsonValue) -> EvalResult<JsonValue> {
|
||||||
let s = start.as_i64().ok_or_else(|| {
|
let s = start
|
||||||
EvalError::TypeError("range() requires integer arguments".to_string())
|
.as_i64()
|
||||||
})?;
|
.ok_or_else(|| EvalError::TypeError("range() requires integer arguments".to_string()))?;
|
||||||
let e = end.as_i64().ok_or_else(|| {
|
let e = end
|
||||||
EvalError::TypeError("range() requires integer arguments".to_string())
|
.as_i64()
|
||||||
})?;
|
.ok_or_else(|| EvalError::TypeError("range() requires integer arguments".to_string()))?;
|
||||||
let arr: Vec<JsonValue> = (s..e).map(|i| json!(i)).collect();
|
let arr: Vec<JsonValue> = (s..e).map(|i| json!(i)).collect();
|
||||||
Ok(JsonValue::Array(arr))
|
Ok(JsonValue::Array(arr))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn_slice(v: &JsonValue, start: &JsonValue, end: &JsonValue) -> EvalResult<JsonValue> {
|
fn fn_slice(v: &JsonValue, start: &JsonValue, end: &JsonValue) -> EvalResult<JsonValue> {
|
||||||
let s = start.as_i64().ok_or_else(|| {
|
let s = start
|
||||||
EvalError::TypeError("slice() start must be integer".to_string())
|
.as_i64()
|
||||||
})? as usize;
|
.ok_or_else(|| EvalError::TypeError("slice() start must be integer".to_string()))?
|
||||||
|
as usize;
|
||||||
|
|
||||||
match v {
|
match v {
|
||||||
JsonValue::Array(arr) => {
|
JsonValue::Array(arr) => {
|
||||||
let e = if end.is_null() {
|
let e = if end.is_null() {
|
||||||
arr.len()
|
arr.len()
|
||||||
} else {
|
} else {
|
||||||
end.as_i64()
|
end.as_i64().ok_or_else(|| {
|
||||||
.ok_or_else(|| EvalError::TypeError("slice() end must be integer".to_string()))?
|
EvalError::TypeError("slice() end must be integer".to_string())
|
||||||
as usize
|
})? as usize
|
||||||
};
|
};
|
||||||
let e = e.min(arr.len());
|
let e = e.min(arr.len());
|
||||||
let s = s.min(e);
|
let s = s.min(e);
|
||||||
@@ -1155,9 +1156,9 @@ fn fn_slice(v: &JsonValue, start: &JsonValue, end: &JsonValue) -> EvalResult<Jso
|
|||||||
let e = if end.is_null() {
|
let e = if end.is_null() {
|
||||||
chars.len()
|
chars.len()
|
||||||
} else {
|
} else {
|
||||||
end.as_i64()
|
end.as_i64().ok_or_else(|| {
|
||||||
.ok_or_else(|| EvalError::TypeError("slice() end must be integer".to_string()))?
|
EvalError::TypeError("slice() end must be integer".to_string())
|
||||||
as usize
|
})? as usize
|
||||||
};
|
};
|
||||||
let e = e.min(chars.len());
|
let e = e.min(chars.len());
|
||||||
let s = s.min(e);
|
let s = s.min(e);
|
||||||
@@ -1182,7 +1183,9 @@ fn fn_index_of(haystack: &JsonValue, needle: &JsonValue) -> EvalResult<JsonValue
|
|||||||
}
|
}
|
||||||
JsonValue::String(s) => {
|
JsonValue::String(s) => {
|
||||||
let needle = needle.as_str().ok_or_else(|| {
|
let needle = needle.as_str().ok_or_else(|| {
|
||||||
EvalError::TypeError("index_of() needle must be string for string search".to_string())
|
EvalError::TypeError(
|
||||||
|
"index_of() needle must be string for string search".to_string(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
match s.find(needle) {
|
match s.find(needle) {
|
||||||
Some(pos) => Ok(json!(pos as i64)),
|
Some(pos) => Ok(json!(pos as i64)),
|
||||||
@@ -1292,10 +1295,7 @@ mod tests {
|
|||||||
&json!({"a": [1, 2], "b": {"c": 3}}),
|
&json!({"a": [1, 2], "b": {"c": 3}}),
|
||||||
&json!({"b": {"c": 3}, "a": [1, 2]})
|
&json!({"b": {"c": 3}, "a": [1, 2]})
|
||||||
));
|
));
|
||||||
assert!(!json_eq(
|
assert!(!json_eq(&json!({"a": [1, 2]}), &json!({"a": [1, 3]})));
|
||||||
&json!({"a": [1, 2]}),
|
|
||||||
&json!({"a": [1, 3]})
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ use serde_json::Value as JsonValue;
|
|||||||
/// This is the main entry point for the expression engine. It tokenizes the
|
/// This is the main entry point for the expression engine. It tokenizes the
|
||||||
/// input, parses it into an AST, and evaluates it to produce a `JsonValue`.
|
/// input, parses it into an AST, and evaluates it to produce a `JsonValue`.
|
||||||
pub fn eval_expression(input: &str, ctx: &dyn EvalContext) -> EvalResult<JsonValue> {
|
pub fn eval_expression(input: &str, ctx: &dyn EvalContext) -> EvalResult<JsonValue> {
|
||||||
let tokens = Tokenizer::new(input).tokenize().map_err(|e| {
|
let tokens = Tokenizer::new(input)
|
||||||
EvalError::ParseError(format!("{}", e))
|
.tokenize()
|
||||||
})?;
|
.map_err(|e| EvalError::ParseError(format!("{}", e)))?;
|
||||||
let ast = Parser::new(&tokens).parse().map_err(|e| {
|
let ast = Parser::new(&tokens)
|
||||||
EvalError::ParseError(format!("{}", e))
|
.parse()
|
||||||
})?;
|
.map_err(|e| EvalError::ParseError(format!("{}", e)))?;
|
||||||
evaluator::eval(&ast, ctx)
|
evaluator::eval(&ast, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +84,9 @@ pub fn eval_expression(input: &str, ctx: &dyn EvalContext) -> EvalResult<JsonVal
|
|||||||
///
|
///
|
||||||
/// Useful for validation or inspection.
|
/// Useful for validation or inspection.
|
||||||
pub fn parse_expression(input: &str) -> Result<Expr, ParseError> {
|
pub fn parse_expression(input: &str) -> Result<Expr, ParseError> {
|
||||||
let tokens = Tokenizer::new(input).tokenize().map_err(|e| {
|
let tokens = Tokenizer::new(input)
|
||||||
ParseError::TokenError(format!("{}", e))
|
.tokenize()
|
||||||
})?;
|
.map_err(|e| ParseError::TokenError(format!("{}", e)))?;
|
||||||
Parser::new(&tokens).parse()
|
Parser::new(&tokens).parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,10 @@ mod tests {
|
|||||||
fn test_float_arithmetic() {
|
fn test_float_arithmetic() {
|
||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
assert_eq!(eval_expression("2.5 + 1.5", &ctx).unwrap(), json!(4.0));
|
assert_eq!(eval_expression("2.5 + 1.5", &ctx).unwrap(), json!(4.0));
|
||||||
assert_eq!(eval_expression("10.0 / 3.0", &ctx).unwrap(), json!(10.0 / 3.0));
|
assert_eq!(
|
||||||
|
eval_expression("10.0 / 3.0", &ctx).unwrap(),
|
||||||
|
json!(10.0 / 3.0)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -214,9 +217,18 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_string_comparison() {
|
fn test_string_comparison() {
|
||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
assert_eq!(eval_expression("\"abc\" == \"abc\"", &ctx).unwrap(), json!(true));
|
assert_eq!(
|
||||||
assert_eq!(eval_expression("\"abc\" < \"abd\"", &ctx).unwrap(), json!(true));
|
eval_expression("\"abc\" == \"abc\"", &ctx).unwrap(),
|
||||||
assert_eq!(eval_expression("\"abc\" > \"abb\"", &ctx).unwrap(), json!(true));
|
json!(true)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("\"abc\" < \"abd\"", &ctx).unwrap(),
|
||||||
|
json!(true)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("\"abc\" > \"abb\"", &ctx).unwrap(),
|
||||||
|
json!(true)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -225,7 +237,10 @@ mod tests {
|
|||||||
assert_eq!(eval_expression("null == null", &ctx).unwrap(), json!(true));
|
assert_eq!(eval_expression("null == null", &ctx).unwrap(), json!(true));
|
||||||
assert_eq!(eval_expression("null != null", &ctx).unwrap(), json!(false));
|
assert_eq!(eval_expression("null != null", &ctx).unwrap(), json!(false));
|
||||||
assert_eq!(eval_expression("null == 0", &ctx).unwrap(), json!(false));
|
assert_eq!(eval_expression("null == 0", &ctx).unwrap(), json!(false));
|
||||||
assert_eq!(eval_expression("null == false", &ctx).unwrap(), json!(false));
|
assert_eq!(
|
||||||
|
eval_expression("null == false", &ctx).unwrap(),
|
||||||
|
json!(false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -256,9 +271,15 @@ mod tests {
|
|||||||
fn test_boolean_operators() {
|
fn test_boolean_operators() {
|
||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
assert_eq!(eval_expression("true and true", &ctx).unwrap(), json!(true));
|
assert_eq!(eval_expression("true and true", &ctx).unwrap(), json!(true));
|
||||||
assert_eq!(eval_expression("true and false", &ctx).unwrap(), json!(false));
|
assert_eq!(
|
||||||
|
eval_expression("true and false", &ctx).unwrap(),
|
||||||
|
json!(false)
|
||||||
|
);
|
||||||
assert_eq!(eval_expression("false or true", &ctx).unwrap(), json!(true));
|
assert_eq!(eval_expression("false or true", &ctx).unwrap(), json!(true));
|
||||||
assert_eq!(eval_expression("false or false", &ctx).unwrap(), json!(false));
|
assert_eq!(
|
||||||
|
eval_expression("false or false", &ctx).unwrap(),
|
||||||
|
json!(false)
|
||||||
|
);
|
||||||
assert_eq!(eval_expression("not true", &ctx).unwrap(), json!(false));
|
assert_eq!(eval_expression("not true", &ctx).unwrap(), json!(false));
|
||||||
assert_eq!(eval_expression("not false", &ctx).unwrap(), json!(true));
|
assert_eq!(eval_expression("not false", &ctx).unwrap(), json!(true));
|
||||||
}
|
}
|
||||||
@@ -283,8 +304,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dot_access() {
|
fn test_dot_access() {
|
||||||
let ctx = TestContext::new()
|
let ctx = TestContext::new().with_var("obj", json!({"a": {"b": 42}}));
|
||||||
.with_var("obj", json!({"a": {"b": 42}}));
|
|
||||||
assert_eq!(eval_expression("obj.a.b", &ctx).unwrap(), json!(42));
|
assert_eq!(eval_expression("obj.a.b", &ctx).unwrap(), json!(42));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +314,10 @@ mod tests {
|
|||||||
.with_var("arr", json!([10, 20, 30]))
|
.with_var("arr", json!([10, 20, 30]))
|
||||||
.with_var("obj", json!({"key": "value"}));
|
.with_var("obj", json!({"key": "value"}));
|
||||||
assert_eq!(eval_expression("arr[1]", &ctx).unwrap(), json!(20));
|
assert_eq!(eval_expression("arr[1]", &ctx).unwrap(), json!(20));
|
||||||
assert_eq!(eval_expression("obj[\"key\"]", &ctx).unwrap(), json!("value"));
|
assert_eq!(
|
||||||
|
eval_expression("obj[\"key\"]", &ctx).unwrap(),
|
||||||
|
json!("value")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -304,9 +327,18 @@ mod tests {
|
|||||||
.with_var("obj", json!({"key": "val"}));
|
.with_var("obj", json!({"key": "val"}));
|
||||||
assert_eq!(eval_expression("2 in arr", &ctx).unwrap(), json!(true));
|
assert_eq!(eval_expression("2 in arr", &ctx).unwrap(), json!(true));
|
||||||
assert_eq!(eval_expression("5 in arr", &ctx).unwrap(), json!(false));
|
assert_eq!(eval_expression("5 in arr", &ctx).unwrap(), json!(false));
|
||||||
assert_eq!(eval_expression("\"key\" in obj", &ctx).unwrap(), json!(true));
|
assert_eq!(
|
||||||
assert_eq!(eval_expression("\"nope\" in obj", &ctx).unwrap(), json!(false));
|
eval_expression("\"key\" in obj", &ctx).unwrap(),
|
||||||
assert_eq!(eval_expression("\"ell\" in \"hello\"", &ctx).unwrap(), json!(true));
|
json!(true)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("\"nope\" in obj", &ctx).unwrap(),
|
||||||
|
json!(false)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("\"ell\" in \"hello\"", &ctx).unwrap(),
|
||||||
|
json!(true)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -319,7 +351,10 @@ mod tests {
|
|||||||
.with_var("arr", json!([1, 2, 3]))
|
.with_var("arr", json!([1, 2, 3]))
|
||||||
.with_var("obj", json!({"a": 1, "b": 2}));
|
.with_var("obj", json!({"a": 1, "b": 2}));
|
||||||
assert_eq!(eval_expression("length(arr)", &ctx).unwrap(), json!(3));
|
assert_eq!(eval_expression("length(arr)", &ctx).unwrap(), json!(3));
|
||||||
assert_eq!(eval_expression("length(\"hello\")", &ctx).unwrap(), json!(5));
|
assert_eq!(
|
||||||
|
eval_expression("length(\"hello\")", &ctx).unwrap(),
|
||||||
|
json!(5)
|
||||||
|
);
|
||||||
assert_eq!(eval_expression("length(obj)", &ctx).unwrap(), json!(2));
|
assert_eq!(eval_expression("length(obj)", &ctx).unwrap(), json!(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +362,10 @@ mod tests {
|
|||||||
fn test_type_conversions() {
|
fn test_type_conversions() {
|
||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
assert_eq!(eval_expression("string(42)", &ctx).unwrap(), json!("42"));
|
assert_eq!(eval_expression("string(42)", &ctx).unwrap(), json!("42"));
|
||||||
assert_eq!(eval_expression("number(\"3.14\")", &ctx).unwrap(), json!(3.14));
|
assert_eq!(
|
||||||
|
eval_expression("number(\"3.14\")", &ctx).unwrap(),
|
||||||
|
json!(3.14)
|
||||||
|
);
|
||||||
assert_eq!(eval_expression("int(3.9)", &ctx).unwrap(), json!(3));
|
assert_eq!(eval_expression("int(3.9)", &ctx).unwrap(), json!(3));
|
||||||
assert_eq!(eval_expression("int(\"42\")", &ctx).unwrap(), json!(42));
|
assert_eq!(eval_expression("int(\"42\")", &ctx).unwrap(), json!(42));
|
||||||
assert_eq!(eval_expression("bool(1)", &ctx).unwrap(), json!(true));
|
assert_eq!(eval_expression("bool(1)", &ctx).unwrap(), json!(true));
|
||||||
@@ -341,18 +379,35 @@ mod tests {
|
|||||||
let ctx = TestContext::new()
|
let ctx = TestContext::new()
|
||||||
.with_var("arr", json!([1]))
|
.with_var("arr", json!([1]))
|
||||||
.with_var("obj", json!({}));
|
.with_var("obj", json!({}));
|
||||||
assert_eq!(eval_expression("type_of(42)", &ctx).unwrap(), json!("number"));
|
assert_eq!(
|
||||||
assert_eq!(eval_expression("type_of(\"hi\")", &ctx).unwrap(), json!("string"));
|
eval_expression("type_of(42)", &ctx).unwrap(),
|
||||||
assert_eq!(eval_expression("type_of(true)", &ctx).unwrap(), json!("bool"));
|
json!("number")
|
||||||
assert_eq!(eval_expression("type_of(null)", &ctx).unwrap(), json!("null"));
|
);
|
||||||
assert_eq!(eval_expression("type_of(arr)", &ctx).unwrap(), json!("array"));
|
assert_eq!(
|
||||||
assert_eq!(eval_expression("type_of(obj)", &ctx).unwrap(), json!("object"));
|
eval_expression("type_of(\"hi\")", &ctx).unwrap(),
|
||||||
|
json!("string")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("type_of(true)", &ctx).unwrap(),
|
||||||
|
json!("bool")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("type_of(null)", &ctx).unwrap(),
|
||||||
|
json!("null")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("type_of(arr)", &ctx).unwrap(),
|
||||||
|
json!("array")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("type_of(obj)", &ctx).unwrap(),
|
||||||
|
json!("object")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_values() {
|
fn test_keys_values() {
|
||||||
let ctx = TestContext::new()
|
let ctx = TestContext::new().with_var("obj", json!({"b": 2, "a": 1}));
|
||||||
.with_var("obj", json!({"b": 2, "a": 1}));
|
|
||||||
let keys = eval_expression("sort(keys(obj))", &ctx).unwrap();
|
let keys = eval_expression("sort(keys(obj))", &ctx).unwrap();
|
||||||
assert_eq!(keys, json!(["a", "b"]));
|
assert_eq!(keys, json!(["a", "b"]));
|
||||||
let values = eval_expression("sort(values(obj))", &ctx).unwrap();
|
let values = eval_expression("sort(values(obj))", &ctx).unwrap();
|
||||||
@@ -368,15 +423,27 @@ mod tests {
|
|||||||
assert_eq!(eval_expression("round(3.5)", &ctx).unwrap(), json!(4));
|
assert_eq!(eval_expression("round(3.5)", &ctx).unwrap(), json!(4));
|
||||||
assert_eq!(eval_expression("min(3, 7)", &ctx).unwrap(), json!(3));
|
assert_eq!(eval_expression("min(3, 7)", &ctx).unwrap(), json!(3));
|
||||||
assert_eq!(eval_expression("max(3, 7)", &ctx).unwrap(), json!(7));
|
assert_eq!(eval_expression("max(3, 7)", &ctx).unwrap(), json!(7));
|
||||||
assert_eq!(eval_expression("sum([1, 2, 3, 4])", &ctx).unwrap(), json!(10));
|
assert_eq!(
|
||||||
|
eval_expression("sum([1, 2, 3, 4])", &ctx).unwrap(),
|
||||||
|
json!(10)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string_functions() {
|
fn test_string_functions() {
|
||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
assert_eq!(eval_expression("lower(\"HELLO\")", &ctx).unwrap(), json!("hello"));
|
assert_eq!(
|
||||||
assert_eq!(eval_expression("upper(\"hello\")", &ctx).unwrap(), json!("HELLO"));
|
eval_expression("lower(\"HELLO\")", &ctx).unwrap(),
|
||||||
assert_eq!(eval_expression("trim(\" hi \")", &ctx).unwrap(), json!("hi"));
|
json!("hello")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("upper(\"hello\")", &ctx).unwrap(),
|
||||||
|
json!("HELLO")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("trim(\" hi \")", &ctx).unwrap(),
|
||||||
|
json!("hi")
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
eval_expression("replace(\"hello world\", \"world\", \"rust\")", &ctx).unwrap(),
|
eval_expression("replace(\"hello world\", \"world\", \"rust\")", &ctx).unwrap(),
|
||||||
json!("hello rust")
|
json!("hello rust")
|
||||||
@@ -414,10 +481,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collection_functions() {
|
fn test_collection_functions() {
|
||||||
let ctx = TestContext::new()
|
let ctx = TestContext::new().with_var("arr", json!([3, 1, 2]));
|
||||||
.with_var("arr", json!([3, 1, 2]));
|
assert_eq!(
|
||||||
assert_eq!(eval_expression("sort(arr)", &ctx).unwrap(), json!([1, 2, 3]));
|
eval_expression("sort(arr)", &ctx).unwrap(),
|
||||||
assert_eq!(eval_expression("reversed(arr)", &ctx).unwrap(), json!([2, 1, 3]));
|
json!([1, 2, 3])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("reversed(arr)", &ctx).unwrap(),
|
||||||
|
json!([2, 1, 3])
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
eval_expression("unique([1, 2, 2, 3, 1])", &ctx).unwrap(),
|
eval_expression("unique([1, 2, 2, 3, 1])", &ctx).unwrap(),
|
||||||
json!([1, 2, 3])
|
json!([1, 2, 3])
|
||||||
@@ -435,14 +507,23 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_range() {
|
fn test_range() {
|
||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
assert_eq!(eval_expression("range(5)", &ctx).unwrap(), json!([0, 1, 2, 3, 4]));
|
assert_eq!(
|
||||||
assert_eq!(eval_expression("range(2, 5)", &ctx).unwrap(), json!([2, 3, 4]));
|
eval_expression("range(5)", &ctx).unwrap(),
|
||||||
|
json!([0, 1, 2, 3, 4])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_expression("range(2, 5)", &ctx).unwrap(),
|
||||||
|
json!([2, 3, 4])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reversed_string() {
|
fn test_reversed_string() {
|
||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
assert_eq!(eval_expression("reversed(\"abc\")", &ctx).unwrap(), json!("cba"));
|
assert_eq!(
|
||||||
|
eval_expression("reversed(\"abc\")", &ctx).unwrap(),
|
||||||
|
json!("cba")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -464,8 +545,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_complex_expression() {
|
fn test_complex_expression() {
|
||||||
let ctx = TestContext::new()
|
let ctx = TestContext::new().with_var("items", json!([1, 2, 3, 4, 5]));
|
||||||
.with_var("items", json!([1, 2, 3, 4, 5]));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
eval_expression("length(items) > 3 and 5 in items", &ctx).unwrap(),
|
eval_expression("length(items) > 3 and 5 in items", &ctx).unwrap(),
|
||||||
json!(true)
|
json!(true)
|
||||||
@@ -474,8 +554,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chained_access() {
|
fn test_chained_access() {
|
||||||
let ctx = TestContext::new()
|
let ctx = TestContext::new().with_var(
|
||||||
.with_var("data", json!({"users": [{"name": "Alice"}, {"name": "Bob"}]}));
|
"data",
|
||||||
|
json!({"users": [{"name": "Alice"}, {"name": "Bob"}]}),
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
eval_expression("data.users[1].name", &ctx).unwrap(),
|
eval_expression("data.users[1].name", &ctx).unwrap(),
|
||||||
json!("Bob")
|
json!("Bob")
|
||||||
@@ -484,8 +566,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ternary_via_boolean() {
|
fn test_ternary_via_boolean() {
|
||||||
let ctx = TestContext::new()
|
let ctx = TestContext::new().with_var("x", json!(10));
|
||||||
.with_var("x", json!(10));
|
|
||||||
// No ternary operator, but boolean expressions work for conditions
|
// No ternary operator, but boolean expressions work for conditions
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
eval_expression("x > 5 and x < 20", &ctx).unwrap(),
|
eval_expression("x > 5 and x < 20", &ctx).unwrap(),
|
||||||
|
|||||||
@@ -368,8 +368,8 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use super::super::tokenizer::Tokenizer;
|
use super::super::tokenizer::Tokenizer;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
fn parse(input: &str) -> Expr {
|
fn parse(input: &str) -> Expr {
|
||||||
let tokens = Tokenizer::new(input).tokenize().unwrap();
|
let tokens = Tokenizer::new(input).tokenize().unwrap();
|
||||||
|
|||||||
@@ -320,14 +320,14 @@ impl Tokenizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if is_float {
|
if is_float {
|
||||||
let val: f64 = num_str.parse().map_err(|_| {
|
let val: f64 = num_str
|
||||||
TokenError::InvalidNumber(start, num_str.clone())
|
.parse()
|
||||||
})?;
|
.map_err(|_| TokenError::InvalidNumber(start, num_str.clone()))?;
|
||||||
Ok(Token::new(TokenKind::Float(val), start, self.pos))
|
Ok(Token::new(TokenKind::Float(val), start, self.pos))
|
||||||
} else {
|
} else {
|
||||||
let val: i64 = num_str.parse().map_err(|_| {
|
let val: i64 = num_str
|
||||||
TokenError::InvalidNumber(start, num_str.clone())
|
.parse()
|
||||||
})?;
|
.map_err(|_| TokenError::InvalidNumber(start, num_str.clone()))?;
|
||||||
Ok(Token::new(TokenKind::Integer(val), start, self.pos))
|
Ok(Token::new(TokenKind::Integer(val), start, self.pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,11 +365,7 @@ mod tests {
|
|||||||
|
|
||||||
fn tokenize(input: &str) -> Vec<TokenKind> {
|
fn tokenize(input: &str) -> Vec<TokenKind> {
|
||||||
let mut t = Tokenizer::new(input);
|
let mut t = Tokenizer::new(input);
|
||||||
t.tokenize()
|
t.tokenize().unwrap().into_iter().map(|t| t.kind).collect()
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.kind)
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -307,9 +307,7 @@ impl WorkflowLoader {
|
|||||||
// Strip `.workflow` suffix if present:
|
// Strip `.workflow` suffix if present:
|
||||||
// "deploy.workflow.yaml" -> stem "deploy.workflow" -> name "deploy"
|
// "deploy.workflow.yaml" -> stem "deploy.workflow" -> name "deploy"
|
||||||
// "deploy.yaml" -> stem "deploy" -> name "deploy"
|
// "deploy.yaml" -> stem "deploy" -> name "deploy"
|
||||||
let name = raw_stem
|
let name = raw_stem.strip_suffix(".workflow").unwrap_or(raw_stem);
|
||||||
.strip_suffix(".workflow")
|
|
||||||
.unwrap_or(raw_stem);
|
|
||||||
|
|
||||||
let ref_name = format!("{}.{}", pack_name, name);
|
let ref_name = format!("{}.{}", pack_name, name);
|
||||||
workflow_files.push(WorkflowFile {
|
workflow_files.push(WorkflowFile {
|
||||||
|
|||||||
@@ -1501,7 +1501,10 @@ tasks:
|
|||||||
let failure_transition = &task.next[1];
|
let failure_transition = &task.next[1];
|
||||||
assert_eq!(failure_transition.publish.len(), 1);
|
assert_eq!(failure_transition.publish.len(), 1);
|
||||||
if let PublishDirective::Simple(map) = &failure_transition.publish[0] {
|
if let PublishDirective::Simple(map) = &failure_transition.publish[0] {
|
||||||
assert_eq!(map.get("validation_passed"), Some(&serde_json::Value::Bool(false)));
|
assert_eq!(
|
||||||
|
map.get("validation_passed"),
|
||||||
|
Some(&serde_json::Value::Bool(false))
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Simple publish directive");
|
panic!("Expected Simple publish directive");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ async fn test_create_execution_basic() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let execution = ExecutionRepository::create(&pool, input).await.unwrap();
|
let execution = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -76,7 +76,7 @@ async fn test_create_execution_without_action() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let execution = ExecutionRepository::create(&pool, input).await.unwrap();
|
let execution = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -109,7 +109,7 @@ async fn test_create_execution_with_all_fields() {
|
|||||||
executor: None, // Don't reference non-existent identity
|
executor: None, // Don't reference non-existent identity
|
||||||
status: ExecutionStatus::Scheduled,
|
status: ExecutionStatus::Scheduled,
|
||||||
result: Some(json!({"status": "ok"})),
|
result: Some(json!({"status": "ok"})),
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let execution = ExecutionRepository::create(&pool, input).await.unwrap();
|
let execution = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -144,7 +144,7 @@ async fn test_create_execution_with_parent() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Running,
|
status: ExecutionStatus::Running,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let parent = ExecutionRepository::create(&pool, parent_input)
|
let parent = ExecutionRepository::create(&pool, parent_input)
|
||||||
@@ -162,7 +162,7 @@ async fn test_create_execution_with_parent() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let child = ExecutionRepository::create(&pool, child_input)
|
let child = ExecutionRepository::create(&pool, child_input)
|
||||||
@@ -200,7 +200,7 @@ async fn test_find_execution_by_id() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -252,7 +252,7 @@ async fn test_list_executions() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
ExecutionRepository::create(&pool, input).await.unwrap();
|
ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -297,7 +297,7 @@ async fn test_list_executions_ordered_by_created_desc() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let exec = ExecutionRepository::create(&pool, input).await.unwrap();
|
let exec = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -347,7 +347,7 @@ async fn test_update_execution_status() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -391,7 +391,7 @@ async fn test_update_execution_result() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Running,
|
status: ExecutionStatus::Running,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -436,7 +436,7 @@ async fn test_update_execution_executor() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -479,7 +479,7 @@ async fn test_update_execution_status_transitions() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let exec = ExecutionRepository::create(&pool, input).await.unwrap();
|
let exec = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -569,7 +569,7 @@ async fn test_update_execution_failed_status() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Running,
|
status: ExecutionStatus::Running,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -613,7 +613,7 @@ async fn test_update_execution_no_changes() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -656,7 +656,7 @@ async fn test_delete_execution() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Completed,
|
status: ExecutionStatus::Completed,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -721,7 +721,7 @@ async fn test_find_executions_by_status() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: *status,
|
status: *status,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
ExecutionRepository::create(&pool, input).await.unwrap();
|
ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -767,7 +767,7 @@ async fn test_find_executions_by_enforcement() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
let _exec1 = ExecutionRepository::create(&pool, exec1_input)
|
let _exec1 = ExecutionRepository::create(&pool, exec1_input)
|
||||||
.await
|
.await
|
||||||
@@ -785,7 +785,7 @@ async fn test_find_executions_by_enforcement() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
ExecutionRepository::create(&pool, input).await.unwrap();
|
ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -828,7 +828,7 @@ async fn test_parent_child_execution_hierarchy() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Running,
|
status: ExecutionStatus::Running,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let parent = ExecutionRepository::create(&pool, parent_input)
|
let parent = ExecutionRepository::create(&pool, parent_input)
|
||||||
@@ -848,7 +848,7 @@ async fn test_parent_child_execution_hierarchy() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let child = ExecutionRepository::create(&pool, child_input)
|
let child = ExecutionRepository::create(&pool, child_input)
|
||||||
@@ -891,7 +891,7 @@ async fn test_nested_execution_hierarchy() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Running,
|
status: ExecutionStatus::Running,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let grandparent = ExecutionRepository::create(&pool, grandparent_input)
|
let grandparent = ExecutionRepository::create(&pool, grandparent_input)
|
||||||
@@ -909,7 +909,7 @@ async fn test_nested_execution_hierarchy() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Running,
|
status: ExecutionStatus::Running,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let parent = ExecutionRepository::create(&pool, parent_input)
|
let parent = ExecutionRepository::create(&pool, parent_input)
|
||||||
@@ -927,7 +927,7 @@ async fn test_nested_execution_hierarchy() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let child = ExecutionRepository::create(&pool, child_input)
|
let child = ExecutionRepository::create(&pool, child_input)
|
||||||
@@ -968,7 +968,7 @@ async fn test_execution_timestamps() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -1038,7 +1038,7 @@ async fn test_execution_config_json() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let execution = ExecutionRepository::create(&pool, input).await.unwrap();
|
let execution = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -1070,7 +1070,7 @@ async fn test_execution_result_json() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Running,
|
status: ExecutionStatus::Running,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
let created = ExecutionRepository::create(&pool, input).await.unwrap();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ async fn test_create_inquiry_minimal() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -109,7 +109,7 @@ async fn test_create_inquiry_with_response_schema() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -166,7 +166,7 @@ async fn test_create_inquiry_with_timeout() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -219,7 +219,7 @@ async fn test_create_inquiry_with_assigned_user() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -306,7 +306,7 @@ async fn test_find_inquiry_by_id() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -366,7 +366,7 @@ async fn test_get_inquiry_by_id() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -434,7 +434,7 @@ async fn test_list_inquiries() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -494,7 +494,7 @@ async fn test_update_inquiry_status() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -549,7 +549,7 @@ async fn test_update_inquiry_status_transitions() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -633,7 +633,7 @@ async fn test_update_inquiry_response() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -690,7 +690,7 @@ async fn test_update_inquiry_with_response_and_status() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -747,7 +747,7 @@ async fn test_update_inquiry_assignment() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -813,7 +813,7 @@ async fn test_update_inquiry_no_changes() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -888,7 +888,7 @@ async fn test_delete_inquiry() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -946,7 +946,7 @@ async fn test_delete_execution_cascades_to_inquiries() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1012,7 +1012,7 @@ async fn test_find_inquiries_by_status() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1090,7 +1090,7 @@ async fn test_find_inquiries_by_execution() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1108,7 +1108,7 @@ async fn test_find_inquiries_by_execution() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1171,7 +1171,7 @@ async fn test_inquiry_timestamps_auto_managed() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1237,7 +1237,7 @@ async fn test_inquiry_complex_response_schema() {
|
|||||||
executor: None,
|
executor: None,
|
||||||
status: attune_common::models::enums::ExecutionStatus::Requested,
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
workflow_task: None,
|
workflow_task: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ async fn test_list_rules() {
|
|||||||
action_params: json!({}),
|
action_params: json!({}),
|
||||||
trigger_params: json!({}),
|
trigger_params: json!({}),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
is_adhoc: false,
|
is_adhoc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
RuleRepository::create(&pool, input).await.unwrap();
|
RuleRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -535,7 +535,7 @@ async fn test_list_rules_ordered_by_ref() {
|
|||||||
action_params: json!({}),
|
action_params: json!({}),
|
||||||
trigger_params: json!({}),
|
trigger_params: json!({}),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
is_adhoc: false,
|
is_adhoc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
RuleRepository::create(&pool, input).await.unwrap();
|
RuleRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -983,7 +983,7 @@ async fn test_find_rules_by_pack() {
|
|||||||
action_params: json!({}),
|
action_params: json!({}),
|
||||||
trigger_params: json!({}),
|
trigger_params: json!({}),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
is_adhoc: false,
|
is_adhoc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
RuleRepository::create(&pool, input).await.unwrap();
|
RuleRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -1060,7 +1060,7 @@ async fn test_find_rules_by_action() {
|
|||||||
action_params: json!({}),
|
action_params: json!({}),
|
||||||
trigger_params: json!({}),
|
trigger_params: json!({}),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
is_adhoc: false,
|
is_adhoc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
RuleRepository::create(&pool, input).await.unwrap();
|
RuleRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -1141,7 +1141,7 @@ async fn test_find_rules_by_trigger() {
|
|||||||
action_params: json!({}),
|
action_params: json!({}),
|
||||||
trigger_params: json!({}),
|
trigger_params: json!({}),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
is_adhoc: false,
|
is_adhoc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
RuleRepository::create(&pool, input).await.unwrap();
|
RuleRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -1172,7 +1172,9 @@ async fn test_find_rules_by_trigger() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(trigger1_rules.len(), 2);
|
assert_eq!(trigger1_rules.len(), 2);
|
||||||
assert!(trigger1_rules.iter().all(|r| r.trigger == Some(trigger1.id)));
|
assert!(trigger1_rules
|
||||||
|
.iter()
|
||||||
|
.all(|r| r.trigger == Some(trigger1.id)));
|
||||||
|
|
||||||
let trigger2_rules = RuleRepository::find_by_trigger(&pool, trigger2.id)
|
let trigger2_rules = RuleRepository::find_by_trigger(&pool, trigger2.id)
|
||||||
.await
|
.await
|
||||||
@@ -1217,7 +1219,7 @@ async fn test_find_enabled_rules() {
|
|||||||
action_params: json!({}),
|
action_params: json!({}),
|
||||||
trigger_params: json!({}),
|
trigger_params: json!({}),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
is_adhoc: false,
|
is_adhoc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
RuleRepository::create(&pool, input).await.unwrap();
|
RuleRepository::create(&pool, input).await.unwrap();
|
||||||
@@ -1239,7 +1241,7 @@ async fn test_find_enabled_rules() {
|
|||||||
action_params: json!({}),
|
action_params: json!({}),
|
||||||
trigger_params: json!({}),
|
trigger_params: json!({}),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
is_adhoc: false,
|
is_adhoc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
RuleRepository::create(&pool, input).await.unwrap();
|
RuleRepository::create(&pool, input).await.unwrap();
|
||||||
|
|||||||
@@ -691,7 +691,10 @@ tasks:
|
|||||||
assert_eq!(transitions.len(), 1);
|
assert_eq!(transitions.len(), 1);
|
||||||
assert_eq!(transitions[0].publish.len(), 1);
|
assert_eq!(transitions[0].publish.len(), 1);
|
||||||
assert_eq!(transitions[0].publish[0].name, "msg");
|
assert_eq!(transitions[0].publish[0].name, "msg");
|
||||||
assert_eq!(transitions[0].publish[0].value, JsonValue::String("task1 done".to_string()));
|
assert_eq!(
|
||||||
|
transitions[0].publish[0].value,
|
||||||
|
JsonValue::String("task1 done".to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(runtime.can_execute(&context));
|
assert!(runtime.can_execute(&context));
|
||||||
@@ -210,7 +210,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!runtime.can_execute(&context));
|
assert!(!runtime.can_execute(&context));
|
||||||
|
|||||||
@@ -906,7 +906,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(runtime.can_execute(&context));
|
assert!(runtime.can_execute(&context));
|
||||||
@@ -941,7 +941,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(runtime.can_execute(&context));
|
assert!(runtime.can_execute(&context));
|
||||||
@@ -976,7 +976,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!runtime.can_execute(&context));
|
assert!(!runtime.can_execute(&context));
|
||||||
@@ -1067,7 +1067,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -1125,7 +1125,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -1164,7 +1164,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -1215,7 +1215,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -1324,7 +1324,7 @@ mod tests {
|
|||||||
parameter_delivery: ParameterDelivery::default(),
|
parameter_delivery: ParameterDelivery::default(),
|
||||||
parameter_format: ParameterFormat::default(),
|
parameter_format: ParameterFormat::default(),
|
||||||
output_format: OutputFormat::default(),
|
output_format: OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
|
|||||||
@@ -623,7 +623,7 @@ mod tests {
|
|||||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||||
output_format: attune_common::models::OutputFormat::default(),
|
output_format: attune_common::models::OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -660,7 +660,7 @@ mod tests {
|
|||||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||||
output_format: attune_common::models::OutputFormat::default(),
|
output_format: attune_common::models::OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -692,7 +692,7 @@ mod tests {
|
|||||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||||
output_format: attune_common::models::OutputFormat::default(),
|
output_format: attune_common::models::OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -726,7 +726,7 @@ mod tests {
|
|||||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||||
output_format: attune_common::models::OutputFormat::default(),
|
output_format: attune_common::models::OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -775,7 +775,7 @@ echo "missing=$missing"
|
|||||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||||
output_format: attune_common::models::OutputFormat::default(),
|
output_format: attune_common::models::OutputFormat::default(),
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -819,7 +819,7 @@ echo '{"id": 3, "name": "Charlie"}'
|
|||||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||||
output_format: attune_common::models::OutputFormat::Jsonl,
|
output_format: attune_common::models::OutputFormat::Jsonl,
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
@@ -942,7 +942,7 @@ echo '{"result": "success", "count": 42}'
|
|||||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||||
output_format: attune_common::models::OutputFormat::Json,
|
output_format: attune_common::models::OutputFormat::Json,
|
||||||
cancel_token: None,
|
cancel_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = runtime.execute(context).await.unwrap();
|
let result = runtime.execute(context).await.unwrap();
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ fn make_python_process_runtime(packs_base_dir: PathBuf) -> ProcessRuntime {
|
|||||||
dependencies: None,
|
dependencies: None,
|
||||||
env_vars: std::collections::HashMap::new(),
|
env_vars: std::collections::HashMap::new(),
|
||||||
};
|
};
|
||||||
ProcessRuntime::new("python".to_string(), config, packs_base_dir.clone(), packs_base_dir.join("../runtime_envs"))
|
ProcessRuntime::new(
|
||||||
|
"python".to_string(),
|
||||||
|
config,
|
||||||
|
packs_base_dir.clone(),
|
||||||
|
packs_base_dir.join("../runtime_envs"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_python_context(
|
fn make_python_context(
|
||||||
@@ -269,7 +274,12 @@ async fn test_shell_process_runtime_truncation() {
|
|||||||
dependencies: None,
|
dependencies: None,
|
||||||
env_vars: std::collections::HashMap::new(),
|
env_vars: std::collections::HashMap::new(),
|
||||||
};
|
};
|
||||||
let runtime = ProcessRuntime::new("shell".to_string(), config, tmp.path().to_path_buf(), tmp.path().join("runtime_envs"));
|
let runtime = ProcessRuntime::new(
|
||||||
|
"shell".to_string(),
|
||||||
|
config,
|
||||||
|
tmp.path().to_path_buf(),
|
||||||
|
tmp.path().join("runtime_envs"),
|
||||||
|
);
|
||||||
|
|
||||||
let context = ExecutionContext {
|
let context = ExecutionContext {
|
||||||
execution_id: 8,
|
execution_id: 8,
|
||||||
|
|||||||
@@ -22,8 +22,16 @@ fn make_python_process_runtime(packs_base_dir: PathBuf) -> ProcessRuntime {
|
|||||||
dependencies: None,
|
dependencies: None,
|
||||||
env_vars: std::collections::HashMap::new(),
|
env_vars: std::collections::HashMap::new(),
|
||||||
};
|
};
|
||||||
let runtime_envs_dir = packs_base_dir.parent().unwrap_or(&packs_base_dir).join("runtime_envs");
|
let runtime_envs_dir = packs_base_dir
|
||||||
ProcessRuntime::new("python".to_string(), config, packs_base_dir, runtime_envs_dir)
|
.parent()
|
||||||
|
.unwrap_or(&packs_base_dir)
|
||||||
|
.join("runtime_envs");
|
||||||
|
ProcessRuntime::new(
|
||||||
|
"python".to_string(),
|
||||||
|
config,
|
||||||
|
packs_base_dir,
|
||||||
|
runtime_envs_dir,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -436,7 +444,12 @@ echo "PASS: No secrets in environment"
|
|||||||
dependencies: None,
|
dependencies: None,
|
||||||
env_vars: std::collections::HashMap::new(),
|
env_vars: std::collections::HashMap::new(),
|
||||||
};
|
};
|
||||||
let runtime = ProcessRuntime::new("shell".to_string(), config, tmp.path().to_path_buf(), tmp.path().join("runtime_envs"));
|
let runtime = ProcessRuntime::new(
|
||||||
|
"shell".to_string(),
|
||||||
|
config,
|
||||||
|
tmp.path().to_path_buf(),
|
||||||
|
tmp.path().join("runtime_envs"),
|
||||||
|
);
|
||||||
|
|
||||||
let context = ExecutionContext {
|
let context = ExecutionContext {
|
||||||
execution_id: 7,
|
execution_id: 7,
|
||||||
@@ -508,7 +521,12 @@ print(json.dumps({"leaked": leaked}))
|
|||||||
dependencies: None,
|
dependencies: None,
|
||||||
env_vars: std::collections::HashMap::new(),
|
env_vars: std::collections::HashMap::new(),
|
||||||
};
|
};
|
||||||
let runtime = ProcessRuntime::new("python".to_string(), config, tmp.path().to_path_buf(), tmp.path().join("runtime_envs"));
|
let runtime = ProcessRuntime::new(
|
||||||
|
"python".to_string(),
|
||||||
|
config,
|
||||||
|
tmp.path().to_path_buf(),
|
||||||
|
tmp.path().join("runtime_envs"),
|
||||||
|
);
|
||||||
|
|
||||||
let context = ExecutionContext {
|
let context = ExecutionContext {
|
||||||
execution_id: 8,
|
execution_id: 8,
|
||||||
|
|||||||
4733
web/package-lock.json
generated
Normal file
4733
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user