97 lines
3.3 KiB
Go
97 lines
3.3 KiB
Go
// Package lib provides core functionality for the Bifrost HTTP service.
|
|
// This file contains JSON schema validation for config files.
|
|
package lib
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/santhosh-tekuri/jsonschema/v6"
|
|
)
|
|
|
|
// localSchemaCandidates lists paths (relative to CWD) where config.schema.json may be found
|
|
// when running from a source checkout. Checked in order before falling back to the remote URL.
|
|
var localSchemaCandidates = []string{
|
|
"config.schema.json", // running from transports/
|
|
"../config.schema.json", // running from transports/bifrost-http/
|
|
"transports/config.schema.json", // running from repo root
|
|
}
|
|
|
|
// tryLoadLocalSchema attempts to read config.schema.json from known local paths.
|
|
// Returns nil if none are found.
|
|
func tryLoadLocalSchema() []byte {
|
|
for _, p := range localSchemaCandidates {
|
|
data, err := os.ReadFile(p)
|
|
if err == nil {
|
|
return data
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateConfigSchema validates config data against the JSON schema.
|
|
// Returns nil if valid, or a formatted error describing all validation failures.
|
|
// An optional schemaOverride can be provided to use a local schema instead of fetching from the remote URL.
|
|
func ValidateConfigSchema(data []byte, schemaOverride ...[]byte) error {
|
|
var configSchemaJSONBytes []byte
|
|
if len(schemaOverride) > 0 && len(schemaOverride[0]) > 0 {
|
|
configSchemaJSONBytes = schemaOverride[0]
|
|
} else if localSchema := tryLoadLocalSchema(); localSchema != nil {
|
|
// Prefer the local schema file from the source checkout when available.
|
|
// This avoids validating against a potentially stale remote schema.
|
|
configSchemaJSONBytes = localSchema
|
|
} else {
|
|
// Pulling config.schema from https://www.getbifrost.ai/schema
|
|
configSchemaJSON, err := http.Get("https://www.getbifrost.ai/schema")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get config schema: %w", err)
|
|
}
|
|
defer configSchemaJSON.Body.Close()
|
|
var readErr error
|
|
configSchemaJSONBytes, readErr = io.ReadAll(configSchemaJSON.Body)
|
|
if readErr != nil {
|
|
logger.Warn("failed to download config schema: %v. running without config.json schema validation", readErr)
|
|
return nil
|
|
}
|
|
}
|
|
// Parse the schema JSON
|
|
schemaDoc, err := jsonschema.UnmarshalJSON(bytes.NewReader(configSchemaJSONBytes))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse config schema JSON: %w", err)
|
|
}
|
|
c := jsonschema.NewCompiler()
|
|
if err := c.AddResource("config.schema.json", schemaDoc); err != nil {
|
|
return fmt.Errorf("failed to add config schema resource: %w", err)
|
|
}
|
|
// Compile the schema
|
|
compiledSchema, err := c.Compile("config.schema.json")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to compile config schema: %w", err)
|
|
}
|
|
var v any
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
return fmt.Errorf("invalid JSON: %w", err)
|
|
}
|
|
err = compiledSchema.Validate(v)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
// Format validation errors for better readability
|
|
return formatValidationError(err)
|
|
}
|
|
|
|
// formatValidationError converts jsonschema validation errors into user-friendly messages
|
|
func formatValidationError(err error) error {
|
|
validationErr, ok := err.(*jsonschema.ValidationError)
|
|
if !ok {
|
|
return err
|
|
}
|
|
|
|
// Use the GoString format which provides detailed hierarchical output
|
|
return fmt.Errorf("schema validation failed:\n%s", validationErr.GoString())
|
|
}
|