132 lines
4.2 KiB
Go
132 lines
4.2 KiB
Go
package contract
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.ipao.vip/rogee/go-sip/contracts"
|
|
"github.com/santhosh-tekuri/jsonschema/v6"
|
|
)
|
|
|
|
var compiledSchemas sync.Map
|
|
|
|
type bundleLoader struct{ version string }
|
|
|
|
func (l bundleLoader) base() string {
|
|
return "https://go-sip.local/contracts/" + l.version + "/"
|
|
}
|
|
|
|
// Load never accesses the network or resolves references against another bundle.
|
|
func (l bundleLoader) Load(address string) (any, error) {
|
|
if !strings.HasPrefix(address, l.base()) {
|
|
return nil, fmt.Errorf("schema reference outside pinned bundle: %s", address)
|
|
}
|
|
name := strings.TrimPrefix(address, l.base())
|
|
if name == "" || path.Base(name) != name || !strings.HasSuffix(name, ".schema.json") {
|
|
return nil, fmt.Errorf("invalid schema resource name %q", name)
|
|
}
|
|
raw, err := contracts.Files.ReadFile("upstream/" + l.version + "/" + name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read pinned schema %s: %w", name, err)
|
|
}
|
|
return jsonschema.UnmarshalJSON(bytes.NewReader(raw))
|
|
}
|
|
|
|
const projectLocalSchemaBase = "https://go-sip.local/contracts/proposals/"
|
|
|
|
type projectBundleLoader struct{}
|
|
|
|
func (projectBundleLoader) Load(address string) (any, error) {
|
|
upstream := bundleLoader{version: contracts.SourceCommit}
|
|
if strings.HasPrefix(address, upstream.base()) {
|
|
return upstream.Load(address)
|
|
}
|
|
if !strings.HasPrefix(address, projectLocalSchemaBase) {
|
|
return nil, fmt.Errorf("schema reference outside pinned project bundles: %s", address)
|
|
}
|
|
name := strings.TrimPrefix(address, projectLocalSchemaBase)
|
|
if name == "" || path.Base(name) != name || !strings.HasSuffix(name, ".schema.json") {
|
|
return nil, fmt.Errorf("invalid project-local schema resource %q", name)
|
|
}
|
|
raw, err := contracts.ReadLocal(localSchemaVersion(name), name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read project-local schema %s: %w", name, err)
|
|
}
|
|
return jsonschema.UnmarshalJSON(bytes.NewReader(raw))
|
|
}
|
|
|
|
func localSchemaVersion(name string) string {
|
|
switch name {
|
|
case "config-read-v0.1.schema.json", "command-next-v0.1-proposal.schema.json", "call-result-v0.1-proposal.schema.json", "local-mock-recording-failure-v0.1.schema.json":
|
|
return "v0.1"
|
|
case "config-read-v0.2.schema.json":
|
|
return "v0.2"
|
|
case "task-discovery-v0.3-proposal.schema.json":
|
|
return "v0.3"
|
|
case "task-discovery-v0.4-proposal.schema.json", "task-control-v0.4-proposal.schema.json", "call-execute-v0.4-proposal.schema.json":
|
|
return "v0.4"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func localSchemaFromBundle(name string) (*jsonschema.Schema, error) {
|
|
version := localSchemaVersion(name)
|
|
if version == "" {
|
|
return nil, fmt.Errorf("unknown project-local schema %q", name)
|
|
}
|
|
key := "local/" + version + "/" + name
|
|
if cached, ok := compiledSchemas.Load(key); ok {
|
|
return cached.(*jsonschema.Schema), nil
|
|
}
|
|
raw, err := contracts.ReadLocal(version, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
doc, err := jsonschema.UnmarshalJSON(bytes.NewReader(raw))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode project-local schema %s: %w", name, err)
|
|
}
|
|
resource := projectLocalSchemaBase + name
|
|
compiler := jsonschema.NewCompiler()
|
|
compiler.UseLoader(projectBundleLoader{})
|
|
compiler.AssertFormat()
|
|
if err := compiler.AddResource(resource, doc); err != nil {
|
|
return nil, fmt.Errorf("register project-local schema %s: %w", name, err)
|
|
}
|
|
schema, err := compiler.Compile(resource)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("compile project-local schema %s: %w", name, err)
|
|
}
|
|
actual, _ := compiledSchemas.LoadOrStore(key, schema)
|
|
return actual.(*jsonschema.Schema), nil
|
|
}
|
|
|
|
func schemaFromBundle(version, name string) (*jsonschema.Schema, error) {
|
|
key := version + "/" + name
|
|
if cached, ok := compiledSchemas.Load(key); ok {
|
|
return cached.(*jsonschema.Schema), nil
|
|
}
|
|
loader := bundleLoader{version: version}
|
|
resource := loader.base() + name
|
|
doc, err := loader.Load(resource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
compiler := jsonschema.NewCompiler()
|
|
compiler.UseLoader(loader)
|
|
compiler.AssertFormat()
|
|
if err := compiler.AddResource(resource, doc); err != nil {
|
|
return nil, fmt.Errorf("register %s: %w", name, err)
|
|
}
|
|
schema, err := compiler.Compile(resource)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("compile %s: %w", name, err)
|
|
}
|
|
actual, _ := compiledSchemas.LoadOrStore(key, schema)
|
|
return actual.(*jsonschema.Schema), nil
|
|
}
|