Files

177 lines
4.4 KiB
Go

package serviceadapter
import (
"fmt"
"strings"
"sync"
"time"
)
// Registry holds all loaded ServiceAdapters indexed by serviceName.
type Registry struct {
mu sync.RWMutex
adapters map[string]*ServiceAdapter
logger Logger
}
// Logger interface for flexible logging.
type Logger interface {
Infof(format string, args ...interface{})
Errorf(format string, args ...interface{})
}
// SimpleLogger logs to stdout/stderr.
type SimpleLogger struct{}
func (l *SimpleLogger) Infof(format string, args ...interface{}) {
fmt.Printf("[INFO] "+format+"\n", args...)
}
func (l *SimpleLogger) Errorf(format string, args ...interface{}) {
fmt.Printf("[ERROR] "+format+"\n", args...)
}
// NewRegistry creates a new ServiceAdapter registry.
func NewRegistry(logger Logger) *Registry {
if logger == nil {
logger = &SimpleLogger{}
}
return &Registry{
adapters: make(map[string]*ServiceAdapter),
logger: logger,
}
}
// Add adds or updates a ServiceAdapter in the registry.
// Malformed schemas are logged but don't crash the registry.
func (r *Registry) Add(adapter *ServiceAdapter) error {
r.mu.Lock()
defer r.mu.Unlock()
// Validate schemas (basic check - real validation in 8.3)
if err := r.validateSchemas(adapter); err != nil {
r.logger.Errorf("adapter %s has invalid schema: %v, skipping", adapter.Namespace+"/"+adapter.ServiceName, err)
return nil // Don't crash, just skip
}
r.logger.Infof("adding/updating ServiceAdapter %s/%s", adapter.Namespace, adapter.ServiceName)
adapter.CreatedAt = time.Now()
r.adapters[adapter.ServiceName] = adapter
return nil
}
// Update updates an existing ServiceAdapter.
func (r *Registry) Update(adapter *ServiceAdapter) error {
return r.Add(adapter)
}
// Delete removes a ServiceAdapter from the registry.
func (r *Registry) Delete(serviceName string) {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.adapters[serviceName]; ok {
r.logger.Infof("deleting ServiceAdapter %s", serviceName)
delete(r.adapters, serviceName)
}
}
// Get returns a ServiceAdapter by name.
func (r *Registry) Get(serviceName string) *ServiceAdapter {
r.mu.RLock()
defer r.mu.RUnlock()
return r.adapters[serviceName]
}
// List returns all ServiceAdapters.
func (r *Registry) List() []*ServiceAdapter {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]*ServiceAdapter, 0, len(r.adapters))
for _, adapter := range r.adapters {
result = append(result, adapter)
}
return result
}
// Count returns the number of registered adapters.
func (r *Registry) Count() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.adapters)
}
// validateSchemas checks for malformed requestSchema/responseSchema.
// Real validation is in 8.3 (flat KV+type DSL parser).
func (r *Registry) validateSchemas(adapter *ServiceAdapter) error {
for _, res := range adapter.Spec.Resources {
for _, method := range res.Methods {
// Basic validation: schemas shouldn't contain obviously malformed patterns
if method.RequestSchema != "" {
if err := basicSchemaCheck(method.RequestSchema); err != nil {
return fmt.Errorf("resource %s method %s requestSchema: %w", res.Name, method.Verb, err)
}
}
if method.ResponseSchema != "" {
if err := basicSchemaCheck(method.ResponseSchema); err != nil {
return fmt.Errorf("resource %s method %s responseSchema: %w", res.Name, method.Verb, err)
}
}
}
}
return nil
}
// basicSchemaCheck does a simple sanity check on schema strings.
// Real parsing is in 8.3.
func basicSchemaCheck(schema string) error {
if schema == "" {
return nil
}
// Reject obviously invalid patterns
if strings.Contains(schema, "{{") && !strings.Contains(schema, "}}") {
return fmt.Errorf("unclosed template braces")
}
if strings.Count(schema, "(") != strings.Count(schema, ")") {
return fmt.Errorf("mismatched parentheses")
}
return nil
}
// MockLogger for testing.
type MockLogger struct {
entries []string
mu sync.Mutex
}
func (l *MockLogger) Infof(format string, args ...interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
l.entries = append(l.entries, fmt.Sprintf("[INFO] "+format, args...))
}
func (l *MockLogger) Errorf(format string, args ...interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
l.entries = append(l.entries, fmt.Sprintf("[ERROR] "+format, args...))
}
func (l *MockLogger) Entries() []string {
l.mu.Lock()
defer l.mu.Unlock()
result := make([]string, len(l.entries))
copy(result, l.entries)
return result
}
func (l *MockLogger) Clear() {
l.mu.Lock()
defer l.mu.Unlock()
l.entries = nil
}