feat: add spec definition api with versioning
This commit is contained in:
@ -6,7 +6,11 @@ import (
|
||||
)
|
||||
|
||||
func Equals(a Spec, b Spec) (bool, error) {
|
||||
if a.SpecName() != b.SpecName() {
|
||||
if a.SpecDefinitionName() != b.SpecDefinitionName() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if a.SpecDefinitionVersion() != b.SpecDefinitionVersion() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
package spec
|
||||
|
||||
type Name string
|
@ -11,7 +11,7 @@ import (
|
||||
var schema []byte
|
||||
|
||||
func init() {
|
||||
if err := spec.Register(NameProxy, schema); err != nil {
|
||||
if err := spec.Register(string(Name), Version, schema); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||
"$id": "https://proxy.emissary.cadoles.com/spec.json",
|
||||
"title": "ProxySpec",
|
||||
"description": "Emissary 'Proxy' specification",
|
||||
@ -26,16 +26,24 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["hostPattern", "target"]
|
||||
"required": [
|
||||
"hostPattern",
|
||||
"target"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["address", "mappings"],
|
||||
"required": [
|
||||
"address",
|
||||
"mappings"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["proxies"],
|
||||
"required": [
|
||||
"proxies"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
@ -2,7 +2,10 @@ package proxy
|
||||
|
||||
import "forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
|
||||
const NameProxy spec.Name = "proxy.emissary.cadoles.com"
|
||||
const (
|
||||
Name string = "proxy.emissary.cadoles.com"
|
||||
Version = "0.0.0"
|
||||
)
|
||||
|
||||
type ID string
|
||||
|
||||
@ -21,8 +24,12 @@ type ProxyMapping struct {
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
func (s *Spec) SpecName() spec.Name {
|
||||
return NameProxy
|
||||
func (s *Spec) SpecDefinitionName() string {
|
||||
return Name
|
||||
}
|
||||
|
||||
func (s *Spec) SpecDefinitionVersion() string {
|
||||
return Version
|
||||
}
|
||||
|
||||
func (s *Spec) SpecRevision() int {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore/memory"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -37,11 +38,15 @@ var validatorTestCases = []validatorTestCase{
|
||||
func TestValidator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validator := spec.NewValidator()
|
||||
if err := validator.Register(NameProxy, schema); err != nil {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := memory.NewSpecDefinitionRepository()
|
||||
if _, err := repo.Upsert(ctx, Name, Version, schema); err != nil {
|
||||
t.Fatalf("+%v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
validator := spec.NewValidator(repo)
|
||||
|
||||
for _, tc := range validatorTestCases {
|
||||
func(tc validatorTestCase) {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
|
66
internal/spec/registry.go
Normal file
66
internal/spec/registry.go
Normal file
@ -0,0 +1,66 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/qri-io/jsonschema"
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
definitions map[string]map[string][]byte
|
||||
}
|
||||
|
||||
func (r *Registry) Register(name string, version string, schema []byte) error {
|
||||
// Assert that provided schema is valid
|
||||
if err := json.Unmarshal(schema, &jsonschema.Schema{}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
specDefinitions, exists := r.definitions[name]
|
||||
if !exists {
|
||||
specDefinitions = make(map[string][]byte)
|
||||
}
|
||||
|
||||
specDefinitions[version] = schema
|
||||
|
||||
r.definitions[name] = specDefinitions
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Registry) Walk(fn func(name string, version string, schema []byte) error) error {
|
||||
for name, specDefinitions := range r.definitions {
|
||||
for version, schema := range specDefinitions {
|
||||
if err := fn(name, version, schema); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
definitions: make(map[string]map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
var defaultRegistry = NewRegistry()
|
||||
|
||||
func Register(name string, version string, schema []byte) error {
|
||||
if err := defaultRegistry.Register(name, version, schema); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Walk(fn func(name string, version string, schema []byte) error) error {
|
||||
if err := defaultRegistry.Walk(fn); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,19 +1,27 @@
|
||||
package spec
|
||||
|
||||
const DefaultVersion = "0.0.0"
|
||||
|
||||
type Spec interface {
|
||||
SpecName() Name
|
||||
SpecDefinitionName() string
|
||||
SpecDefinitionVersion() string
|
||||
SpecRevision() int
|
||||
SpecData() map[string]any
|
||||
}
|
||||
|
||||
type RawSpec struct {
|
||||
Name Name `json:"name"`
|
||||
Revision int `json:"revision"`
|
||||
Data map[string]any `json:"data"`
|
||||
DefinitionName string `json:"name"`
|
||||
DefinitionVersion string `json:"version"`
|
||||
Revision int `json:"revision"`
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
|
||||
func (s *RawSpec) SpecName() Name {
|
||||
return s.Name
|
||||
func (s *RawSpec) SpecDefinitionName() string {
|
||||
return s.DefinitionName
|
||||
}
|
||||
|
||||
func (s *RawSpec) SpecDefinitionVersion() string {
|
||||
return s.DefinitionVersion
|
||||
}
|
||||
|
||||
func (s *RawSpec) SpecRevision() int {
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
var schema []byte
|
||||
|
||||
func init() {
|
||||
if err := spec.Register(NameUCI, schema); err != nil {
|
||||
if err := spec.Register(string(Name), Version, schema); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||
"$id": "https://uci.emissary.cadoles.com/spec.json",
|
||||
"title": "UCISpec",
|
||||
"description": "Emissary 'UCI' specification",
|
||||
@ -15,7 +15,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["packages"],
|
||||
"required": [
|
||||
"packages"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"postImportCommands": {
|
||||
@ -33,12 +35,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["command", "args"],
|
||||
"required": [
|
||||
"command",
|
||||
"args"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["config", "postImportCommands"],
|
||||
"required": [
|
||||
"config",
|
||||
"postImportCommands"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"$defs": {
|
||||
"package": {
|
||||
@ -54,7 +62,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name", "configs"],
|
||||
"required": [
|
||||
"name",
|
||||
"configs"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"config": {
|
||||
@ -74,11 +85,15 @@
|
||||
"$ref": "#/$defs/option"
|
||||
}
|
||||
},
|
||||
{ "type": "null" }
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"option": {
|
||||
@ -86,7 +101,10 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["list", "option"]
|
||||
"enum": [
|
||||
"list",
|
||||
"option"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@ -95,7 +113,11 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "name", "value"],
|
||||
"required": [
|
||||
"type",
|
||||
"name",
|
||||
"value"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ import (
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
)
|
||||
|
||||
const NameUCI spec.Name = "uci.emissary.cadoles.com"
|
||||
const (
|
||||
Name string = "uci.emissary.cadoles.com"
|
||||
Version string = "0.0.0"
|
||||
)
|
||||
|
||||
type Spec struct {
|
||||
Revision int `json:"revision"`
|
||||
@ -18,8 +21,12 @@ type UCIPostImportCommand struct {
|
||||
Args []string `json:"args"`
|
||||
}
|
||||
|
||||
func (s *Spec) SpecName() spec.Name {
|
||||
return NameUCI
|
||||
func (s *Spec) SpecDefinitionName() string {
|
||||
return Name
|
||||
}
|
||||
|
||||
func (s *Spec) SpecDefinitionVersion() string {
|
||||
return Version
|
||||
}
|
||||
|
||||
func (s *Spec) SpecRevision() int {
|
||||
|
@ -3,9 +3,10 @@ package uci
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore/memory"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -32,17 +33,21 @@ var validatorTestCases = []validatorTestCase{
|
||||
func TestValidator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validator := spec.NewValidator()
|
||||
if err := validator.Register(NameUCI, schema); err != nil {
|
||||
ctx := context.Background()
|
||||
|
||||
repo := memory.NewSpecDefinitionRepository()
|
||||
if _, err := repo.Upsert(ctx, Name, Version, schema); err != nil {
|
||||
t.Fatalf("+%v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
validator := spec.NewValidator(repo)
|
||||
|
||||
for _, tc := range validatorTestCases {
|
||||
func(tc validatorTestCase) {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rawSpec, err := ioutil.ReadFile(tc.Source)
|
||||
rawSpec, err := os.ReadFile(tc.Source)
|
||||
if err != nil {
|
||||
t.Fatalf("+%v", errors.WithStack(err))
|
||||
}
|
||||
|
@ -4,29 +4,35 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/qri-io/jsonschema"
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
schemas map[Name]*jsonschema.Schema
|
||||
}
|
||||
|
||||
func (v *Validator) Register(name Name, rawSchema []byte) error {
|
||||
schema := &jsonschema.Schema{}
|
||||
if err := json.Unmarshal(rawSchema, schema); err != nil {
|
||||
return errors.Wrapf(err, "could not register spec shema '%s'", name)
|
||||
}
|
||||
|
||||
v.schemas[name] = schema
|
||||
|
||||
return nil
|
||||
repo datastore.SpecDefinitionRepository
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(ctx context.Context, spec Spec) error {
|
||||
schema, exists := v.schemas[spec.SpecName()]
|
||||
if !exists {
|
||||
return errors.WithStack(ErrUnknownSchema)
|
||||
name := spec.SpecDefinitionName()
|
||||
|
||||
version := spec.SpecDefinitionVersion()
|
||||
if version == "" {
|
||||
version = DefaultVersion
|
||||
}
|
||||
|
||||
specDef, err := v.repo.Get(ctx, name, version)
|
||||
if err != nil {
|
||||
if errors.Is(err, datastore.ErrNotFound) {
|
||||
return errors.WithStack(ErrUnknownSchema)
|
||||
}
|
||||
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
schema := &jsonschema.Schema{}
|
||||
if err := json.Unmarshal(specDef.Schema, schema); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
state := schema.Validate(ctx, map[string]any(spec.SpecData()))
|
||||
@ -37,26 +43,8 @@ func (v *Validator) Validate(ctx context.Context, spec Spec) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewValidator() *Validator {
|
||||
func NewValidator(repo datastore.SpecDefinitionRepository) *Validator {
|
||||
return &Validator{
|
||||
schemas: make(map[Name]*jsonschema.Schema),
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
var defaultValidator = NewValidator()
|
||||
|
||||
func Register(name Name, rawSchema []byte) error {
|
||||
if err := defaultValidator.Register(name, rawSchema); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Validate(ctx context.Context, spec Spec) error {
|
||||
if err := defaultValidator.Validate(ctx, spec); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user