3
0
Tomaž Jerman f661206a79 Tweak envoy config structs
Have each node define it's own configs where the decoder may
define the wanted values and the global config populates missing
values in the Bake step.
2023-03-17 10:58:46 +01:00

303 lines
6.4 KiB
Go

package envoyx
import (
"strconv"
"github.com/spf13/cast"
)
type (
// Node is a wrapper around a Corteza resource for use within Envoy
Node struct {
Resource resource
Datasource Datasource
ResourceType string
Identifiers Identifiers
References map[string]Ref
Scope Scope
// Placeholders are resources which were added to help resolve missing deps
Placeholder bool
Config EnvoyConfig
}
EnvoyConfig struct {
MergeAlg mergeAlg
SkipIf string
}
NodeSet []*Node
resource interface {
SetValue(name string, pos uint, value any) error
GetValue(name string, pos uint) (any, error)
GetID() uint64
}
Identifiers struct {
Slice []string
Index map[string]bool
}
// Scope lets us group nodes based on some common context
//
// Scope is primarily used to scope low-code applications to denote to what
// namespace a specific module reference belongs to.
// In the previous version this was referred to as reference constraints;
// This is the same but different.
//
// When constructing dependency graphs, nodes with the same scope are grouped together.
// Nodes from the same scope can reference each other.
// Nodes from a defined scope can reference nodes from an undefined scope,
// but not the other way around.
Scope struct {
ResourceType string
Identifiers Identifiers
}
// Ref defines a reference to a different resource
//
// The reference only holds if all three parts match -- the resource type,
// there is an intersection between the identifiers, and the scope matches.
Ref struct {
ResourceType string
Identifiers Identifiers
Scope Scope
// @todo consider replacing with something that indicates
// it can't be fetched from the DB
Optional bool
}
)
// MakeIdentifiers initializes an Identifiers instance from the given slice
func MakeIdentifiers(ii ...any) (out Identifiers) {
return Identifiers{}.Add(ii...)
}
// NodesByResourceType returns Nodes grouped by their resource type
func NodesByResourceType(nn ...*Node) (out map[string]NodeSet) {
out = make(map[string]NodeSet, len(nn)/2)
for _, n := range nn {
out[n.ResourceType] = append(out[n.ResourceType], n)
}
return
}
// NodesForResourceType returns which belong to the given resource type
func NodesForResourceType(rt string, nn ...*Node) (out NodeSet) {
return NodesByResourceType(nn...)[rt]
}
// NodeForRef returns the Node that matches the given ref
func NodeForRef(ref Ref, nn ...*Node) (out *Node) {
for _, n := range nn {
if !n.Scope.Equals(ref.Scope) {
continue
}
if n.ResourceType != ref.ResourceType {
continue
}
if n.Identifiers.HasIntersection(ref.Identifiers) {
return n
}
}
return
}
func OmitPlaceholderNodes(nn ...*Node) (out NodeSet) {
out = make(NodeSet, 0, len(nn))
for _, n := range nn {
if n.Placeholder {
continue
}
out = append(out, n)
}
return
}
func MergeRefs(a, b map[string]Ref) (c map[string]Ref) {
c = make(map[string]Ref)
for k, v := range a {
c[k] = v
}
for k, v := range b {
c[k] = v
}
return
}
// MergeIdents merges the two identifiers and returns a new one
func MergeIdents(a, b Identifiers) (cc Identifiers) {
cc = Identifiers{
Index: make(map[string]bool, 2),
}
for a := range a.Index {
cc.Index[a] = true
}
for b := range b.Index {
cc.Index[b] = true
}
for c := range cc.Index {
cc.Slice = append(cc.Slice, c)
}
return
}
func (n Node) ToRef() Ref {
return Ref{
ResourceType: n.ResourceType,
Identifiers: n.Identifiers,
Scope: n.Scope,
}
}
func (r Ref) Idents() (ints []uint64, rest []string) {
return r.Identifiers.Idents()
}
// ResourceFilter returns a filter which would match the referenced resource
func (r Ref) ResourceFilter() (out map[string]ResourceFilter) {
out = make(map[string]ResourceFilter)
out[r.ResourceType] = ResourceFilter{
Identifiers: r.Identifiers,
Scope: r.Scope,
// A ref would point to a single resource.
// Don't set the limit so we can error out on ambiguity.
// Limit: 1,
}
return
}
func (a Scope) Equals(b Scope) bool {
if a.IsEmpty() && b.IsEmpty() {
return true
}
if a.ResourceType != b.ResourceType {
return false
}
return a.Identifiers.HasIntersection(b.Identifiers)
}
func (s Scope) IsEmpty() bool {
return s.ResourceType == "" && len(s.Identifiers.Slice) == 0
}
// Add adds the given values to the identifier
func (ii Identifiers) Add(vv ...any) (out Identifiers) {
if ii.Index == nil {
ii.Index = make(map[string]bool, len(vv))
ii.Slice = make([]string, 0, len(vv))
}
for _, v := range vv {
switch casted := v.(type) {
case string:
if casted == "" {
continue
}
case uint64, uint, int, int64:
if casted == 0 {
continue
}
}
if c, ok := v.(Identifiers); ok {
ii = ii.Merge(c)
continue
}
aux := cast.ToString(v)
if aux == "" || aux == "0" {
continue
}
ii.Slice = append(ii.Slice, aux)
ii.Index[aux] = true
}
return ii
}
func (ii Identifiers) IdentsAsStrings() (ids, rest []string) {
aux, rest := ii.Idents()
for _, a := range aux {
ids = append(ids, strconv.FormatUint(a, 10))
}
return
}
// Idents returns a slice of numeric and text identifiers
func (ii Identifiers) Idents() (ints []uint64, rest []string) {
var aux uint64
var err error
for _, i := range ii.Slice {
aux, err = cast.ToUint64E(i)
if err != nil {
rest = append(rest, i)
} else {
ints = append(ints, aux)
}
}
return
}
// Intersection returns a slice of identifiers which are in an intersection
func (aa Identifiers) Intersection(bb Identifiers) (out []string) {
for _, b := range bb.Slice {
if aa.Index[b] {
out = append(out, b)
}
}
return
}
// HasIntersection returns true if the two identifiers define an intersection
func (aa Identifiers) HasIntersection(bb Identifiers) bool {
if len(aa.Slice) == 0 && len(bb.Slice) == 0 {
return true
}
return len(aa.Intersection(bb)) > 0
}
// Merge merges the two identifiers and returns a new one
// @todo deprecate this; use MergeIdents instead
func (aa Identifiers) Merge(bb Identifiers) (cc Identifiers) {
return MergeIdents(aa, bb)
}
// FriendlyIdentifier returns the best available identifier
//
// If any non-ID identifiers are available, it uses the first one.
// If no non-ID identifiers are available, it returns the first ID.
func (ii Identifiers) FriendlyIdentifier() (out string) {
a, b := ii.Idents()
if len(b) > 0 {
return b[0]
}
if len(a) > 0 {
return strconv.FormatUint(a[0], 10)
}
return
}