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 }