// 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 }