3
0
2023-03-17 10:58:46 +01:00

284 lines
5.8 KiB
Go

package envoyx
import (
"context"
"fmt"
"strings"
"github.com/cortezaproject/corteza/server/pkg/expr"
)
type (
service struct {
decoders map[decodeType][]Decoder
encoders map[encodeType][]Encoder
preparers map[encodeType][]Preparer
}
// Traverser provides a structure which can be used to traverse the node's deps
Traverser interface {
// ParentForRef returns the parent of the provided node which matches the ref
//
// If no parent is found, nil is returned.
ParentForRef(*Node, Ref) *Node
// ChildrenForResourceType returns the children of the provided node which
// match the provided resource type
ChildrenForResourceType(*Node, string) NodeSet
// Children returns all of the children of the provided node
Children(*Node) NodeSet
}
Preparer interface {
// Prepare performs generic preprocessing on the provided nodes
//
// The function is called for every resource type where all of the nodes of
// that resource type are passed as the argument.
Prepare(context.Context, EncodeParams, string, NodeSet) error
}
Encoder interface {
// Encode encodes the data
//
// The function receives a set of root-level nodes (with no parent dependencies)
// and a Traverser it can use to handle all of the child nodes.
Encode(context.Context, EncodeParams, string, NodeSet, Traverser) (err error)
}
PrepareEncoder interface {
Preparer
Encoder
}
Decoder interface {
// Decode returns a set of Nodes extracted based on the provided definition
Decode(ctx context.Context, p DecodeParams) (out NodeSet, err error)
}
DecodeParams struct {
Type decodeType
Params map[string]any
Config DecoderConfig
Filter map[string]ResourceFilter
}
DecoderConfig struct{}
EncodeParams struct {
Type encodeType
Params map[string]any
Envoy EnvoyConfig
Encoder EncoderConfig
}
EncoderConfig struct {
PreferredTimeLayout string
PreferredTimezone string
}
ResourceFilter struct {
Identifiers Identifiers
Refs map[string]Ref
Limit uint
Scope Scope
}
decodeType string
encodeType string
mergeAlg int
)
var (
global *service
ex = expr.NewParser()
)
const (
OnConflictDefault mergeAlg = iota
OnConflictReplace
OnConflictSkip
OnConflictPanic
// OnConflictMergeLeft mergeAlg = "mergeLeft"
// OnConflictMergeRight mergeAlg = "mergeRight"
DecodeTypeURI decodeType = "uri"
DecodeTypeStore decodeType = "store"
EncodeTypeURI encodeType = "uri"
EncodeTypeStore encodeType = "store"
EncodeTypeIo encodeType = "io"
)
// New initializes a new Envoy service
func New() *service {
return &service{}
}
// SetGlobal sets the global envoy service
func SetGlobal(n *service) {
global = n
}
// Service gets the global envoy service
func Service() *service {
if global == nil {
panic("global service not defined")
}
return global
}
// Decode returns a set of envoy Nodes based on the given decode params
func (svc *service) Decode(ctx context.Context, p DecodeParams) (nn NodeSet, err error) {
err = p.validate()
if err != nil {
return
}
switch p.Type {
case DecodeTypeURI:
return svc.decodeUri(ctx, p)
case DecodeTypeStore:
return svc.decodeStore(ctx, p)
default:
err = fmt.Errorf("unsupported decoder type %s", p.Type)
}
return
}
func (svc *service) Bake(ctx context.Context, p EncodeParams, nodes ...*Node) (err error) {
err = svc.bakeEnvoyConfig(p.Envoy, nodes...)
if err != nil {
return
}
err = svc.bakeExpressions(nodes...)
return
}
// Encode encodes Corteza resources bases on the provided encode params
//
// use the BuildDepGraph function to build the default dependency graph.
func (svc *service) Encode(ctx context.Context, p EncodeParams, dg *depGraph) (err error) {
err = p.validate()
if err != nil {
return
}
switch p.Type {
case EncodeTypeStore:
return svc.encodeStore(ctx, dg, p)
case EncodeTypeIo:
return svc.encodeIo(ctx, dg, p)
default:
err = fmt.Errorf("unsupported encoder type %s", p.Type)
}
return
}
func (svc *service) AddDecoder(t decodeType, dd ...Decoder) {
if svc.decoders == nil {
svc.decoders = make(map[decodeType][]Decoder)
}
svc.decoders[t] = append(svc.decoders[t], dd...)
}
func (svc *service) AddEncoder(t encodeType, ee ...Encoder) {
if svc.encoders == nil {
svc.encoders = make(map[encodeType][]Encoder)
}
svc.encoders[t] = append(svc.encoders[t], ee...)
}
func (svc *service) AddPreparer(t encodeType, pp ...Preparer) {
if svc.preparers == nil {
svc.preparers = make(map[encodeType][]Preparer)
}
svc.preparers[t] = append(svc.preparers[t], pp...)
}
// Utility functions
func (p DecodeParams) validate() (err error) {
switch p.Type {
case DecodeTypeURI:
_, ok := p.Params["uri"]
if !ok {
return fmt.Errorf("uhoh, no uri provided")
}
case DecodeTypeStore:
}
// @todo...
return
}
func (p EncodeParams) validate() (err error) {
switch p.Type {
// @todo...
}
// @todo...
return
}
func CastMergeAlg(v string) (mergeAlg mergeAlg) {
switch strings.ToLower(v) {
case "replace", "mergeleft":
mergeAlg = OnConflictReplace
case "skip", "mergeright":
mergeAlg = OnConflictSkip
case "panic", "error":
mergeAlg = OnConflictPanic
}
return
}
func (svc *service) bakeEnvoyConfig(dft EnvoyConfig, nodes ...*Node) (err error) {
for _, n := range nodes {
n.Config = svc.mergeEnvoyConfigs(n.Config, dft)
}
return
}
func (svc *service) bakeExpressions(nodes ...*Node) (err error) {
for _, n := range nodes {
if n.Config.SkipIf == "" {
continue
}
n.Config.SkipIfEval, err = ex.Parse(n.Config.SkipIf)
if err != nil {
return
}
}
return
}
func (svc *service) mergeEnvoyConfigs(a, b EnvoyConfig) (c EnvoyConfig) {
c = a
if c.MergeAlg == OnConflictDefault {
c.MergeAlg = b.MergeAlg
}
if c.MergeAlg == OnConflictDefault {
c.MergeAlg = OnConflictReplace
}
// @todo pre eval this?
if c.SkipIf == "" {
c.SkipIf = b.SkipIf
}
return
}