/
registry.go
151 lines (137 loc) · 4.43 KB
/
registry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package validator
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/frictionlessdata/datapackage-go/validator/profile_cache"
"github.com/santhosh-tekuri/jsonschema"
_ "github.com/santhosh-tekuri/jsonschema/httploader" // This import alows jsonschema to load urls.
_ "github.com/santhosh-tekuri/jsonschema/loader" // This import alows jsonschema to load filepaths.
)
// RegistryLoader loads a registry.
type RegistryLoader func() (Registry, error)
// Registry represents a set of registered validators, which could be loaded locally or remotelly.
type Registry interface {
GetValidator(profile string) (DescriptorValidator, error)
}
type profileSpec struct {
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Schema string `json:"schema,omitempty"`
SchemaPath string `json:"schema_path,omitempty"`
Specification string `json:"specification,omitempty"`
}
type localRegistry struct {
registry map[string]profileSpec
inMemoryOnly bool
}
func (local *localRegistry) GetValidator(profile string) (DescriptorValidator, error) {
spec, ok := local.registry[profile]
if !ok {
return nil, fmt.Errorf("invalid profile:%s", profile)
}
b, err := profile_cache.FSByte(!local.inMemoryOnly, spec.Schema)
if err != nil {
return nil, err
}
c := jsonschema.NewCompiler()
c.AddResource(profile, bytes.NewReader(b)) // Adding in-memory resource.
schema, err := c.Compile(profile)
if err != nil {
return nil, err
}
return &jsonSchema{schema: schema}, nil
}
// LocalRegistryLoader creates a new registry, which is based on the local file system (or in-memory cache)
// to locate json schema profiles. Setting inMemoryOnly to true will make sure only the in-memory
// cache (registry_cache Go package) is accessed, thus avoiding access the filesystem.
func LocalRegistryLoader(localRegistryPath string, inMemoryOnly bool) RegistryLoader {
return func() (Registry, error) {
buf, err := profile_cache.FSByte(!inMemoryOnly, localRegistryPath)
if err != nil {
return nil, err
}
m, err := unmarshalRegistryContents(buf)
if err != nil {
return nil, err
}
return &localRegistry{registry: m, inMemoryOnly: inMemoryOnly}, nil
}
}
type remoteRegistry struct {
registry map[string]profileSpec
}
func (remote *remoteRegistry) GetValidator(profile string) (DescriptorValidator, error) {
spec, ok := remote.registry[profile]
if !ok {
return nil, fmt.Errorf("invalid profile:%s", profile)
}
c := jsonschema.NewCompiler()
c.AddResource(profile, strings.NewReader(spec.Schema)) // Adding in-memory resource.
schema, err := c.Compile(spec.Schema)
if err != nil {
return nil, err
}
return &jsonSchema{schema: schema}, nil
}
// RemoteRegistryLoader loads the schema registry map from the passed-in URL.
func RemoteRegistryLoader(url string) RegistryLoader {
return func() (Registry, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("error fetching remote profile cache registry from %s: %q", url, err)
}
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading remote profile cache registry from %s: %q", url, err)
}
m, err := unmarshalRegistryContents(buf)
if err != nil {
return nil, err
}
return &remoteRegistry{registry: m}, nil
}
}
// FallbackRegistryLoader returns the first passed-in registry loaded successfully.
// It returns an error if there is no successfully loaded registry.
func FallbackRegistryLoader(loaders ...RegistryLoader) RegistryLoader {
return func() (Registry, error) {
if len(loaders) == 0 {
return nil, fmt.Errorf("there should be at least one registry loader to fallback")
}
var registry Registry
var errors []error
for _, loader := range loaders {
reg, err := loader()
if err != nil {
errors = append(errors, err)
continue
}
registry = reg
break
}
if registry == nil {
var erroMsg string
for _, err := range errors {
erroMsg += fmt.Sprintln(err.Error())
}
return nil, fmt.Errorf(erroMsg)
}
return registry, nil
}
}
func unmarshalRegistryContents(buf []byte) (map[string]profileSpec, error) {
var specs []profileSpec
if err := json.Unmarshal(buf, &specs); err != nil {
return nil, fmt.Errorf("error parsing profile cache registry. Contents:\"%s\". Err:\"%q\"", string(buf), err)
}
m := make(map[string]profileSpec, len(specs))
for _, s := range specs {
m[s.ID] = s
}
return m, nil
}