331 lines
11 KiB
Plaintext
331 lines
11 KiB
Plaintext
|
|
# Provider Loader System
|
||
|
|
# Dynamic provider loading and interface validation
|
||
|
|
|
||
|
|
use registry.nu *
|
||
|
|
use interface.nu *
|
||
|
|
use ../utils/logging.nu *
|
||
|
|
|
||
|
|
# Load provider dynamically with validation
|
||
|
|
export def load-provider [name: string]: nothing -> record {
|
||
|
|
# Silent loading - only log errors, not info/success
|
||
|
|
# Provider loading happens multiple times due to wrapper scripts, logging creates noise
|
||
|
|
|
||
|
|
# Check if provider is available
|
||
|
|
if not (is-provider-available $name) {
|
||
|
|
log-error $"Provider ($name) not found or not available" "provider-loader"
|
||
|
|
return {}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get provider registry entry
|
||
|
|
let provider_entry = (get-provider-entry $name)
|
||
|
|
|
||
|
|
# Load the provider module
|
||
|
|
let provider_instance = if ($provider_entry.type == "core") {
|
||
|
|
load-core-provider $provider_entry
|
||
|
|
} else {
|
||
|
|
load-extension-provider $provider_entry
|
||
|
|
}
|
||
|
|
|
||
|
|
if not ($provider_instance | is-empty) {
|
||
|
|
# Validate interface compliance
|
||
|
|
let validation = (validate-provider-interface $name $provider_instance)
|
||
|
|
if $validation.valid {
|
||
|
|
$provider_instance
|
||
|
|
} else {
|
||
|
|
log-error $"Provider ($name) failed interface validation" "provider-loader"
|
||
|
|
log-error $"Missing functions: ($validation.missing_functions | str join ', ')" "provider-loader"
|
||
|
|
{}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
log-error $"Failed to load provider module for ($name)" "provider-loader"
|
||
|
|
{}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Load core provider
|
||
|
|
def load-core-provider [provider_entry: record]: nothing -> record {
|
||
|
|
# For core providers, use direct module loading
|
||
|
|
# Core providers should be in the core library path
|
||
|
|
let module_path = $provider_entry.entry_point
|
||
|
|
|
||
|
|
# Create provider instance record
|
||
|
|
{
|
||
|
|
name: $provider_entry.name
|
||
|
|
type: "core"
|
||
|
|
loaded: true
|
||
|
|
entry_point: $module_path
|
||
|
|
load_time: (date now)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Load extension provider
|
||
|
|
def load-extension-provider [provider_entry: record]: nothing -> record {
|
||
|
|
# For extension providers, use the adapter pattern
|
||
|
|
let module_path = $provider_entry.entry_point
|
||
|
|
|
||
|
|
# Test that the provider file exists and has the required functions
|
||
|
|
let test_cmd = $"nu -c \"use ($module_path) *; get-provider-metadata | to json\""
|
||
|
|
let test_result_check = (do { nu -c $test_cmd | complete })
|
||
|
|
|
||
|
|
if ($test_result_check.exit_code != 0) {
|
||
|
|
log-error $"Provider validation failed for ($provider_entry.name)" "provider-loader"
|
||
|
|
{}
|
||
|
|
} else {
|
||
|
|
# Create provider instance record
|
||
|
|
{
|
||
|
|
name: $provider_entry.name
|
||
|
|
type: "extension"
|
||
|
|
loaded: true
|
||
|
|
entry_point: $module_path
|
||
|
|
load_time: (date now)
|
||
|
|
metadata: ($test_result_check.stdout | from json)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get provider instance (with caching)
|
||
|
|
export def get-provider [name: string]: nothing -> record {
|
||
|
|
# Check if already loaded in this session
|
||
|
|
let cache_key = $"PROVIDER_LOADED_($name)"
|
||
|
|
if ($env | get -o $cache_key) != null {
|
||
|
|
return ($env | get $cache_key)
|
||
|
|
}
|
||
|
|
|
||
|
|
# Load and cache the provider
|
||
|
|
let provider = (load-provider $name)
|
||
|
|
if not ($provider | is-empty) {
|
||
|
|
load-env { $cache_key: $provider }
|
||
|
|
}
|
||
|
|
$provider
|
||
|
|
}
|
||
|
|
|
||
|
|
# Call a provider function dynamically
|
||
|
|
export def call-provider-function [
|
||
|
|
provider_name: string
|
||
|
|
function_name: string
|
||
|
|
...args
|
||
|
|
]: nothing -> any {
|
||
|
|
# Get provider entry
|
||
|
|
let provider_entry = (get-provider-entry $provider_name)
|
||
|
|
|
||
|
|
if ($provider_entry | is-empty) {
|
||
|
|
log-error $"Provider ($provider_name) not found" "provider-loader"
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
# Use direct import and call via a wrapper script
|
||
|
|
let temp_dir = ($env.TMPDIR? | default "/tmp")
|
||
|
|
|
||
|
|
# Save arguments as a list to a single file
|
||
|
|
let args_file = ($temp_dir | path join $"provider_args_(random chars).nuon")
|
||
|
|
$args | to nuon | save --force $args_file
|
||
|
|
|
||
|
|
# Create wrapper script that loads args and calls function
|
||
|
|
# Build individual arg references based on count
|
||
|
|
let wrapper_script = ($temp_dir | path join $"provider_wrapper_(random chars).nu")
|
||
|
|
let arg_count = ($args | length)
|
||
|
|
|
||
|
|
let call_line = if $arg_count == 0 {
|
||
|
|
$"($function_name) | to json"
|
||
|
|
} else if $arg_count == 1 {
|
||
|
|
$"($function_name) \(\$args | get 0\) | to json"
|
||
|
|
} else if $arg_count == 2 {
|
||
|
|
$"($function_name) \(\$args | get 0\) \(\$args | get 1\) | to json"
|
||
|
|
} else if $arg_count == 3 {
|
||
|
|
$"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) | to json"
|
||
|
|
} else if $arg_count == 4 {
|
||
|
|
$"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) \(\$args | get 3\) | to json"
|
||
|
|
} else if $arg_count == 5 {
|
||
|
|
$"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) \(\$args | get 3\) \(\$args | get 4\) | to json"
|
||
|
|
} else {
|
||
|
|
log-error $"Too many arguments \(($arg_count)\) for provider function" "provider-loader"
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
let script_content = $"
|
||
|
|
use ($provider_entry.entry_point) *
|
||
|
|
let args = \(open ($args_file)\)
|
||
|
|
($call_line)
|
||
|
|
"
|
||
|
|
$script_content | save --force $wrapper_script
|
||
|
|
|
||
|
|
# Execute the wrapper script
|
||
|
|
let result = (do { nu $wrapper_script } | complete)
|
||
|
|
|
||
|
|
# Clean up temp files
|
||
|
|
if ($args_file | path exists) { rm -f $args_file }
|
||
|
|
if ($wrapper_script | path exists) { rm -f $wrapper_script }
|
||
|
|
|
||
|
|
# Return result if successful, null otherwise
|
||
|
|
if $result.exit_code == 0 {
|
||
|
|
# Try to parse as structured data (JSON, NUON, etc), fallback to string
|
||
|
|
let output = ($result.stdout | str trim)
|
||
|
|
if ($output | is-empty) {
|
||
|
|
null
|
||
|
|
} else if $output == "true" {
|
||
|
|
true
|
||
|
|
} else if $output == "false" {
|
||
|
|
false
|
||
|
|
} else if ($output | str starts-with "{") or ($output | str starts-with "[") {
|
||
|
|
# Try JSON parse - use error handling for Nushell 0.107
|
||
|
|
let parsed = (do -i { $output | from json })
|
||
|
|
if ($parsed | is-empty) {
|
||
|
|
$output
|
||
|
|
} else {
|
||
|
|
$parsed
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$output
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
log-error $"Provider function call failed: ($result.stderr)" "provider-loader"
|
||
|
|
null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get required provider functions
|
||
|
|
def get-required-functions []: nothing -> list<string> {
|
||
|
|
[
|
||
|
|
"get-provider-metadata"
|
||
|
|
"query_servers"
|
||
|
|
"server_exists"
|
||
|
|
"check_server_requirements"
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
# Validate provider interface compliance
|
||
|
|
def validate-provider-interface [provider_name: string, provider_instance: record]: nothing -> record {
|
||
|
|
let required_functions = (get-required-functions)
|
||
|
|
mut missing_functions = []
|
||
|
|
mut valid = true
|
||
|
|
|
||
|
|
# Check if provider file exists
|
||
|
|
let provider_file = $provider_instance.entry_point
|
||
|
|
if not ($provider_file | path exists) {
|
||
|
|
return {
|
||
|
|
valid: false
|
||
|
|
missing_functions: ["provider file not found"]
|
||
|
|
provider: $provider_name
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check each required function
|
||
|
|
for func in $required_functions {
|
||
|
|
let check_cmd = $"nu -c \"use ($provider_file) *; help commands | where name == '($func)' | length\""
|
||
|
|
let check_result = (nu -c $check_cmd | complete)
|
||
|
|
|
||
|
|
if $check_result.exit_code == 0 {
|
||
|
|
let func_count = ($check_result.stdout | str trim | into int)
|
||
|
|
if $func_count == 0 {
|
||
|
|
$missing_functions = ($missing_functions | append $func)
|
||
|
|
$valid = false
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$missing_functions = ($missing_functions | append $func)
|
||
|
|
$valid = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
valid: $valid
|
||
|
|
missing_functions: $missing_functions
|
||
|
|
provider: $provider_name
|
||
|
|
checked_functions: ($required_functions | length)
|
||
|
|
validation_time: (date now)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Load multiple providers
|
||
|
|
export def load-providers [provider_names: list<string>]: nothing -> record {
|
||
|
|
mut results = {
|
||
|
|
successful: 0
|
||
|
|
failed: 0
|
||
|
|
total: ($provider_names | length)
|
||
|
|
details: []
|
||
|
|
}
|
||
|
|
|
||
|
|
for provider_name in $provider_names {
|
||
|
|
let result = (load-provider $provider_name)
|
||
|
|
if not ($result | is-empty) {
|
||
|
|
$results.successful = ($results.successful + 1)
|
||
|
|
$results.details = ($results.details | append {
|
||
|
|
provider: $provider_name
|
||
|
|
status: "success"
|
||
|
|
loaded: true
|
||
|
|
})
|
||
|
|
} else {
|
||
|
|
$results.failed = ($results.failed + 1)
|
||
|
|
$results.details = ($results.details | append {
|
||
|
|
provider: $provider_name
|
||
|
|
status: "failed"
|
||
|
|
loaded: false
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$results
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check provider health
|
||
|
|
export def check-provider-health [provider_name: string]: nothing -> record {
|
||
|
|
let health_check = {
|
||
|
|
provider: $provider_name
|
||
|
|
available: false
|
||
|
|
loadable: false
|
||
|
|
interface_valid: false
|
||
|
|
metadata_accessible: false
|
||
|
|
timestamp: (date now)
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check if provider is available
|
||
|
|
let available = (is-provider-available $provider_name)
|
||
|
|
let updated_health = ($health_check | merge { available: $available })
|
||
|
|
|
||
|
|
if not $available {
|
||
|
|
return $updated_health
|
||
|
|
}
|
||
|
|
|
||
|
|
# Try to load provider
|
||
|
|
let provider_instance = (load-provider $provider_name)
|
||
|
|
let loadable = not ($provider_instance | is-empty)
|
||
|
|
let updated_health = ($updated_health | merge { loadable: $loadable })
|
||
|
|
|
||
|
|
if not $loadable {
|
||
|
|
return $updated_health
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check interface validation
|
||
|
|
let validation = (validate-provider-interface $provider_name $provider_instance)
|
||
|
|
let updated_health = ($updated_health | merge { interface_valid: $validation.valid })
|
||
|
|
|
||
|
|
# Check metadata access
|
||
|
|
let provider_entry = (get-provider-entry $provider_name)
|
||
|
|
let metadata_cmd = $"nu -c \"use ($provider_entry.entry_point) *; get-provider-metadata\""
|
||
|
|
let metadata_result = (nu -c $metadata_cmd | complete)
|
||
|
|
let metadata_accessible = ($metadata_result.exit_code == 0)
|
||
|
|
|
||
|
|
$updated_health | merge { metadata_accessible: $metadata_accessible }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check health of all providers
|
||
|
|
export def check-all-providers-health []: nothing -> table {
|
||
|
|
let providers = (list-providers --available-only)
|
||
|
|
|
||
|
|
$providers | each {|provider|
|
||
|
|
check-provider-health $provider.name
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get loader statistics
|
||
|
|
export def get-loader-stats []: nothing -> record {
|
||
|
|
let provider_stats = (get-provider-stats)
|
||
|
|
let health_checks = (check-all-providers-health)
|
||
|
|
|
||
|
|
{
|
||
|
|
total_providers: $provider_stats.total_providers
|
||
|
|
available_providers: $provider_stats.available_providers
|
||
|
|
healthy_providers: ($health_checks | where interface_valid == true | length)
|
||
|
|
last_check: (date now)
|
||
|
|
}
|
||
|
|
}
|