use crate::backends::go::gen_bindings::binding_file::{format_go_code, strip_trailing_whitespace};
pub(super) fn generate(package: &str, ffi_prefix: &str, ffi_header: &str, ffi_lib_name: &str, to_root: &str) -> String {
let source = format!(
r#"// Package {package} provides Go bindings for the component manager.
package {package}
/*
#cgo CFLAGS: -I${{SRCDIR}}/include
#cgo LDFLAGS: -L${{SRCDIR}}/{to_root}target/release -l{ffi_lib_name}
#include <stdlib.h>
#include "{ffi_header}"
*/
import "C"
import (
"encoding/json"
"fmt"
"strings"
"unsafe"
)
// ComponentError reports a downloadable-component operation's stable failure code alongside
// its human-readable message, so callers can branch on Code -- "offline", "signature_invalid",
// "unsupported_host", and so on -- instead of parsing free-form text.
type ComponentError struct {{
Code string
Message string
}}
func (e *ComponentError) Error() string {{
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}}
// ComponentLoad downloads, verifies, loads, and pins a configured component.
func ComponentLoad(component string) error {{
cComponent := C.CString(component)
defer C.free(unsafe.Pointer(cComponent))
if C.{ffi_prefix}_component_load(cComponent) != 0 {{
return componentError("component load")
}}
return nil
}}
// ComponentPrefetch downloads and verifies one named component, or every configured
// component when called without an argument.
func ComponentPrefetch(component ...string) ([]string, error) {{
if len(component) > 1 {{
return nil, fmt.Errorf("component prefetch accepts at most one component, got %d", len(component))
}}
var cComponent *C.char
if len(component) == 1 {{
cComponent = C.CString(component[0])
defer C.free(unsafe.Pointer(cComponent))
}}
result := C.{ffi_prefix}_component_prefetch(cComponent)
if result == nil {{
return nil, componentError("component prefetch")
}}
defer C.{ffi_prefix}_free_string(result)
var paths []string
if err := json.Unmarshal([]byte(C.GoString(result)), &paths); err != nil {{
return nil, fmt.Errorf("decode component prefetch result: %w", err)
}}
return paths, nil
}}
// ComponentStatus returns ready, cached, not_downloaded, bundled, or unsupported:<reason>.
// See ComponentStatusCode for the matching numeric code.
func ComponentStatus(component string) (string, error) {{
cComponent := C.CString(component)
defer C.free(unsafe.Pointer(cComponent))
result := C.{ffi_prefix}_component_status(cComponent)
if result == nil {{
return "", componentError("component status")
}}
defer C.{ffi_prefix}_free_string(result)
return C.GoString(result), nil
}}
// ComponentStatusCode returns the numeric counterpart to ComponentStatus, stable across
// releases: 0 ready, 1 cached, 2 not_downloaded, 3 bundled, 4 unsupported.
func ComponentStatusCode(component string) (int32, error) {{
cComponent := C.CString(component)
defer C.free(unsafe.Pointer(cComponent))
result := C.{ffi_prefix}_component_status_code(cComponent)
if result < 0 {{
return 0, componentError("component status code")
}}
return int32(result), nil
}}
// ComponentCachePath returns the content-addressed cache path for a component.
func ComponentCachePath(component string) (string, error) {{
cComponent := C.CString(component)
defer C.free(unsafe.Pointer(cComponent))
result := C.{ffi_prefix}_component_cache_path(cComponent)
if result == nil {{
return "", componentError("component cache path")
}}
defer C.{ffi_prefix}_free_string(result)
return C.GoString(result), nil
}}
// ComponentActivate downloads, verifies, loads, and registers a component's
// contracts so core-crate code that looks them up finds them. It is never
// triggered automatically; call it explicitly before relying on a component.
func ComponentActivate(component string) error {{
cComponent := C.CString(component)
defer C.free(unsafe.Pointer(cComponent))
if C.{ffi_prefix}_component_activate(cComponent) != 0 {{
return componentError("component activate")
}}
return nil
}}
func componentError(operation string) error {{
err := lastError()
if err == nil {{
return fmt.Errorf("%s failed without a native error", operation)
}}
// The native layer prefixes every component failure message with a stable
// "code: " token (see alef_component_error_message in alef::backends::native_components);
// splitting it back out here is what lets a caller type-assert *ComponentError and
// branch on Code instead of matching on the message text.
code, message, found := strings.Cut(err.Error(), ": ")
if !found {{
return fmt.Errorf("%s: %w", operation, err)
}}
return fmt.Errorf("%s: %w", operation, &ComponentError{{Code: code, Message: message}})
}}
"#,
);
format_go_code(&strip_trailing_whitespace(&source))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emits_go_wrappers_over_shared_c_manager() {
let generated = generate("demo", "demo", "demo.h", "demo_ffi", "../../");
assert!(generated.contains("func ComponentLoad(component string) error"));
assert!(generated.contains("func ComponentPrefetch(component ...string)"));
assert!(generated.contains("C.demo_component_status(cComponent)"));
assert!(generated.contains("func ComponentStatusCode(component string) (int32, error)"));
assert!(generated.contains("C.demo_component_status_code(cComponent)"));
assert!(generated.contains("C.demo_component_cache_path(cComponent)"));
assert!(generated.contains("C.demo_free_string(result)"));
assert!(generated.contains("func ComponentActivate(component string) error"));
assert!(generated.contains("C.demo_component_activate(cComponent)"));
assert!(generated.contains("type ComponentError struct"));
assert!(generated.contains("strings.Cut(err.Error(), \": \")"));
}
}