94 lines
2.0 KiB
Go
94 lines
2.0 KiB
Go
// Package validator provides a validator for the marka data.
|
|
package validator
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.max-richter.dev/max/marka/registry"
|
|
"github.com/santhosh-tekuri/jsonschema/v6"
|
|
)
|
|
|
|
var (
|
|
loadOnce sync.Once
|
|
compiler *jsonschema.Compiler
|
|
compileErr error
|
|
schemaCache sync.Map
|
|
)
|
|
|
|
// ValidateSchema validates instance against the Schema.org JSON Schema named `schemaName`.
|
|
// Examples: ValidateSchema(inst, "Recipe"), ValidateSchema(inst, "schema:Recipe").
|
|
func ValidateSchema(instance any, schemaName string) error {
|
|
if err := ensureCompiler(); err != nil {
|
|
return err
|
|
}
|
|
|
|
ref := normalizeRef(schemaName)
|
|
|
|
if v, ok := schemaCache.Load(ref); ok {
|
|
if err := v.(*jsonschema.Schema).Validate(instance); err != nil {
|
|
return fmt.Errorf("validation failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
sch, err := compiler.Compile(ref)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to compile schema %q: %w", ref, err)
|
|
}
|
|
schemaCache.Store(ref, sch)
|
|
|
|
if err := sch.Validate(instance); err != nil {
|
|
return fmt.Errorf("validation failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ensureCompiler() error {
|
|
loadOnce.Do(func() {
|
|
c := jsonschema.NewCompiler()
|
|
|
|
rawSchemas, err := registry.GetSchemas()
|
|
if err != nil {
|
|
compileErr = fmt.Errorf("read schema directory: %w", err)
|
|
return
|
|
}
|
|
|
|
for _, rawSchema := range rawSchemas {
|
|
js, err := jsonschema.UnmarshalJSON(bytes.NewReader(rawSchema))
|
|
if err != nil {
|
|
compileErr = err
|
|
return
|
|
}
|
|
|
|
id := extractID(rawSchema)
|
|
if err := c.AddResource(id, js); err != nil {
|
|
compileErr = fmt.Errorf("add resource %s: %w", id, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
compiler = c
|
|
})
|
|
return compileErr
|
|
}
|
|
|
|
func extractID(raw []byte) string {
|
|
var tmp struct {
|
|
ID string `json:"$id"`
|
|
}
|
|
_ = json.Unmarshal(raw, &tmp)
|
|
return strings.TrimSpace(tmp.ID)
|
|
}
|
|
|
|
func normalizeRef(name string) string {
|
|
n := strings.TrimSpace(name)
|
|
if strings.HasPrefix(n, "schema:") || strings.HasPrefix(n, "http://") || strings.HasPrefix(n, "https://") {
|
|
return n
|
|
}
|
|
return "schema:" + n
|
|
}
|