cuengine 0.26.19

Go-Rust FFI bridge for CUE evaluation with production-ready features
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
//! Go-Rust FFI bridge for CUE evaluation
//!
//! This crate provides a safe Rust interface to the Go-based CUE evaluator.
//! It handles all FFI operations, memory management, and error handling for
//! calling Go functions from Rust.

#![allow(unsafe_code)] // Required for FFI with Go
#![allow(clippy::missing_safety_doc)] // Safety is documented inline
#![allow(clippy::missing_panics_doc)] // Panics are documented where relevant

pub mod cache;
pub mod error;
pub mod retry;
pub mod validation;

// Re-export main types
pub use error::{CueEngineError, Result};
pub use retry::RetryConfig;
pub use validation::Limits;

// Local type alias for internal use
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;

// Bridge error codes - keep in sync with Go side constants
// These match the constants defined in bridge.go
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";

/// Error response from the Go bridge
#[derive(Debug, Deserialize, Serialize)]
struct BridgeError {
    code: String,
    message: String,
    hint: Option<String>,
}

/// Structured response envelope from the Go bridge
#[derive(Debug, Deserialize)]
struct BridgeEnvelope<'a> {
    #[allow(dead_code)] // Deserialized from Go FFI bridge; used in tests
    version: String,
    #[serde(borrow)]
    ok: Option<&'a serde_json::value::RawValue>,
    error: Option<BridgeError>,
}

/// RAII wrapper for C strings returned from FFI
/// Ensures proper cleanup when the wrapper goes out of scope
///
/// This type is intentionally !Send and !Sync because the underlying
/// C pointer comes from Go's runtime which is not thread-safe.
pub struct CStringPtr {
    ptr: *mut c_char,
    // Marker to make this type !Send + !Sync
    _marker: PhantomData<*const ()>,
}

impl CStringPtr {
    /// Creates a new wrapper from a raw pointer
    ///
    /// # Safety
    ///
    /// The caller must ensure that:
    /// - `ptr` is either null or a valid pointer returned from a CUE FFI function
    /// - The pointer has not been freed already
    /// - The pointer will not be used after this wrapper is dropped
    /// - No other thread is accessing this pointer
    /// - The memory pointed to by `ptr` will remain valid for the lifetime of this wrapper
    ///
    /// # FFI Contract
    ///
    /// This function expects that the Go side:
    /// - Returns either null or a valid C string pointer
    /// - Allocates memory that must be freed with `cue_free_string`
    /// - Does not modify the memory after returning the pointer
    pub const unsafe fn new(ptr: *mut c_char) -> Self {
        Self {
            ptr,
            _marker: PhantomData,
        }
    }

    /// Checks if the wrapped pointer is null
    #[must_use]
    pub const fn is_null(&self) -> bool {
        self.ptr.is_null()
    }

    /// Converts the C string to a Rust &str
    ///
    /// # Safety
    ///
    /// This function is safe to call when:
    /// - The wrapped pointer is not null (checked with `debug_assert`)
    /// - The pointer points to a valid null-terminated C string
    /// - The pointed-to memory contains valid UTF-8 data
    /// - The memory will not be modified during the lifetime of the returned &str
    ///
    /// # Errors
    ///
    /// Returns an error if the C string contains invalid UTF-8
    ///
    /// # Panics
    ///
    /// In debug builds, panics if the pointer is null
    pub unsafe fn to_str(&self) -> Result<&str> {
        debug_assert!(
            !self.is_null(),
            "Attempted to convert null pointer to string"
        );

        // SAFETY: We've verified the pointer is not null via debug_assert
        // The caller must ensure the pointer points to a valid C 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() {
            // Safety: cue_free_string is safe to call because:
            // - self.ptr is not null (checked above)
            // - self.ptr was allocated by the Go side via cue_eval_package
            // - We have exclusive ownership of this pointer (enforced by Rust's ownership)
            // - This pointer has not been freed already (enforced by Drop only running once)
            // - After this call, the pointer becomes invalid and won't be used again
            unsafe {
                cue_free_string(self.ptr);
            }
        }
    }
}

// SAFETY: CStringPtr contains a raw pointer to memory managed by Go's garbage collector.
// The PhantomData<*const ()> marker makes this type !Send and !Sync because:
// 1. The Go runtime may have thread-local state associated with this memory
// 2. The FFI contract doesn't guarantee thread-safety of the underlying memory
// 3. Concurrent access to cue_free_string from multiple threads is undefined behavior
// 4. Raw pointers are inherently not Send/Sync, so PhantomData<*const ()> prevents both

// Real FFI for normal builds
#[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;
}

// Stub FFI for documentation builds - these satisfy the compiler but panic if called
#[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")
}

/// Options for module evaluation
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleEvalOptions {
    /// Extract source positions into separate `meta` map
    pub with_meta: bool,
    /// Extract reference paths for values that are CUE references (e.g., `dependsOn: [build]`)
    pub with_references: bool,
    /// true: cue eval ./... (recursive), false: cue eval . (current directory)
    pub recursive: bool,
    /// Filter to specific package name, None = all packages
    pub package_name: Option<String>,
    /// Directory to evaluate (for non-recursive), None = module root.
    /// Use this to evaluate a specific subdirectory without loading the entire module.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target_dir: Option<String>,
}

/// Source location metadata for a single field
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldMeta {
    /// Directory containing the file (relative to module root)
    #[serde(default)]
    pub directory: String,
    /// Filename where the field is defined
    #[serde(default)]
    pub filename: String,
    /// Line number in the file
    #[serde(default)]
    pub line: usize,
    /// If this value is a CUE reference, the path it refers to (e.g., "tasks.build")
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reference: Option<String>,
}

/// Result of evaluating an entire CUE module
#[derive(Debug, Deserialize)]
pub struct ModuleResult {
    /// Map of relative path to evaluated JSON value
    pub instances: std::collections::HashMap<String, serde_json::Value>,
    /// Paths that conform to schema.#Project (verified via CUE unification)
    #[serde(default)]
    pub projects: Vec<String>,
    /// Map of "path/field" to source location (only populated when `with_meta`: true)
    #[serde(default)]
    pub meta: std::collections::HashMap<String, FieldMeta>,
}

/// Evaluates CUE instances in a module and returns results with optional source metadata
///
/// This function evaluates CUE files in a module using native CUE loading patterns:
/// - `recursive: true` → equivalent to `cue eval ./...`
/// - `recursive: false` → equivalent to `cue eval .`
///
/// # Arguments
/// * `module_root` - Path to the CUE module root (directory containing cue.mod/)
/// * `package_name` - Name of the CUE package to evaluate (legacy parameter, prefer using `options.package_name`)
/// * `options` - Evaluation options:
///   - `with_meta`: Extract source positions into separate `meta` map
///   - `with_references`: Extract CUE reference paths (e.g., for `dependsOn: [build]`, records that `dependsOn[0]` refers to `tasks.build`)
///   - `recursive`: Evaluate entire module tree (./...) or just current directory (.)
///   - `package_name`: Filter to specific package (takes precedence over legacy parameter)
///
/// # Returns
/// A `ModuleResult` containing:
/// - `instances`: Map of relative paths to their evaluated JSON values
/// - `meta`: Map of "path/field" to source locations and reference paths (when `with_meta` or `with_references` is true)
///
/// # Errors
/// Returns an error if:
/// - The module root path is invalid
/// - The CUE module cannot be loaded
/// - All CUE instances fail evaluation
#[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)] // FFI orchestration has inherent 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)
}

/// Convert a path to a `CString` for FFI.
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)
}

/// Convert a string to a `CString` for FFI.
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}"))
    })
}

/// Serialize options to JSON `CString` for FFI.
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")
}

/// Call the FFI function and return the JSON string result.
#[allow(clippy::cognitive_complexity)] // FFI error handling requires multiple branches
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();

    // Safety: cue_eval_module is an FFI function that:
    // - Takes three valid C string pointers (guaranteed by CString::as_ptr())
    // - Returns either null or a valid pointer to a C string
    // - The returned pointer must be freed with cue_free_string
    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"
    );

    // Safety: CStringPtr::new is safe because result_ptr is from cue_eval_module
    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(),
        ));
    }

    // Safety: result.to_str() is safe because we checked result is not null
    unsafe { result.to_str() }.map(String::from)
}

/// Parse the JSON envelope from the Go bridge.
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}"),
        )
    })
}

/// Process the bridge response and return the module result.
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)
}

/// Handle a bridge error and convert it to our error type.
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),
    }
}

/// Parse the module result from JSON.
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}"),
        )
    })
}

/// Extract a string from an FFI result wrapper.
#[allow(clippy::needless_pass_by_value)] // CStringPtr Drop impl manages FFI memory - must take ownership
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")));
    }

    // Safety: wrapper.to_str() is safe because we checked wrapper is not null
    unsafe { wrapper.to_str() }.map(String::from)
}

/// Gets the bridge version information from the Go side
///
/// This function returns version information about the Go FFI bridge,
/// including the protocol version and Go runtime version.
///
/// # Errors
///
/// Returns an error if:
/// - The FFI call fails
/// - The returned string is not valid UTF-8
///
/// # Returns
/// String containing bridge version information (e.g., "bridge/1 (Go go1.21.1)")
pub fn get_bridge_version() -> Result<String> {
    tracing::debug!("Getting bridge version information");

    // Safety: cue_bridge_version is an FFI function that:
    // - Takes no parameters
    // - Returns either null or a valid pointer to a C string
    // - The returned pointer must be freed with cue_free_string
    let version_ptr = unsafe { cue_bridge_version() };

    // Safety: CStringPtr::new is safe because version_ptr is from 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)
}

/// Convenience wrapper around `evaluate_module` for single-directory evaluation.
///
/// Uses `evaluate_module` with `recursive: false` internally.
///
/// # Arguments
/// * `dir_path` - Directory containing the CUE files
/// * `package_name` - Name of the CUE package to evaluate
///
/// # Returns
/// JSON string containing the evaluated CUE configuration
///
/// # Errors
///
/// Returns an error if:
/// - The CUE module evaluation fails (see [`evaluate_module`])
/// - No CUE instance is found in the specified directory
#[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, // Use module root
    };

    let result = evaluate_module(dir_path, package_name, Some(&options))?;

    // For single-directory eval, extract the instance at "." or the only instance
    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())
}

/// Evaluates a CUE package and returns the result as a typed struct
///
/// This is a convenience wrapper around `evaluate_cue_package` that deserializes
/// the JSON result into a strongly-typed Rust struct.
///
/// # Type Parameters
/// * `T` - The type to deserialize into. Must implement `serde::de::DeserializeOwned`
///
/// # Errors
///
/// Returns an error if:
/// - The CUE evaluation fails (see `evaluate_cue_package` for details)
/// - The JSON cannot be deserialized into the target type
///
/// # Arguments
/// * `dir_path` - Directory containing the CUE files
/// * `package_name` - Name of the CUE package to evaluate
///
/// # Returns
/// The evaluated CUE configuration as the specified type `T`
///
/// # Example
/// ```no_run
/// use cuengine::evaluate_cue_package_typed;
/// use serde::Deserialize;
/// use std::path::Path;
///
/// #[derive(Deserialize)]
/// struct MyConfig {
///     name: String,
/// }
///
/// let path = Path::new("/path/to/cue/files");
/// let config: MyConfig = evaluate_cue_package_typed(path, "mypackage").unwrap();
/// ```
#[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)] // Generic deserialization with error handling is inherently complex
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");

    // Get the JSON string from the basic evaluation
    let json_str = evaluate_cue_package(dir_path, package_name)?;

    // Deserialize into the target type
    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() {
        // Test with null pointer
        let null_ptr = unsafe { CStringPtr::new(std::ptr::null_mut()) };
        assert!(null_ptr.is_null());

        // Test with non-null pointer (we'll create a mock one)
        // Note: In real scenarios, this would come from FFI calls
        let test_string = CString::new("test").unwrap();
        let ptr = test_string.into_raw();
        let wrapper = unsafe { CStringPtr::new(ptr) };
        assert!(!wrapper.is_null());

        // Convert back to string and verify
        let result_str = unsafe { wrapper.to_str().unwrap() };
        assert_eq!(result_str, "test");
        // CStringPtr will automatically free the memory when dropped
    }

    #[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()) };

        // Test that we correctly identify null pointers
        assert!(null_wrapper.is_null());

        // In debug builds, this should panic. In release builds, it's undefined behavior.
        // Rather than testing undefined behavior, let's test the null check works
        if cfg!(debug_assertions) {
            // In debug mode, we expect a panic
            std::panic::catch_unwind(|| {
                let _ = unsafe { null_wrapper.to_str() };
            })
            .expect_err("Expected panic in debug mode for null pointer");
        } else {
            // In release mode, we just verify the null check works
            // Don't actually call to_str() with null as it's undefined behavior
            tracing::info!(
                "Skipping null pointer dereference test in release mode (undefined behavior)"
            );
        }
    }

    #[test]
    fn test_evaluate_cue_package_invalid_path() {
        // Test with invalid UTF-8 path (simulated)
        let invalid_path = Path::new("/nonexistent/\u{0000}/invalid");
        let result = evaluate_cue_package(invalid_path, "test");

        // Should fail with configuration error for invalid path
        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();

        // Package name with null bytes should fail
        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");

        // The behavior depends on the Go CUE implementation and FFI availability
        // In CI environments, the FFI bridge may behave differently
        // We just verify that the function doesn't panic and returns some result
        match result {
            Ok(json) => {
                // If it succeeds unexpectedly, log it but don't fail
                tracing::info!("FFI succeeded for nonexistent path (CI behavior): {json}");
                // In some CI environments, this might succeed with empty/default values
            }
            Err(error) => {
                // This is the expected behavior - log the 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();

        // Create a simple valid CUE file
        let cue_content = r#"package cuenv

env: {
    TEST_VAR: "test_value"
    NUMBER: 42
}
"#;
        fs::write(temp_dir.path().join("env.cue"), cue_content).unwrap();

        // This test depends on the Go FFI being available
        // In a real environment, this should work
        let result = evaluate_cue_package(temp_dir.path(), "cuenv");

        // The result depends on whether the FFI bridge is properly built
        // In CI this might fail if Go dependencies aren't available
        match result {
            Err(error) => {
                // If FFI isn't available, we should get a specific error
                tracing::info!("FFI not available in test environment: {error}");
                // This is acceptable in test environments without Go build
            }
            Ok(json) => {
                // If it works, verify the JSON contains our values
                println!("Got JSON response: {json}");
                // The JSON wraps everything in an "env" object
                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();

        // Create an invalid CUE file
        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");

        // The behavior depends on the Go CUE implementation and FFI availability
        // In CI environments, the FFI bridge may be more lenient or handle errors differently
        match result {
            Ok(json) => {
                // If it succeeds despite invalid CUE, this might be CI-specific behavior
                tracing::info!("FFI succeeded with invalid CUE (CI behavior): {json}");
                // Don't fail the test - just log the unexpected success
            }
            Err(error) => {
                // This is the expected behavior for invalid CUE
                tracing::info!("Got expected error for invalid CUE: {error}");
                assert!(!error.to_string().is_empty());
            }
        }
    }

    #[test]
    fn test_path_conversion_edge_cases() {
        // Test various path edge cases that might cause issues
        let temp_dir = TempDir::new().unwrap();
        let path_with_spaces = temp_dir.path().join("dir with spaces");
        fs::create_dir(&path_with_spaces).unwrap();

        // This should handle spaces correctly
        let result = evaluate_cue_package(&path_with_spaces, "env");

        // The result might be an error due to missing CUE files, but the path handling should work
        if let Err(e) = result {
            // Should not be a path conversion error
            assert!(!e.to_string().contains("Invalid directory path: not UTF-8"));
        }
    }

    // Integration test to verify memory management doesn't leak
    #[test]
    fn test_ffi_memory_management_stress() {
        let temp_dir = TempDir::new().unwrap();

        // Create a simple CUE file with valid syntax
        let cue_content = r#"package cuenv

env: {
    TEST: "value"
}"#;
        fs::write(temp_dir.path().join("env.cue"), cue_content).unwrap();

        // Call FFI function multiple times to test memory management
        for i in 0..100 {
            let result = evaluate_cue_package(temp_dir.path(), "cuenv");

            // Each call should be independent and not cause memory issues
            match result {
                Ok(json) => {
                    // If FFI is available, all calls should succeed
                    // Check for either TEST or env field (JSON structure may vary)
                    // The JSON wraps everything in an "env" object
                    assert!(
                        json.contains("env"),
                        "JSON should contain env field. Got: {json}"
                    );
                }
                Err(error) => {
                    // If FFI isn't available, error should be consistent
                    let error_msg = error.to_string();
                    tracing::info!("Iteration {i}: {error_msg}");

                    // Break early if it's clearly an FFI availability issue
                    if i > 5 {
                        break;
                    }
                }
            }
        }

        // If we get here without crashes, memory management is working
    }

    #[test]
    fn test_get_bridge_version() {
        let result = get_bridge_version();

        // The behavior depends on whether the Go FFI bridge is available
        match result {
            Ok(version) => {
                // If FFI is available, we should get a version string
                tracing::info!("Bridge version: {}", version);
                assert!(!version.is_empty());
                // Version should start with "bridge/1" according to the envelope format
                // but we'll be lenient in case the format changes
                assert!(version.len() > 3); // At least some meaningful content
            }
            Err(error) => {
                // If FFI isn't available, we should get a specific error
                tracing::info!("FFI not available for bridge version: {}", error);
                // This is acceptable in test environments without Go build
                let error_msg = error.to_string();
                assert!(!error_msg.is_empty());
                // Should mention the FFI function name
                assert!(error_msg.contains("cue_bridge_version") || error_msg.contains("FFI"));
            }
        }
    }

    // Test the error message parsing logic
    #[test]
    fn test_error_message_parsing() {
        // This tests the logic that parses "error:" prefixed messages
        // We can't easily mock the FFI call, but we can test the string logic

        let temp_dir = TempDir::new().unwrap();

        // The actual test depends on implementation details
        // For now, just verify the function exists and handles basic cases
        let result = evaluate_cue_package(temp_dir.path(), "nonexistent_package");

        // The behavior depends on whether the Go FFI bridge is available:
        // - If available: should return error for nonexistent package
        // - If not available: may return different error types
        // Either way, we should get some kind of result (error or success)

        match result {
            Ok(output) => {
                // If FFI isn't available or returns empty result, that's acceptable
                tracing::info!("FFI returned success (possibly unavailable): {output}");
            }
            Err(error) => {
                // Expected case - should get an error for nonexistent package
                let error_str = error.to_string();
                assert!(!error_str.is_empty());
                assert!(error_str.len() > 5); // Should be a meaningful message
                tracing::info!("Got expected error: {error_str}");
            }
        }

        // The main thing is the function doesn't crash/panic
    }

    #[test]
    fn test_bridge_error_constants_consistency() {
        // Test that our error constants match expected values
        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() {
        // Test parsing of valid success envelope
        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());

        // Test parsing of valid error envelope
        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() {
        // Test parsing of error envelope without hint
        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() {
        // Test that Drop trait is correctly implemented
        // This is mostly to ensure the Drop implementation doesn't panic

        // Test dropping a null pointer (should be safe)
        let null_ptr = unsafe { CStringPtr::new(std::ptr::null_mut()) };
        drop(null_ptr); // Should not panic

        // Test dropping a valid pointer
        let test_string = CString::new("test").unwrap();
        let ptr = test_string.into_raw();
        let wrapper = unsafe { CStringPtr::new(ptr) };
        drop(wrapper); // Should free the memory properly
    }

    #[test]
    fn test_get_bridge_version_functionality() {
        // This test covers the actual bridge version functionality
        // The behavior will depend on whether the Go bridge is available

        let result = get_bridge_version();

        match result {
            Ok(version) => {
                // If the bridge is available, test the version format
                tracing::info!("Bridge available with version: {}", version);

                // Version should not be empty
                assert!(!version.is_empty());

                // Version should contain the word "bridge" (case insensitive)
                assert!(
                    version.to_lowercase().contains("bridge"),
                    "Version should contain 'bridge': {version}"
                );

                // Should contain some Go version information
                assert!(
                    version.contains("go") || version.contains("Go"),
                    "Version should contain Go info: {version}"
                );
            }
            Err(error) => {
                // If the bridge is not available, verify the error is meaningful
                let error_str = error.to_string();

                // Error should not be empty
                assert!(!error_str.is_empty());

                // Should be an FFI error or mention the function name
                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() {
        // Test that we handle different error codes correctly
        // We can't easily mock the FFI, but we can test the logic

        // Create a mock bridge error for each error type
        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,
            };

            // The error should serialize and deserialize properly
            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() {
        // Test more edge cases for path handling

        // Test with empty package name - should be handled by Go side validation
        let temp_dir = TempDir::new().unwrap();
        let result = evaluate_cue_package(temp_dir.path(), "");

        // This should either fail with a validation error or succeed (depending on FFI availability)
        match result {
            Ok(_) => {
                // If it succeeds, the FFI might not be available (CI behavior)
                tracing::info!("FFI not available or handles empty package name gracefully");
            }
            Err(error) => {
                // Should get some meaningful 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() {
        // Test version compatibility checking logic
        // We can test this by creating mock JSON responses

        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() {
        // Test that the Serialize import is available even if not used
        // This ensures the import consistency we added is correct

        use serde::Serialize;

        #[derive(Serialize)]
        struct TestStruct {
            field: String,
        }

        let test = TestStruct {
            field: "test".to_string(),
        };

        let _json = serde_json::to_string(&test).unwrap();
        // If this compiles, the Serialize import is working
    }
}