seed

package module
v0.0.0-...-702b9ba Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 19, 2022 License: BSD-2-Clause Imports: 10 Imported by: 0

README

Seed

A Metadata Driven Architecture

Metadata Describes

  • The core data model (C): The most natural way to structure data.
  • The write model (W): How data enters the system.
  • The read model (R): How the system provide data to users.
  • The periphery descriptors: Hints at the shape of the data.

Principals

  • There is always a standard. Used it.
  • Separate goals and implementations.

Decoupling

Code Generation

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FieldTypeSettingCover

func FieldTypeSettingCover(s, s2 FieldTypeSetting) bool

func GetFieldTypeSetting

func GetFieldTypeSetting[T FieldTypeSetting](f *Field) (T, error)

func NewFields

func NewFields[T ThingGetter](fs ...T) (*dictionary.SelfKeyed[CodeName, T], error)

NewFields build a self keyed dictionary with field rules, such as FieldGroup.Fields It errors if values added violate field naming rules.

func NewFields0

func NewFields0[T ThingGetter]() *dictionary.SelfKeyed[CodeName, T]

NewFields0 is the zero argument version of NewFields, it also does not error

func NewObjects

func NewObjects[T ObjectGetter](objs ...T) (*dictionary.SelfKeyed[CodeName, T], error)

func NewObjects0

func NewObjects0[T ThingGetter]() *dictionary.SelfKeyed[CodeName, T]

NewObjects0 is the zero argument version of NewObjects, it also does not error

func Pick

func Pick[T any](picker *Picker, values I18nGetter[T]) (language.Tag, bool)

Pick picks the best value from I18n by the picker's preference. If no languages matched, then (und, false) is returned.

func RangeRanges

func RangeRanges(g FieldGroupGetter, f func(r Range) error) error

Types

type BinarySetting

type BinarySetting struct {
	MinBytes int64
	MaxBytes int64
}

func (BinarySetting) Covers

func (s BinarySetting) Covers(s2 BinarySetting) bool

Covers returns true if s can support all values in s2

type BooleanSetting

type BooleanSetting struct{}

func (BooleanSetting) Covers

func (s BooleanSetting) Covers(s2 BooleanSetting) bool

Covers returns true if s can support all values in s2

type CodeName

type CodeName string

CodeName marks a string as expected to follow seed naming rules. See ./dictionary for details.

type CombinationSetting

type CombinationSetting = FieldGroup // Reuse FieldGroup

type Condition

type Condition struct {
	Op         Op // condition operator
	Children   []Condition
	FieldPaths []Path
	Literal    any // if null, the literal is skipped
}

Condition represents boolean operations on branch nodes and value to boolean operations on leaves.

The root condition can use any operator except PushUp. PushUp sudo condition exist only in the physical representation of tree, not in the logical.

func MakeDirectedCondition

func MakeDirectedCondition(op Op, operands ...any) (Condition, error)

MakeDirectedCondition create a Condition tree from a list of operands for a directional operator. If types match, MakeDirectedCondition sees the operand as a child condition or field path, otherwise the operand is a literal. Operands should not contain any nil pointers. MakeDirectedCondition encapsulate the order of operands so Condition builders don't need to worry about it.

Both []Condition and Condition matches Children. Both []Path and Path matches FieldPaths

func (Condition) ForEach

func (c Condition) ForEach(cf func(Condition), pf func([]CodeName), lf func(any))

ForEach loops through each operand to call a function to handle each case. `PushUp` sudo conditions are recursed so the condition handler function will never see a `PushUp`, while the invariant Op of c applies to all handler function calls. The literal handler function will never be called if Literal is the unset nil.

type Domain

type Domain struct {
	Thing
	Objects *dictionary.SelfKeyed[CodeName, *Object]
}

Domain holds a collection of objects, equivalent to all create table statements in a SQL database. Only one Domain is needed for most use cases.

Domain is expected to be build once and never modified after first use. Data migration to support changing domain will not be done through direct modifications to domain.

func NewDomain

func NewDomain(thing Thing, objs ...*Object) (*Domain, error)

func (*Domain) GetObjects

func (d *Domain) GetObjects() dictionary.Getter[CodeName, ObjectGetter]

type DomainGetter

type DomainGetter interface {
	ThingGetter
	GetObjects() dictionary.Getter[CodeName, ObjectGetter]
}

DomainGetter describes a read only interface to a domain, upto the level of Field. As a compormized between interface complexity and extenablity of domain meta data.

type Field

type Field struct {
	Thing
	FieldTypeSetting
	FieldType
	IsI18n   bool // if true, different values for different locals is possible. Only String and Binary need to be supported.
	Nullable bool // if true, difference between null and zero values are significate.
}

type FieldGroup

type FieldGroup struct {
	Fields     *dictionary.SelfKeyed[CodeName, *Field] // List each fields. The ordering of fields is not relevant for behavior but preserved for implementation details.
	Identities []Identity                              // required for object definitions and CombinationSetting on fields referred to in parent identity.
	Ranges     []Range                                 // if any ranges is left out of identities, it can be described here.
}

FieldGroup describe a collection of fields.

Identities and Ranges have optional names, which allow them to be referred to if set.

func (*FieldGroup) GetFields

func (g *FieldGroup) GetFields() dictionary.Getter[CodeName, *Field]

func (*FieldGroup) GetIdentities

func (g *FieldGroup) GetIdentities() []Identity

func (*FieldGroup) GetRanges

func (g *FieldGroup) GetRanges() []Range

type FieldGroupGetter

type FieldGroupGetter interface {
	GetFields() dictionary.Getter[CodeName, *Field]
	GetIdentities() []Identity
	GetRanges() []Range
}

type FieldType

type FieldType int8
const (
	FieldTypeUnset FieldType = iota
	String
	Binary
	Boolean
	TimeStamp
	Integer
	Real
	Reference
	List
	Combination
	FieldTypeMax = Combination
)

func (FieldType) String

func (f FieldType) String() string

func (FieldType) Valid

func (f FieldType) Valid() bool

type FieldTypeSetting

type FieldTypeSetting any

FieldTypeSetting is any of:

	String      StringSetting
	Binary      BinarySetting
	Boolean     BooleanSetting
    TimeStamp   TimeStampSetting
	Integer     IntegerSetting
	Real        RealSetting
	Reference   ReferenceSetting
	List        ListSetting
	Combination CombinationSetting

type I18n

type I18n[T any] map[language.Tag]T

I18n[T any] is used for internationalization, such as text and icons displayed to users.

func NewI18n

func NewI18n[T any](from I18nGetter[T]) I18n[T]

func (I18n[T]) Count

func (n I18n[T]) Count() int

func (I18n[T]) GetValue

func (n I18n[T]) GetValue(p *Picker, fallback T) T

func (I18n[T]) RangeAll

func (n I18n[T]) RangeAll(f func(language.Tag, T))

type I18nGetter

type I18nGetter[T any] interface {
	GetValue(p *Picker, fallback T) T
	Count() int
	RangeAll(func(language.Tag, T))
}

type Identity

type Identity struct {
	Thing
	Fields []CodeName
	Ranges []Range
}

Identity is used to mark a subset of fields in an object or a combination field as capable of identifying an single instance (a license plate number can ID a car), or uniqueness is required for correct modeling of state (two philosophers can not use the same fork at the same time).

All values in Identity.Thing is optional

type IntegerSetting

type IntegerSetting struct {
	Min  *big.Int
	Max  *big.Int
	Unit *Unit
}

func Int64Setting

func Int64Setting() IntegerSetting

func (IntegerSetting) Covers

func (s IntegerSetting) Covers(s2 IntegerSetting) bool

Covers returns true if s can support all values in s2

type ListSetting

type ListSetting struct {
	MinLength int64
	MaxLength int64
	IsOrdered bool // if true, the items ordering is preserved.
	IsUnique  bool // if true, repeated items should be ignored.

	ItemType        FieldType
	ItemTypeSetting FieldTypeSetting
}

ListSetting describes a collection of the same type:

| IsOrdered | IsUnique | collection type |
| false     | false    | counted set |
| false     | true     | set |
| true      | any      | list |

func (ListSetting) Covers

func (s ListSetting) Covers(s2 ListSetting) bool

func (ListSetting) ItemField

func (s ListSetting) ItemField(parent *Field) *Field

type NameTree

type NameTree map[CodeName]NameTree

type Object

type Object struct {
	Thing
	FieldGroup
}

Object describes a business object.

type ObjectGetter

type ObjectGetter interface {
	ThingGetter
	FieldGroupGetter
}

type ObjectNamePath

type ObjectNamePath struct {
	Domain CodeName
	Object CodeName
}

type Op

type Op uint8

Op defines condition operators. All Op, except PushUp, has an inverse. A large number of operators are defined for ease of writing and manipulating conditions. When realizing conditions, conditions should be simplified to match closely with underling implementation.

const (
	// Push up elements to the parent Condition, useful for custom ordering of operands.
	// When evaluated, PushUp can not be the top condition's operator.
	PushUp Op = iota

	// Unidirectional boolean operators.
	//
	// Care is taken to give sensible results when there are less operands than common definition to decrease
	// special case handling code. (|E| stands for the number of operands)
	//
	// Conceptually, removing an operand from `And` should only move the statement in the true direction.
	// Well removing an operand from `Or` should only move the statement in the false direction.
	//
	// When used to concatenate variable conditions, `And` is often used to concatenate filters,
	// without any filter defined, all value should be returned. So filter should be true for all values.
	// `Or` is often used to concatenate authorisation rules from roles. When no role is found,
	// user should not have access to any data.
	//
	// Implantation wise, `And` can be check by returning false on first false operand encountered,
	// true on exhaustion (unless null is also encountered).
	// `Or` can be check by returning true on first true operand encountered, false on exhaustion.
	And  // all operands are true, if |E| = 0, always true
	Nand // if |E| = 0, always false
	Or   // one or more operands is true, if |E| = 0, always false
	Nor  // if |E| = 0, always true
	Eq   // all operands are equal, if |E| < 2, always true
	Neq  // if |E| < 2, always false

	// A nil Literal can't be used, so use *Null for comparison against nil values.
	// When other operators encounter nil values, it is viewed as unknown.
	// ie: Eq(nil, nil) = nil; And(true, nil) = true; And(false, nil) = nil.
	AndIsNull  // true iff all values are nil.    If |E| = 0, always true.  Inverse of OrNotNull
	OrIsNull   // true iff any values are nil.    If |E| = 0, always false. Inverse of AndNotNull
	AndNotNull // true iff no  values are nil.    If |E| = 0, always true.  Inverse of OrIsNull
	OrNotNull  // true iff any values is not nil. If |E| = 0, always false. Inverse of AndIsNull

	// Set operators work on sets, `In` does intersects and return true iff intersect is not empty.
	// Operands that are not sets are automatically promoted to a set containing it self.
	In    // if |E| = 0, always true. But if any operand is empty, always false.
	NotIn // inverse of In

	// Directional operators apply Children first, then FieldPaths, and Literal last.
	// All operands must be of the same orderable type to be comparable.
	// If |E| > 2, such as {a<b<c}, it is considered equivalent to {(a<b)&(b<c)}.
	// Even though {a<b} is inversed of {a>=b}, this is not true for {a<b<c} vs {a>=b>=c}.
	// For example: {1<3<2} and {1>=3>=2} are both false. So directional operators get their own
	// inverse N* operators instead of using the more common conversions.
	//
	// For Lt,Lte,Gt,and Gte; if |E| < 2, always true. Meaning operand list is total or partial ordered.
	// For their inverses (N*); if |E| < 2, always false.
	Lt
	Nlt
	Lte
	Nlte
	Gt
	Ngt
	Gte
	Ngte

	OpMax = Ngte
)

func (Op) Inverse

func (op Op) Inverse() Op

func (Op) IsDirectional

func (op Op) IsDirectional() bool

func (Op) String

func (op Op) String() string

type PartialOrder

type PartialOrder struct {
	FieldPath []CodeName
	Inverse   bool
}

type Path

type Path []CodeName

Path is a list of code names to walk through references and fields

func NewPath

func NewPath(ns ...CodeName) Path

type Picker

type Picker struct {
	// contains filtered or unexported fields
}

Picker is an inverse language.Matcher, where user request language is static but supported language is dynamic.

Picker is safe to use concurrently.

func NewPicker

func NewPicker(preferred []language.Tag, fallback *Picker) *Picker

NewPicker creates a language picker by confidence, then by natural order of the preferred list. If no matches are found, fallback is used.

func SystemLogPicker

func SystemLogPicker() *Picker

SystemLogPicker picks the language based on the configuration of the local environment.

type Query

type Query struct {
	ObjectName ObjectNamePath
	Fields     NameTree
	Condition  Condition
	Count      bool
	Order      []PartialOrder
	Offset     int64
	Limit      int64 // future: use order and last row data for offset condition
}

type Range

type Range struct {
	Thing
	Start           CodeName
	End             CodeName
	IncludeEndValue bool
}

Range marks two fields by name as describing a range of values. Start and End must reference two comparable fields of the same type.

All values in Range.Thing is optional

  • If IncludeEndValue = false, then Start < End. (range must have none zero length)
  • If IncludeEndValue = true, then Start <= End.

Currently, the only for seen usage of Range is to support time ranges. Under this context, the end value of a time range can create ambiguities:

  • When a room is booked from 1 to 2 o'clock, it can be booked from 2 onwards. Here the end value is excluded from the range.
  • When a person is busy from Monday to Friday, he is still busy on Friday. Here the end value is included in the range.

Although it's possible to only support one type of range on the backend and only convert to the users' expectation on display, this crates a horrible off-by-one trap that can only be fixed once on the display path. By adding an IncludeEndValue option, the most human friendly interpretation of end value is preserved thought-out.

type RealSetting

type RealSetting struct {
	Standard    RealStandard
	Base        uint8 // base 2 and 10 are most common, others are unlikely to be supported.
	MinMantissa *big.Int
	MaxMantissa *big.Int
	MinExponent *int64 // pointer to diff zero vs not set
	MaxExponent *int64

	// Alternative settings for RealStandard of Float32 or Float64 to replace *Mantissa and *Exponent.
	// The full range is supported if not set.
	MinFloat *float64
	MaxFloat *float64

	Unit *Unit
}

func (RealSetting) Covers

func (s RealSetting) Covers(s2 RealSetting) bool

Covers returns true if s can support all values in s2.

func (*RealSetting) Valid

func (s *RealSetting) Valid() bool

type RealStandard

type RealStandard int8
const (
	CustomReal RealStandard = iota
	Float32
	Float64
)

func (RealStandard) Covers

func (s RealStandard) Covers(s2 RealStandard) bool

Covers returns true if s can support all values in s2.

func (RealStandard) String

func (s RealStandard) String() string

type ReferenceField

type ReferenceField struct {
	Local  CodeName
	Target CodeName
}

type ReferenceSetting

type ReferenceSetting struct {
	Object       CodeName         // target object.
	Identity     CodeName         // target identity or field name (if an identity has just this field)
	PromotionMap []ReferenceField // Promote selected fields to parent level, if already exist, field settings must match.
	ReferenceTrackingOption
}

ReferenceSetting allow one object to refer to an other.

func (ReferenceSetting) Covers

func (s ReferenceSetting) Covers(s2 ReferenceSetting) bool

Covers returns true if s can support all values in s2.

type ReferenceTrackingAction

type ReferenceTrackingAction uint8
const (
	ActionRestrict ReferenceTrackingAction = iota // This is the default to protect data.
	ActionCascade                                 // Only supported for OnUpdate.
	ActionSetNull                                 // Only supported for OnDelete.
	ActionIgnore                                  // Nothing happens, the reference is only a suggestion.
)

type ReferenceTrackingOption

type ReferenceTrackingOption struct {
	OnUpdate ReferenceTrackingAction
	OnDelete ReferenceTrackingAction
}

type StringSetting

type StringSetting struct {
	MinCodePoints int64
	MaxCodePoints int64
	IsSingleLine  bool
}

func (StringSetting) Covers

func (s StringSetting) Covers(s2 StringSetting) bool

Covers returns true if s can support all values in s2

type Thing

type Thing struct {
	Name        CodeName     // name is the long term api name of the thing, name is locally unique.
	Label       I18n[string] // used for displaying input label or column header.
	Description I18n[string] // used for displaying addition information.
}

Thing is a base type for anything that can be identified.

func NewThing

func NewThing(from ThingGetter) Thing

func (Thing) GetDescription

func (t Thing) GetDescription() I18nGetter[string]

func (Thing) GetLabel

func (t Thing) GetLabel() I18nGetter[string]

func (Thing) GetName

func (t Thing) GetName() CodeName

func (Thing) GetThing

func (t Thing) GetThing() ThingGetter

type ThingGetter

type ThingGetter interface {
	GetName() CodeName
	GetLabel() I18nGetter[string]
	GetDescription() I18nGetter[string]
}

type TimeStampSetting

type TimeStampSetting struct {
	Min                time.Time
	Max                time.Time
	Scale              time.Duration // >= 24h: date only; >= 1s: datetime; < 1s: support fraction seconds
	WithTimeZoneOffset bool          // if false always UTC
}

func (TimeStampSetting) Covers

func (s TimeStampSetting) Covers(s2 TimeStampSetting) bool

Covers returns true if s can support all values in s2

type Unit

type Unit struct {
	Thing
	Symble string // a display symbol, such as: %, °C
}

func (*Unit) Covers

func (s *Unit) Covers(s2 *Unit) bool

Covers returns true if s can support all values in s2.

For unit comparisons, no value ranges are checked, So *Unit.Covers has a different meaning. For most use cases, receiver s should be nil, since it describes the ability to handle a set of values without care to the unit. But in case it does care about the unit, we protect against mixing units by returning false if s2 is different from s.

Directories

Path Synopsis
demo
dictionary implements a collection to hold field and object definitions and checks CodeName character and namespace rules.
dictionary implements a collection to hold field and object definitions and checks CodeName character and namespace rules.
persistence
seederrors is mostly a warper around github.com/cockroachdb/errors to use a subset of the features.
seederrors is mostly a warper around github.com/cockroachdb/errors to use a subset of the features.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL