From 9a50ca8cfea342a514e16f183c3dd948a6300e3c Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Mon, 23 Mar 2026 12:51:26 -0500 Subject: [PATCH] move schema to an internal package --- api.go | 10 +++---- internal/config/api.go | 22 --------------- internal/config/config.go | 21 -------------- internal/config/route.go | 23 --------------- internal/module/module.go | 46 ------------------------------ internal/processor/processor.go | 42 --------------------------- internal/schema/api.go | 26 +++++++++++++++++ internal/schema/config.go | 45 +++++++++++++++++++++++++++++ internal/schema/modules.go | 50 +++++++++++++++++++++++++++++++++ internal/schema/processors.go | 46 ++++++++++++++++++++++++++++++ internal/schema/routes.go | 24 ++++++++++++++++ internal/schema/schema.go | 27 ++++++++++++++++++ 12 files changed, 223 insertions(+), 159 deletions(-) create mode 100644 internal/schema/api.go create mode 100644 internal/schema/config.go create mode 100644 internal/schema/modules.go create mode 100644 internal/schema/processors.go create mode 100644 internal/schema/routes.go create mode 100644 internal/schema/schema.go diff --git a/api.go b/api.go index a6e34bb..e99b88a 100644 --- a/api.go +++ b/api.go @@ -10,8 +10,8 @@ import ( "github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/module" - "github.com/jwetzell/showbridge-go/internal/processor" "github.com/jwetzell/showbridge-go/internal/route" + "github.com/jwetzell/showbridge-go/internal/schema" ) func (r *Router) startAPIServer(config config.ApiConfig) { @@ -135,7 +135,7 @@ func (r *Router) handleConfigHTTP(w http.ResponseWriter, req *http.Request) { func handleConfigSchema(w http.ResponseWriter, req *http.Request) { switch req.Method { case http.MethodGet: - schemaJSON, err := json.Marshal(config.ConfigSchema) + schemaJSON, err := json.Marshal(schema.ConfigSchema) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return @@ -157,7 +157,7 @@ func handleConfigSchema(w http.ResponseWriter, req *http.Request) { func handleRoutesSchema(w http.ResponseWriter, req *http.Request) { switch req.Method { case http.MethodGet: - schemaJSON, err := json.Marshal(config.RoutesConfigSchema) + schemaJSON, err := json.Marshal(schema.RoutesConfigSchema) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return @@ -179,7 +179,7 @@ func handleRoutesSchema(w http.ResponseWriter, req *http.Request) { func handleModulesSchema(w http.ResponseWriter, req *http.Request) { switch req.Method { case http.MethodGet: - schemaJSON, err := json.Marshal(module.GetModulesSchema()) + schemaJSON, err := json.Marshal(schema.GetModulesSchema()) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return @@ -201,7 +201,7 @@ func handleModulesSchema(w http.ResponseWriter, req *http.Request) { func handleProcessorsSchema(w http.ResponseWriter, req *http.Request) { switch req.Method { case http.MethodGet: - schemaJSON, err := json.Marshal(processor.GetProcessorsSchema()) + schemaJSON, err := json.Marshal(schema.GetProcessorsSchema()) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return diff --git a/internal/config/api.go b/internal/config/api.go index 43714f7..7ec2162 100644 --- a/internal/config/api.go +++ b/internal/config/api.go @@ -1,28 +1,6 @@ package config -import ( - "encoding/json" - - "github.com/google/jsonschema-go/jsonschema" -) - type ApiConfig struct { Enabled bool `json:"enabled"` Port int `json:"port"` } - -var ApiConfigSchema = jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "enabled": { - Type: "boolean", - Description: "Whether the API server is enabled", - Default: json.RawMessage(`false`), - }, - "port": { - Type: "integer", - Description: "Port for the API server to listen on", - }, - }, - Required: []string{"port"}, -} diff --git a/internal/config/config.go b/internal/config/config.go index 552078a..d07a00a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,28 +1,7 @@ package config -import ( - "github.com/google/jsonschema-go/jsonschema" -) - type Config struct { Api ApiConfig `json:"api"` Modules []ModuleConfig `json:"modules"` Routes []RouteConfig `json:"routes"` } - -var ConfigSchema = jsonschema.Schema{ - Schema: "https://json-schema.org/draft/2020-12/schema", - ID: "https://showbridge.io/config.schema.json", - Title: "Config", - Description: "showbridge configuration", - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "api": &ApiConfigSchema, - "modules": { - Ref: "https://showbridge.io/modules.schema.json", - }, - "routes": { - Ref: "https://showbridge.io/routes.schema.json", - }, - }, -} diff --git a/internal/config/route.go b/internal/config/route.go index beaab5a..c23c3b1 100644 --- a/internal/config/route.go +++ b/internal/config/route.go @@ -1,29 +1,6 @@ package config -import "github.com/google/jsonschema-go/jsonschema" - type RouteConfig struct { Input string `json:"input"` Processors []ProcessorConfig `json:"processors"` } - -var RoutesConfigSchema = jsonschema.Schema{ - Schema: "https://json-schema.org/draft/2020-12/schema", - ID: "https://showbridge.io/routes.schema.json", - Title: "Routes", - Description: "route configurations", - Type: "array", - Items: &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "input": { - Type: "string", - MinLength: jsonschema.Ptr(1), - }, - "processors": { - Ref: "https://showbridge.io/processors.schema.json", - }, - }, - Required: []string{"input"}, - }, -} diff --git a/internal/module/module.go b/internal/module/module.go index fe6d8e5..bd26176 100644 --- a/internal/module/module.go +++ b/internal/module/module.go @@ -50,49 +50,3 @@ var ( func CreateLogger(config config.ModuleConfig) *slog.Logger { return slog.Default().With("component", "module", "id", config.Id, "type", config.Type) } - -func GetModulesSchema() *jsonschema.Schema { - moduleRegistryMu.RLock() - defer moduleRegistryMu.RUnlock() - - schema := &jsonschema.Schema{ - Schema: "https://json-schema.org/draft/2020-12/schema", - ID: "https://showbridge.io/modules.schema.json", - Title: "Modules", - Description: "module configurations", - Type: "array", - } - - moduleDefinitionSchemas := []*jsonschema.Schema{} - for _, mod := range ModuleRegistry { - moduleSchema := &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "id": { - Type: "string", - MinLength: jsonschema.Ptr(1), - }, - "type": { - Const: jsonschema.Ptr[any](mod.Type), - }, - }, - Required: []string{"id", "type"}, - AdditionalProperties: nil, - } - if mod.Title != "" { - moduleSchema.Title = mod.Title - } - if mod.Description != "" { - moduleSchema.Description = mod.Description - } - if mod.ParamsSchema != nil { - moduleSchema.Properties["params"] = mod.ParamsSchema - moduleSchema.Required = append(moduleSchema.Required, "params") - } - moduleDefinitionSchemas = append(moduleDefinitionSchemas, moduleSchema) - } - schema.Items = &jsonschema.Schema{ - OneOf: moduleDefinitionSchemas, - } - return schema -} diff --git a/internal/processor/processor.go b/internal/processor/processor.go index a4d0562..7c8e0fd 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -45,45 +45,3 @@ var ( processorRegistryMu sync.RWMutex ProcessorRegistry = make(map[string]ProcessorRegistration) ) - -func GetProcessorsSchema() *jsonschema.Schema { - processorRegistryMu.RLock() - defer processorRegistryMu.RUnlock() - - schema := &jsonschema.Schema{ - Schema: "https://json-schema.org/draft/2020-12/schema", - ID: "https://showbridge.io/processors.schema.json", - Title: "Processors", - Description: "processor configurations", - Type: "array", - } - - processorDefinitionSchemas := []*jsonschema.Schema{} - for _, proc := range ProcessorRegistry { - processorSchema := &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{ - "type": { - Const: jsonschema.Ptr[any](proc.Type), - }, - }, - Required: []string{"type"}, - AdditionalProperties: nil, - } - if proc.Title != "" { - processorSchema.Title = proc.Title - } - if proc.Description != "" { - processorSchema.Description = proc.Description - } - if proc.ParamsSchema != nil { - processorSchema.Properties["params"] = proc.ParamsSchema - processorSchema.Required = append(processorSchema.Required, "params") - } - processorDefinitionSchemas = append(processorDefinitionSchemas, processorSchema) - } - schema.Items = &jsonschema.Schema{ - OneOf: processorDefinitionSchemas, - } - return schema -} diff --git a/internal/schema/api.go b/internal/schema/api.go new file mode 100644 index 0000000..0abb2ba --- /dev/null +++ b/internal/schema/api.go @@ -0,0 +1,26 @@ +package schema + +import ( + "encoding/json" + + "github.com/google/jsonschema-go/jsonschema" +) + +var ApiConfigSchema = jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "enabled": { + Type: "boolean", + Description: "Whether the API server is enabled", + Default: json.RawMessage(`false`), + }, + "port": { + Type: "integer", + Description: "Port for the API server to listen on", + Minimum: jsonschema.Ptr[float64](1024), + Maximum: jsonschema.Ptr[float64](65535), + Default: json.RawMessage(`8080`), + }, + }, + Required: []string{"port"}, +} diff --git a/internal/schema/config.go b/internal/schema/config.go new file mode 100644 index 0000000..bcf5d28 --- /dev/null +++ b/internal/schema/config.go @@ -0,0 +1,45 @@ +package schema + +import ( + "encoding/json" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/jwetzell/showbridge-go/internal/config" +) + +var ConfigSchema = jsonschema.Schema{ + Schema: "https://json-schema.org/draft/2020-12/schema", + ID: "https://showbridge.io/config.schema.json", + Title: "Config", + Description: "showbridge configuration", + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "api": &ApiConfigSchema, + "modules": { + Ref: "https://showbridge.io/modules.schema.json", + }, + "routes": { + Ref: "https://showbridge.io/routes.schema.json", + }, + }, +} + +func ValidateConfig(config config.Config) error { + resolvedSchema, err := GetResolvedConfigSchema() + if err != nil { + return err + } + + jsonBytes, err := json.Marshal(config) + if err != nil { + return err + } + + jsonMap := make(map[string]any) + err = json.Unmarshal(jsonBytes, &jsonMap) + if err != nil { + return err + } + + return resolvedSchema.Validate(jsonMap) +} diff --git a/internal/schema/modules.go b/internal/schema/modules.go new file mode 100644 index 0000000..1934108 --- /dev/null +++ b/internal/schema/modules.go @@ -0,0 +1,50 @@ +package schema + +import ( + "github.com/google/jsonschema-go/jsonschema" + "github.com/jwetzell/showbridge-go/internal/module" +) + +func GetModulesSchema() *jsonschema.Schema { + + schema := &jsonschema.Schema{ + Schema: "https://json-schema.org/draft/2020-12/schema", + ID: "https://showbridge.io/modules.schema.json", + Title: "Modules", + Description: "module configurations", + Type: "array", + } + + moduleDefinitionSchemas := []*jsonschema.Schema{} + for _, mod := range module.ModuleRegistry { + moduleSchema := &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "id": { + Type: "string", + MinLength: jsonschema.Ptr(1), + }, + "type": { + Const: jsonschema.Ptr[any](mod.Type), + }, + }, + Required: []string{"id", "type"}, + AdditionalProperties: nil, + } + if mod.Title != "" { + moduleSchema.Title = mod.Title + } + if mod.Description != "" { + moduleSchema.Description = mod.Description + } + if mod.ParamsSchema != nil { + moduleSchema.Properties["params"] = mod.ParamsSchema + moduleSchema.Required = append(moduleSchema.Required, "params") + } + moduleDefinitionSchemas = append(moduleDefinitionSchemas, moduleSchema) + } + schema.Items = &jsonschema.Schema{ + OneOf: moduleDefinitionSchemas, + } + return schema +} diff --git a/internal/schema/processors.go b/internal/schema/processors.go new file mode 100644 index 0000000..a9ca307 --- /dev/null +++ b/internal/schema/processors.go @@ -0,0 +1,46 @@ +package schema + +import ( + "github.com/google/jsonschema-go/jsonschema" + "github.com/jwetzell/showbridge-go/internal/processor" +) + +func GetProcessorsSchema() *jsonschema.Schema { + + schema := &jsonschema.Schema{ + Schema: "https://json-schema.org/draft/2020-12/schema", + ID: "https://showbridge.io/processors.schema.json", + Title: "Processors", + Description: "processor configurations", + Type: "array", + } + + processorDefinitionSchemas := []*jsonschema.Schema{} + for _, proc := range processor.ProcessorRegistry { + processorSchema := &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "type": { + Const: jsonschema.Ptr[any](proc.Type), + }, + }, + Required: []string{"type"}, + AdditionalProperties: nil, + } + if proc.Title != "" { + processorSchema.Title = proc.Title + } + if proc.Description != "" { + processorSchema.Description = proc.Description + } + if proc.ParamsSchema != nil { + processorSchema.Properties["params"] = proc.ParamsSchema + processorSchema.Required = append(processorSchema.Required, "params") + } + processorDefinitionSchemas = append(processorDefinitionSchemas, processorSchema) + } + schema.Items = &jsonschema.Schema{ + OneOf: processorDefinitionSchemas, + } + return schema +} diff --git a/internal/schema/routes.go b/internal/schema/routes.go new file mode 100644 index 0000000..65ba905 --- /dev/null +++ b/internal/schema/routes.go @@ -0,0 +1,24 @@ +package schema + +import "github.com/google/jsonschema-go/jsonschema" + +var RoutesConfigSchema = jsonschema.Schema{ + Schema: "https://json-schema.org/draft/2020-12/schema", + ID: "https://showbridge.io/routes.schema.json", + Title: "Routes", + Description: "route configurations", + Type: "array", + Items: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "input": { + Type: "string", + MinLength: jsonschema.Ptr(1), + }, + "processors": { + Ref: "https://showbridge.io/processors.schema.json", + }, + }, + Required: []string{"input"}, + }, +} diff --git a/internal/schema/schema.go b/internal/schema/schema.go new file mode 100644 index 0000000..1946b84 --- /dev/null +++ b/internal/schema/schema.go @@ -0,0 +1,27 @@ +package schema + +import ( + "fmt" + "net/url" + + "github.com/google/jsonschema-go/jsonschema" +) + +func GetResolvedConfigSchema() (*jsonschema.Resolved, error) { + configSchema := ConfigSchema + + return configSchema.Resolve(&jsonschema.ResolveOptions{ + Loader: func(uri *url.URL) (*jsonschema.Schema, error) { + switch uri.String() { + case "https://showbridge.io/modules.schema.json": + return GetModulesSchema(), nil + case "https://showbridge.io/processors.schema.json": + return GetProcessorsSchema(), nil + case "https://showbridge.io/routes.schema.json": + return &RoutesConfigSchema, nil + default: + return nil, fmt.Errorf("unknown schema reference: %s", uri.String()) + } + }, + }) +}