#![allow(unsafe_code)] #![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_panics_doc)]
pub mod cache;
pub mod error;
pub mod retry;
pub mod validation;
pub use error::{CueEngineError, Result};
pub use retry::RetryConfig;
pub use validation::Limits;
use error::CueEngineError as Error;
use serde::{Deserialize, Serialize};
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::os::raw::c_char;
use std::path::Path;
const ERROR_CODE_INVALID_INPUT: &str = "INVALID_INPUT";
const ERROR_CODE_LOAD_INSTANCE: &str = "LOAD_INSTANCE";
const ERROR_CODE_BUILD_VALUE: &str = "BUILD_VALUE";
const ERROR_CODE_ORDERED_JSON: &str = "ORDERED_JSON";
const ERROR_CODE_PANIC_RECOVER: &str = "PANIC_RECOVER";
const ERROR_CODE_JSON_MARSHAL: &str = "JSON_MARSHAL_ERROR";
const ERROR_CODE_REGISTRY_INIT: &str = "REGISTRY_INIT";
const ERROR_CODE_DEPENDENCY_RES: &str = "DEPENDENCY_RESOLUTION";
#[derive(Debug, Deserialize, Serialize)]
struct BridgeError {
code: String,
message: String,
hint: Option<String>,
}
#[derive(Debug, Deserialize)]
struct BridgeEnvelope<'a> {
#[allow(dead_code)] version: String,
#[serde(borrow)]
ok: Option<&'a serde_json::value::RawValue>,
error: Option<BridgeError>,
}
pub struct CStringPtr {
ptr: *mut c_char,
_marker: PhantomData<*const ()>,
}
impl CStringPtr {
pub const unsafe fn new(ptr: *mut c_char) -> Self {
Self {
ptr,
_marker: PhantomData,
}
}
#[must_use]
pub const fn is_null(&self) -> bool {
self.ptr.is_null()
}
pub unsafe fn to_str(&self) -> Result<&str> {
debug_assert!(
!self.is_null(),
"Attempted to convert null pointer to string"
);
let cstr = unsafe { CStr::from_ptr(self.ptr) };
cstr.to_str().map_err(|e| {
Error::ffi(
"cue_eval_module",
format!("failed to convert C string to UTF-8: {e}"),
)
})
}
}
impl Drop for CStringPtr {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
cue_free_string(self.ptr);
}
}
}
}
#[cfg(not(docsrs))]
#[link(name = "cue_bridge")]
unsafe extern "C" {
fn cue_eval_module(
module_root: *const c_char,
package_name: *const c_char,
options_json: *const c_char,
) -> *mut c_char;
fn cue_free_string(s: *mut c_char);
fn cue_bridge_version() -> *mut c_char;
}
#[cfg(docsrs)]
unsafe fn cue_eval_module(_: *const c_char, _: *const c_char, _: *const c_char) -> *mut c_char {
panic!("FFI not available in documentation builds")
}
#[cfg(docsrs)]
unsafe fn cue_free_string(_: *mut c_char) {}
#[cfg(docsrs)]
unsafe fn cue_bridge_version() -> *mut c_char {
panic!("FFI not available in documentation builds")
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleEvalOptions {
pub with_meta: bool,
pub with_references: bool,
pub recursive: bool,
pub package_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_dir: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldMeta {
#[serde(default)]
pub directory: String,
#[serde(default)]
pub filename: String,
#[serde(default)]
pub line: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct ModuleResult {
pub instances: std::collections::HashMap<String, serde_json::Value>,
#[serde(default)]
pub projects: Vec<String>,
#[serde(default)]
pub meta: std::collections::HashMap<String, FieldMeta>,
}
#[tracing::instrument(
name = "evaluate_module",
fields(
module_root = %module_root.display(),
package_name = package_name,
operation_id = %uuid::Uuid::new_v4(),
),
level = "info",
skip(options)
)]
#[allow(clippy::cognitive_complexity)] pub fn evaluate_module(
module_root: &Path,
package_name: &str,
options: Option<&ModuleEvalOptions>,
) -> Result<ModuleResult> {
tracing::info!("Starting module-wide CUE evaluation");
let start_time = std::time::Instant::now();
let c_module_root = path_to_cstring(module_root, "cue_eval_module", "module root")?;
let c_package = str_to_cstring(package_name, "cue_eval_module", "package name")?;
let c_options = options_to_cstring(options)?;
let json_str = call_ffi_eval_module(&c_module_root, &c_package, &c_options)?;
let envelope = parse_bridge_envelope(&json_str)?;
let module_result = process_bridge_response(envelope, module_root)?;
let total_duration = start_time.elapsed();
tracing::info!(
total_duration_ms = total_duration.as_millis(),
instance_count = module_result.instances.len(),
"Module evaluation completed successfully"
);
Ok(module_result)
}
fn path_to_cstring(path: &Path, fn_name: &'static str, param_name: &str) -> Result<CString> {
let Some(path_str) = path.to_str() else {
tracing::error!("{} path is not valid UTF-8: {:?}", param_name, path);
return Err(Error::configuration(format!(
"Invalid {param_name} path: not UTF-8"
)));
};
str_to_cstring(path_str, fn_name, param_name)
}
fn str_to_cstring(s: &str, fn_name: &'static str, param_name: &str) -> Result<CString> {
CString::new(s).map_err(|e| {
tracing::error!("Failed to convert {} to C string: {}", param_name, e);
Error::ffi(fn_name, format!("Invalid {param_name}: {e}"))
})
}
fn options_to_cstring(options: Option<&ModuleEvalOptions>) -> Result<CString> {
let options_json = options.map_or_else(
|| "{}".to_string(),
|o| serde_json::to_string(o).unwrap_or_else(|_| "{}".to_string()),
);
str_to_cstring(&options_json, "cue_eval_module", "options")
}
#[allow(clippy::cognitive_complexity)] fn call_ffi_eval_module(
c_module_root: &CString,
c_package: &CString,
c_options: &CString,
) -> Result<String> {
tracing::debug!("Calling FFI function cue_eval_module");
let ffi_start = std::time::Instant::now();
let result_ptr = unsafe {
cue_eval_module(
c_module_root.as_ptr(),
c_package.as_ptr(),
c_options.as_ptr(),
)
};
let ffi_duration = ffi_start.elapsed();
tracing::debug!(
ffi_duration_ms = ffi_duration.as_millis(),
"FFI call completed"
);
let result = unsafe { CStringPtr::new(result_ptr) };
if result.is_null() {
tracing::error!("FFI function returned null pointer");
return Err(Error::ffi(
"cue_eval_module",
"Module evaluation returned null".to_string(),
));
}
unsafe { result.to_str() }.map(String::from)
}
fn parse_bridge_envelope(json_str: &str) -> Result<BridgeEnvelope<'_>> {
serde_json::from_str(json_str).map_err(|e| {
tracing::error!(
json_response = json_str,
parse_error = %e,
"Failed to parse JSON envelope from Go bridge"
);
Error::ffi(
"cue_eval_module",
format!("Invalid JSON envelope from Go bridge: {e}"),
)
})
}
fn process_bridge_response(envelope: BridgeEnvelope, module_root: &Path) -> Result<ModuleResult> {
if let Some(bridge_error) = envelope.error {
return Err(handle_bridge_error(bridge_error, module_root));
}
let json_data = envelope
.ok
.map(|raw| raw.get().to_string())
.ok_or_else(|| {
tracing::error!("Bridge envelope has neither 'ok' nor 'error' field");
Error::ffi(
"cue_eval_module",
"Invalid bridge response: missing both 'ok' and 'error' fields".to_string(),
)
})?;
parse_module_result(&json_data)
}
fn handle_bridge_error(bridge_error: BridgeError, module_root: &Path) -> Error {
tracing::error!(
error_code = bridge_error.code,
error_message = bridge_error.message,
error_hint = bridge_error.hint,
"Module evaluation failed"
);
let full_message = bridge_error
.hint
.map(|hint| format!("{} (Hint: {})", bridge_error.message, hint))
.unwrap_or(bridge_error.message);
match bridge_error.code.as_str() {
ERROR_CODE_INVALID_INPUT | ERROR_CODE_REGISTRY_INIT => Error::configuration(full_message),
ERROR_CODE_LOAD_INSTANCE | ERROR_CODE_BUILD_VALUE | ERROR_CODE_DEPENDENCY_RES => {
Error::cue_parse(module_root, full_message)
}
ERROR_CODE_ORDERED_JSON | ERROR_CODE_PANIC_RECOVER | ERROR_CODE_JSON_MARSHAL => {
Error::ffi("cue_eval_module", full_message)
}
_ => Error::ffi("cue_eval_module", full_message),
}
}
fn parse_module_result(json_data: &str) -> Result<ModuleResult> {
serde_json::from_str(json_data).map_err(|e| {
tracing::error!(
json_data = json_data,
parse_error = %e,
"Failed to parse module result"
);
Error::ffi(
"cue_eval_module",
format!("Failed to parse module result: {e}"),
)
})
}
#[allow(clippy::needless_pass_by_value)] fn extract_ffi_string(wrapper: CStringPtr, fn_name: &'static str) -> Result<String> {
if wrapper.is_null() {
tracing::error!("{} returned null pointer", fn_name);
return Err(Error::ffi(fn_name, format!("{fn_name} returned null")));
}
unsafe { wrapper.to_str() }.map(String::from)
}
pub fn get_bridge_version() -> Result<String> {
tracing::debug!("Getting bridge version information");
let version_ptr = unsafe { cue_bridge_version() };
let version_wrapper = unsafe { CStringPtr::new(version_ptr) };
let bridge_version = extract_ffi_string(version_wrapper, "cue_bridge_version")?;
tracing::info!(bridge_version = bridge_version, "Retrieved bridge version");
Ok(bridge_version)
}
#[tracing::instrument(
name = "evaluate_cue_package",
fields(
dir_path = %dir_path.display(),
package_name = package_name,
),
level = "info"
)]
pub fn evaluate_cue_package(dir_path: &Path, package_name: &str) -> Result<String> {
let options = ModuleEvalOptions {
with_meta: false,
with_references: false,
recursive: false,
package_name: None,
target_dir: None, };
let result = evaluate_module(dir_path, package_name, Some(&options))?;
let instance = result
.instances
.get(".")
.or_else(|| result.instances.values().next())
.ok_or_else(|| {
Error::configuration(format!(
"No CUE instance found in directory: {}",
dir_path.display()
))
})?;
Ok(instance.to_string())
}
#[tracing::instrument(
name = "evaluate_cue_package_typed",
fields(
dir_path = %dir_path.display(),
package_name = package_name,
target_type = std::any::type_name::<T>(),
),
level = "info"
)]
#[allow(clippy::cognitive_complexity)] pub fn evaluate_cue_package_typed<T>(dir_path: &Path, package_name: &str) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
tracing::debug!("Evaluating CUE package with typed deserialization");
let json_str = evaluate_cue_package(dir_path, package_name)?;
serde_json::from_str(&json_str).map_err(|e| {
tracing::error!(
"Failed to deserialize CUE output to {}: {}",
std::any::type_name::<T>(),
e
);
Error::configuration(format!(
"Failed to parse CUE output as {}: {}",
std::any::type_name::<T>(),
e
))
})
}
#[cfg(test)]
#[allow(clippy::print_stdout)]
mod tests {
use super::*;
use std::ffi::CString;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_cstring_ptr_creation() {
let null_ptr = unsafe { CStringPtr::new(std::ptr::null_mut()) };
assert!(null_ptr.is_null());
let test_string = CString::new("test").unwrap();
let ptr = test_string.into_raw();
let wrapper = unsafe { CStringPtr::new(ptr) };
assert!(!wrapper.is_null());
let result_str = unsafe { wrapper.to_str().unwrap() };
assert_eq!(result_str, "test");
}
#[test]
fn test_cstring_ptr_utf8_conversion() {
let test_content = "Hello, 世界! 🦀";
let c_string = CString::new(test_content).unwrap();
let ptr = c_string.into_raw();
let wrapper = unsafe { CStringPtr::new(ptr) };
let converted = unsafe { wrapper.to_str().unwrap() };
assert_eq!(converted, test_content);
}
#[test]
fn test_cstring_ptr_empty_string() {
let empty_string = CString::new("").unwrap();
let ptr = empty_string.into_raw();
let wrapper = unsafe { CStringPtr::new(ptr) };
assert!(!wrapper.is_null());
let result = unsafe { wrapper.to_str().unwrap() };
assert_eq!(result, "");
}
#[test]
fn test_cstring_ptr_null_to_str_panics_debug() {
let null_wrapper = unsafe { CStringPtr::new(std::ptr::null_mut()) };
assert!(null_wrapper.is_null());
if cfg!(debug_assertions) {
std::panic::catch_unwind(|| {
let _ = unsafe { null_wrapper.to_str() };
})
.expect_err("Expected panic in debug mode for null pointer");
} else {
tracing::info!(
"Skipping null pointer dereference test in release mode (undefined behavior)"
);
}
}
#[test]
fn test_evaluate_cue_package_invalid_path() {
let invalid_path = Path::new("/nonexistent/\u{0000}/invalid");
let result = evaluate_cue_package(invalid_path, "test");
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("FFI operation failed"));
}
#[test]
fn test_evaluate_cue_package_invalid_package_name() {
let temp_dir = TempDir::new().unwrap();
let result = evaluate_cue_package(temp_dir.path(), "test\0package");
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("FFI operation failed"));
}
#[test]
fn test_evaluate_cue_package_nonexistent_directory() {
let nonexistent = Path::new("/definitely/does/not/exist/12345");
let result = evaluate_cue_package(nonexistent, "env");
match result {
Ok(json) => {
tracing::info!("FFI succeeded for nonexistent path (CI behavior): {json}");
}
Err(error) => {
tracing::info!("Got expected error for nonexistent path: {error}");
assert!(!error.to_string().is_empty());
}
}
}
#[test]
fn test_evaluate_cue_package_with_valid_setup() {
let temp_dir = TempDir::new().unwrap();
let cue_content = r#"package cuenv
env: {
TEST_VAR: "test_value"
NUMBER: 42
}
"#;
fs::write(temp_dir.path().join("env.cue"), cue_content).unwrap();
let result = evaluate_cue_package(temp_dir.path(), "cuenv");
match result {
Err(error) => {
tracing::info!("FFI not available in test environment: {error}");
}
Ok(json) => {
println!("Got JSON response: {json}");
assert!(
json.contains("env"),
"JSON should contain env field. Got: {json}"
);
assert!(
json.contains("TEST_VAR") || json.contains("test_value"),
"JSON should contain test values. Got: {json}"
);
}
}
}
#[test]
fn test_evaluate_cue_error_handling() {
let temp_dir = TempDir::new().unwrap();
let invalid_cue = r"package cuenv
this is not valid CUE syntax {
missing quotes and wrong structure
";
fs::write(temp_dir.path().join("env.cue"), invalid_cue).unwrap();
let result = evaluate_cue_package(temp_dir.path(), "cuenv");
match result {
Ok(json) => {
tracing::info!("FFI succeeded with invalid CUE (CI behavior): {json}");
}
Err(error) => {
tracing::info!("Got expected error for invalid CUE: {error}");
assert!(!error.to_string().is_empty());
}
}
}
#[test]
fn test_path_conversion_edge_cases() {
let temp_dir = TempDir::new().unwrap();
let path_with_spaces = temp_dir.path().join("dir with spaces");
fs::create_dir(&path_with_spaces).unwrap();
let result = evaluate_cue_package(&path_with_spaces, "env");
if let Err(e) = result {
assert!(!e.to_string().contains("Invalid directory path: not UTF-8"));
}
}
#[test]
fn test_ffi_memory_management_stress() {
let temp_dir = TempDir::new().unwrap();
let cue_content = r#"package cuenv
env: {
TEST: "value"
}"#;
fs::write(temp_dir.path().join("env.cue"), cue_content).unwrap();
for i in 0..100 {
let result = evaluate_cue_package(temp_dir.path(), "cuenv");
match result {
Ok(json) => {
assert!(
json.contains("env"),
"JSON should contain env field. Got: {json}"
);
}
Err(error) => {
let error_msg = error.to_string();
tracing::info!("Iteration {i}: {error_msg}");
if i > 5 {
break;
}
}
}
}
}
#[test]
fn test_get_bridge_version() {
let result = get_bridge_version();
match result {
Ok(version) => {
tracing::info!("Bridge version: {}", version);
assert!(!version.is_empty());
assert!(version.len() > 3); }
Err(error) => {
tracing::info!("FFI not available for bridge version: {}", error);
let error_msg = error.to_string();
assert!(!error_msg.is_empty());
assert!(error_msg.contains("cue_bridge_version") || error_msg.contains("FFI"));
}
}
}
#[test]
fn test_error_message_parsing() {
let temp_dir = TempDir::new().unwrap();
let result = evaluate_cue_package(temp_dir.path(), "nonexistent_package");
match result {
Ok(output) => {
tracing::info!("FFI returned success (possibly unavailable): {output}");
}
Err(error) => {
let error_str = error.to_string();
assert!(!error_str.is_empty());
assert!(error_str.len() > 5); tracing::info!("Got expected error: {error_str}");
}
}
}
#[test]
fn test_bridge_error_constants_consistency() {
assert_eq!(ERROR_CODE_INVALID_INPUT, "INVALID_INPUT");
assert_eq!(ERROR_CODE_LOAD_INSTANCE, "LOAD_INSTANCE");
assert_eq!(ERROR_CODE_BUILD_VALUE, "BUILD_VALUE");
assert_eq!(ERROR_CODE_ORDERED_JSON, "ORDERED_JSON");
assert_eq!(ERROR_CODE_PANIC_RECOVER, "PANIC_RECOVER");
assert_eq!(ERROR_CODE_JSON_MARSHAL, "JSON_MARSHAL_ERROR");
assert_eq!(ERROR_CODE_REGISTRY_INIT, "REGISTRY_INIT");
assert_eq!(ERROR_CODE_DEPENDENCY_RES, "DEPENDENCY_RESOLUTION");
}
#[test]
fn test_bridge_envelope_parsing() {
let success_json = r#"{"version":"bridge/1","ok":{"test":"value"}}"#;
let envelope: BridgeEnvelope = serde_json::from_str(success_json).unwrap();
assert_eq!(envelope.version, "bridge/1");
assert!(envelope.ok.is_some());
assert!(envelope.error.is_none());
let error_json = r#"{"version":"bridge/1","error":{"code":"INVALID_INPUT","message":"test error","hint":"test hint"}}"#;
let envelope: BridgeEnvelope = serde_json::from_str(error_json).unwrap();
assert_eq!(envelope.version, "bridge/1");
assert!(envelope.ok.is_none());
assert!(envelope.error.is_some());
let error = envelope.error.unwrap();
assert_eq!(error.code, "INVALID_INPUT");
assert_eq!(error.message, "test error");
assert_eq!(error.hint, Some("test hint".to_string()));
}
#[test]
fn test_bridge_envelope_parsing_minimal_error() {
let error_json =
r#"{"version":"bridge/1","error":{"code":"LOAD_INSTANCE","message":"test error"}}"#;
let envelope: BridgeEnvelope = serde_json::from_str(error_json).unwrap();
let error = envelope.error.unwrap();
assert_eq!(error.code, "LOAD_INSTANCE");
assert_eq!(error.message, "test error");
assert!(error.hint.is_none());
}
#[test]
fn test_cstring_ptr_drop_behavior() {
let null_ptr = unsafe { CStringPtr::new(std::ptr::null_mut()) };
drop(null_ptr);
let test_string = CString::new("test").unwrap();
let ptr = test_string.into_raw();
let wrapper = unsafe { CStringPtr::new(ptr) };
drop(wrapper); }
#[test]
fn test_get_bridge_version_functionality() {
let result = get_bridge_version();
match result {
Ok(version) => {
tracing::info!("Bridge available with version: {}", version);
assert!(!version.is_empty());
assert!(
version.to_lowercase().contains("bridge"),
"Version should contain 'bridge': {version}"
);
assert!(
version.contains("go") || version.contains("Go"),
"Version should contain Go info: {version}"
);
}
Err(error) => {
let error_str = error.to_string();
assert!(!error_str.is_empty());
assert!(
error_str.contains("FFI") || error_str.contains("cue_bridge_version"),
"Error should mention FFI or function name: {error_str}"
);
tracing::info!("Bridge not available (expected in test env): {}", error_str);
}
}
}
#[test]
fn test_error_code_mapping() {
let test_cases = vec![
(ERROR_CODE_INVALID_INPUT, "Invalid input test", None),
(
ERROR_CODE_LOAD_INSTANCE,
"Load instance test",
Some("Check CUE files".to_string()),
),
(
ERROR_CODE_BUILD_VALUE,
"Build value test",
Some("Check constraints".to_string()),
),
(ERROR_CODE_ORDERED_JSON, "JSON test", None),
(ERROR_CODE_PANIC_RECOVER, "Panic test", None),
(ERROR_CODE_JSON_MARSHAL, "Marshal test", None),
(
ERROR_CODE_REGISTRY_INIT,
"Registry init test",
Some("Check CUE_REGISTRY".to_string()),
),
(
ERROR_CODE_DEPENDENCY_RES,
"Dependency resolution test",
Some("Run 'cue mod tidy'".to_string()),
),
("UNKNOWN_CODE", "Unknown error", None),
];
for (code, message, hint) in test_cases {
let bridge_error = BridgeError {
code: code.to_string(),
message: message.to_string(),
hint,
};
let serialized = serde_json::to_string(&bridge_error).unwrap();
let deserialized: BridgeError = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.code, code);
assert_eq!(deserialized.message, message);
}
}
#[test]
fn test_path_edge_cases() {
let temp_dir = TempDir::new().unwrap();
let result = evaluate_cue_package(temp_dir.path(), "");
match result {
Ok(_) => {
tracing::info!("FFI not available or handles empty package name gracefully");
}
Err(error) => {
let error_str = error.to_string();
assert!(!error_str.is_empty());
tracing::info!("Got expected error for empty package name: {}", error_str);
}
}
}
#[test]
fn test_json_envelope_version_mismatch() {
let incompatible_version_json = r#"{"version":"bridge/2","ok":{"test":"value"}}"#;
let envelope: BridgeEnvelope = serde_json::from_str(incompatible_version_json).unwrap();
assert_eq!(envelope.version, "bridge/2");
assert!(!envelope.version.starts_with("bridge/1"));
}
#[test]
fn test_serialize_import_usage() {
use serde::Serialize;
#[derive(Serialize)]
struct TestStruct {
field: String,
}
let test = TestStruct {
field: "test".to_string(),
};
let _json = serde_json::to_string(&test).unwrap();
}
}