feat: cli client with spec schema validation
This commit is contained in:
17
internal/spec/gateway/init.go
Normal file
17
internal/spec/gateway/init.go
Normal file
@ -0,0 +1,17 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
//go:embed schema.json
|
||||
var schema []byte
|
||||
|
||||
func init() {
|
||||
if err := spec.Register(NameGateway, schema); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}
|
29
internal/spec/gateway/schema.json
Normal file
29
internal/spec/gateway/schema.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://gateway.emissary.cadoles.com/spec.json",
|
||||
"title": "GatewaySpec",
|
||||
"description": "Emissary 'Gateway' specification",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gateways": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string"
|
||||
},
|
||||
"target": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["address", "target"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["gateways"],
|
||||
"additionalProperties": false
|
||||
}
|
37
internal/spec/gateway/spec.go
Normal file
37
internal/spec/gateway/spec.go
Normal file
@ -0,0 +1,37 @@
|
||||
package gateway
|
||||
|
||||
import "forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
|
||||
const NameGateway spec.Name = "gateway.emissary.cadoles.com"
|
||||
|
||||
type ID string
|
||||
|
||||
type Spec struct {
|
||||
Revision int `json:"revision"`
|
||||
Gateways map[ID]GatewayEntry `json:"gateways"`
|
||||
}
|
||||
|
||||
type GatewayEntry struct {
|
||||
Address string `json:"address"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
func (s *Spec) SpecName() spec.Name {
|
||||
return NameGateway
|
||||
}
|
||||
|
||||
func (s *Spec) SpecRevision() int {
|
||||
return s.Revision
|
||||
}
|
||||
|
||||
func (s *Spec) SpecData() any {
|
||||
return struct {
|
||||
Gateways map[ID]GatewayEntry
|
||||
}{Gateways: s.Gateways}
|
||||
}
|
||||
|
||||
func NewSpec() *Spec {
|
||||
return &Spec{
|
||||
Gateways: make(map[ID]GatewayEntry),
|
||||
}
|
||||
}
|
13
internal/spec/gateway/testdata/spec-additional-prop.json
vendored
Normal file
13
internal/spec/gateway/testdata/spec-additional-prop.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "gateway.emissary.cadoles.com",
|
||||
"data": {
|
||||
"gateways": {
|
||||
"cadoles.com": {
|
||||
"address": ":3003",
|
||||
"target": "https://www.cadoles.com",
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"revision": 0
|
||||
}
|
11
internal/spec/gateway/testdata/spec-missing-prop.json
vendored
Normal file
11
internal/spec/gateway/testdata/spec-missing-prop.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "gateway.emissary.cadoles.com",
|
||||
"data": {
|
||||
"gateways": {
|
||||
"cadoles.com": {
|
||||
"address": ":3003"
|
||||
}
|
||||
}
|
||||
},
|
||||
"revision": 0
|
||||
}
|
12
internal/spec/gateway/testdata/spec-ok.json
vendored
Normal file
12
internal/spec/gateway/testdata/spec-ok.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "gateway.emissary.cadoles.com",
|
||||
"data": {
|
||||
"gateways": {
|
||||
"cadoles.com": {
|
||||
"address": ":3003",
|
||||
"target": "https://www.cadoles.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"revision": 0
|
||||
}
|
75
internal/spec/gateway/validator_test.go
Normal file
75
internal/spec/gateway/validator_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type validatorTestCase struct {
|
||||
Name string
|
||||
Source string
|
||||
ExpectedResult bool
|
||||
}
|
||||
|
||||
var validatorTestCases = []validatorTestCase{
|
||||
{
|
||||
Name: "SpecOK",
|
||||
Source: "testdata/spec-ok.json",
|
||||
ExpectedResult: true,
|
||||
},
|
||||
{
|
||||
Name: "SpecMissingProp",
|
||||
Source: "testdata/spec-missing-prop.json",
|
||||
ExpectedResult: false,
|
||||
},
|
||||
{
|
||||
Name: "SpecAdditionalProp",
|
||||
Source: "testdata/spec-additional-prop.json",
|
||||
ExpectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestValidator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validator := spec.NewValidator()
|
||||
if err := validator.Register(NameGateway, schema); err != nil {
|
||||
t.Fatalf("+%v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
for _, tc := range validatorTestCases {
|
||||
func(tc *validatorTestCase) {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rawSpec, err := ioutil.ReadFile(tc.Source)
|
||||
if err != nil {
|
||||
t.Fatalf("+%v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
var spec spec.RawSpec
|
||||
|
||||
if err := json.Unmarshal(rawSpec, &spec); err != nil {
|
||||
t.Fatalf("+%v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
result, err := validator.Validate(ctx, &spec)
|
||||
|
||||
if e, g := tc.ExpectedResult, result; e != g {
|
||||
t.Errorf("result: expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
if tc.ExpectedResult && err != nil {
|
||||
t.Errorf("+%v", errors.WithStack(err))
|
||||
}
|
||||
})
|
||||
}(&tc)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user