3
0
corteza/pkg/dal/model.go
Tomaž Jerman b85f16a368 Add slight structure rename, add supporting services
* Add system connection CRUD & service
* Simplify compose type definitions
2022-05-20 15:37:33 +02:00

177 lines
3.6 KiB
Go

package dal
import (
"fmt"
"strings"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/modern-go/reflect2"
)
type (
// ModelFilter is used to retrieve a model from the DAL based on given params
ModelFilter struct {
ConnectionID uint64
ResourceID uint64
ResourceType string
Resource string
}
// Model describes the underlying data and its shape
Model struct {
ConnectionID uint64
Ident string
Resource string
ResourceID uint64
ResourceType string
Attributes AttributeSet
}
ModelSet []*Model
// Attribute describes a specific value of the dataset
Attribute struct {
Ident string
MultiValue bool
PrimaryKey bool
// If attribute has SoftDeleteFlag=true we use it
// when filtering out deleted items
SoftDeleteFlag bool
// Is attribute sortable?
// Note: all primary keys are sortable
Sortable bool
// Can attribute be used in query expression?
Filterable bool
// Store describes the strategy the underlying storage system should
// apply to the underlying value
Store Codec
// Type describes what the value represents and how it should be
// encoded/decoded
Type Type
}
AttributeSet []*Attribute
)
func PrimaryAttribute(ident string, codec Codec) *Attribute {
out := FullAttribute(ident, TypeID{}, codec)
out.Type = &TypeID{}
out.PrimaryKey = true
return out
}
func FullAttribute(ident string, at Type, codec Codec) *Attribute {
return &Attribute{
Ident: ident,
Sortable: true,
Filterable: true,
Store: codec,
Type: at,
}
}
func (a *Attribute) WithSoftDelete() *Attribute {
a.SoftDeleteFlag = true
return a
}
func (a *Attribute) WithMultiValue() *Attribute {
a.MultiValue = true
return a
}
// FindByResource returns the model that matches the resource
func (mm ModelSet) FindByResource(resType string, resource string) *Model {
for _, m := range mm {
if m.ResourceType == resType && m.Resource == resource {
return m
}
}
return nil
}
func (mm ModelSet) FindByID(id uint64) *Model {
for _, m := range mm {
if m.ResourceID == id {
return m
}
}
return nil
}
// FilterByReferenced returns all of the models that reference b
func (aa ModelSet) FilterByReferenced(b *Model) (out ModelSet) {
for _, aModel := range aa {
if aModel.Resource == b.Resource {
continue
}
for _, aAttribute := range aModel.Attributes {
switch casted := aAttribute.Type.(type) {
case *TypeRef:
if casted.RefModel.Resource == b.Resource {
out = append(out, aModel)
}
}
}
}
return
}
// HasAttribute returns true when the model includes the specified attribute
func (m Model) HasAttribute(ident string) bool {
return m.Attributes.FindByIdent(ident) != nil
}
func (aa AttributeSet) FindByIdent(ident string) *Attribute {
for _, a := range aa {
if strings.EqualFold(a.Ident, ident) {
return a
}
}
return nil
}
// Validate performs a base model validation before it is passed down
func (m Model) Validate() error {
if m.Resource == "" {
return fmt.Errorf("resource not defined")
}
seen := make(map[string]bool)
for _, attr := range m.Attributes {
if attr.Ident == "" {
return fmt.Errorf("invalid attribute ident: ident must not be empty")
}
if !handle.IsValid(attr.Ident) {
return fmt.Errorf("invalid attribute ident: %s is not a valid handle", attr.Ident)
}
if seen[attr.Ident] {
return fmt.Errorf("invalid attribute %s: duplicate attributes are not allowed", attr.Ident)
}
seen[attr.Ident] = true
if reflect2.IsNil(attr.Type) {
return fmt.Errorf("attribute does not define a type: %s", attr.Ident)
}
}
return nil
}