jx/internal/folio/declaration.go
Matthew Rich eaaf0f8931
All checks were successful
Lint / golangci-lint (push) Successful in 10m38s
Declarative Tests / test (push) Successful in 38s
collect resource/doc errors and add to result. Add readwritecloser support for HTTP transport reading response data
2024-10-02 20:26:02 +00:00

411 lines
11 KiB
Go

// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package folio
import (
_ "errors"
"context"
"encoding/json"
"fmt"
"io"
"gopkg.in/yaml.v3"
"log/slog"
_ "gitea.rosskeen.house/rosskeen.house/machine"
//_ "gitea.rosskeen.house/pylon/luaruntime"
"decl/internal/codec"
"decl/internal/data"
"decl/internal/schema"
"net/url"
"runtime/debug"
"errors"
)
var (
ErrUnknownStateTransition error = errors.New("Unknown state transition")
)
type ConfigName string
type DeclarationType struct {
Type TypeName `json:"type" yaml:"type"`
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
Error string `json:"error,omitempty" yaml:"error,omitempty"`
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
}
type Declaration struct {
Type TypeName `json:"type" yaml:"type"`
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
Attributes data.Resource `json:"attributes" yaml:"attributes"`
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
Error string `json:"error,omitempty" yaml:"error,omitempty"`
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
// runtime luaruntime.LuaRunner
document *Document
configBlock data.Block
ResourceTypes data.TypesRegistry[data.Resource] `json:"-" yaml:"-"`
}
func NewDeclaration() *Declaration {
return &Declaration{ ResourceTypes: DocumentRegistry.ResourceTypes }
}
func NewDeclarationFromDocument(document *Document) *Declaration {
return &Declaration{ document: document, ResourceTypes: document.Types() }
}
func (n *ConfigName) Exists() bool {
return DocumentRegistry.ConfigNameMap.Has(string(*n))
}
func (n *ConfigName) GetBlock() *Block {
if v, ok := DocumentRegistry.ConfigNameMap.Get(string(*n)); ok {
return v
}
return nil
}
func (d *Declaration) SetDocument(newDocument *Document) {
slog.Info("Declaration.SetDocument()", "declaration", d)
d.document = newDocument
d.SetConfig(d.document.config)
d.ResourceTypes = d.document.Types()
d.Attributes.SetResourceMapper(d.document.uris)
}
func (d *Declaration) ResolveId(ctx context.Context) string {
defer func() {
if r := recover(); r != nil {
slog.Info("Declaration.ResolveId() - panic", "recover", r, "state", d.Attributes.StateMachine())
if triggerErr := d.Attributes.StateMachine().Trigger("notexists"); triggerErr != nil {
panic(triggerErr)
}
}
}()
slog.Info("Declaration.ResolveId()")
id := d.Attributes.ResolveId(ctx)
return id
}
func (d *Declaration) Clone() data.Declaration {
return &Declaration {
Type: d.Type,
Transition: d.Transition,
Attributes: d.Attributes.Clone(),
//runtime: luaruntime.New(),
Config: d.Config,
Requires: d.Requires,
}
}
func (d *Declaration) Load(docData []byte, f codec.Format) (err error) {
err = f.StringDecoder(string(docData)).Decode(d)
return
}
func (d *Declaration) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
err = f.Decoder(r).Decode(d)
return
}
func (d *Declaration) LoadString(docData string, f codec.Format) (err error) {
err = f.StringDecoder(docData).Decode(d)
return
}
func (d *Declaration) ResourceType() data.TypeName {
return data.TypeName(d.Type)
}
func (d *Declaration) URI() string {
return d.Attributes.URI()
}
func (d *Declaration) JSON() ([]byte, error) {
return json.Marshal(d)
}
func (d *Declaration) Validate() (err error) {
var declarationJson []byte
if declarationJson, err = d.JSON(); err == nil {
s := schema.New(fmt.Sprintf("%s-declaration", d.Type), schemaFiles)
err = s.Validate(string(declarationJson))
}
return err
}
func (d *Declaration) NewResourceFromParsedURI(u *url.URL) (err error) {
if u == nil {
d.Attributes, err = d.ResourceTypes.NewFromParsedURI(&url.URL{ Scheme: string(d.Type) })
} else {
if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(u); err == nil {
err = d.Attributes.SetParsedURI(u)
}
}
return
}
func (d *Declaration) NewResource(uri *string) (err error) {
if d.ResourceTypes == nil {
panic(fmt.Errorf("Undefined type registry: unable to create new resource %s", *uri))
}
if uri == nil {
d.Attributes, err = d.ResourceTypes.New(fmt.Sprintf("%s://", d.Type))
} else {
if d.Attributes, err = d.ResourceTypes.New(*uri); err == nil {
err = d.Attributes.SetURI(*uri)
}
}
return
}
func (d *Declaration) Resource() data.Resource {
return d.Attributes
}
func (d *Declaration) Apply(stateTransition string) (result error) {
defer func() {
if r := recover(); r != nil {
slog.Debug("Declaration.Apply()", "stacktrace", string(debug.Stack()))
slog.Info("Declaration.Apply()", "error", r, "resourceerror", d.Error)
if d.Error != "" {
result = fmt.Errorf("%s - %s", r, d.Error)
} else {
result = fmt.Errorf("%s", r)
}
}
if result != nil {
d.Error = result.Error()
}
}()
if stateTransition == "" {
stateTransition = d.Transition
}
stater := d.Attributes.StateMachine()
slog.Info("Declaration.Apply()", "stateTransition", stateTransition, "machine", stater, "machine.state", stater.CurrentState(), "uri", d.Attributes.URI())
switch stateTransition {
case "construct":
if doc, ok := DocumentRegistry.DeclarationMap[d]; ok {
d.SetDocument(doc)
}
case "stat":
result = stater.Trigger("stat")
case "read":
result = stater.Trigger("read")
case "delete", "absent":
if stater.CurrentState() == "present" {
result = stater.Trigger("delete")
}
case "update":
if result = stater.Trigger("update"); result != nil {
return result
}
result = stater.Trigger("read")
default:
return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI())
case "create", "present":
if stater.CurrentState() == "absent" || stater.CurrentState() == "unknown" {
if result = stater.Trigger("create"); result != nil {
slog.Info("Declaration.Apply()", "trigger", "create", "state", stater.CurrentState(), "error", result, "declaration", d)
return result
}
}
result = stater.Trigger("read")
currentState := stater.CurrentState()
switch currentState {
case "create", "present":
default:
return fmt.Errorf("Failed to create resource: %s - state: %s, err: %s", d.URI(), currentState, d.Error)
}
}
return result
}
func (d *Declaration) SetConfig(configDoc data.Document) {
slog.Info("Declaration.SetConfig()", "config", configDoc)
if configDoc != nil {
if configDoc.Has(string(d.Config)) {
if v, ok := configDoc.Get(string(d.Config)); ok {
d.configBlock = v.(data.Block)
d.Attributes.UseConfig(d.configBlock)
return
}
}
}
if v, ok := DocumentRegistry.ConfigNameMap.Get(string(d.Config)); ok {
d.configBlock = v
d.Attributes.UseConfig(d.configBlock)
return
}
if d.Config != "" { // XXX
panic("failed setting config")
}
}
func (d *Declaration) SetURI(uri string) (err error) {
slog.Info("Declaration.SetURI()", "uri", uri, "declaration", d)
if d.Attributes == nil {
err = d.NewResource(&uri)
} else {
err = d.Attributes.SetURI(uri)
}
if err != nil {
return err
}
if d.Attributes == nil {
return fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
}
d.Type = TypeName(d.Attributes.Type())
_, err = d.Attributes.Read(context.Background()) // fix context
slog.Info("Declaration.SetURI() - read", "error", err)
return
}
func (d *Declaration) SetParsedURI(uri *url.URL) (err error) {
slog.Info("Declaration.SetParsedURI()", "uri", uri, "declaration", d)
if d.Attributes == nil {
err = d.NewResourceFromParsedURI(uri)
} else {
err = d.Attributes.SetParsedURI(uri)
}
if err != nil {
return err
}
if d.Attributes == nil {
return fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
}
d.Type = TypeName(d.Attributes.Type())
_, err = d.Attributes.Read(context.Background()) // fix context
slog.Info("Declaration.SetURI() - read", "error", err)
return
}
func (d *Declaration) UnmarshalValue(value *DeclarationType) error {
slog.Info("Declaration.UnmarshalValue", "declaration", d, "value", value, "addr", d)
if d.ResourceTypes == nil {
panic(fmt.Errorf("Undefined type registry: unable to create new resource %s", value.Type))
}
d.Type = value.Type
d.Transition = value.Transition
d.Config = value.Config
d.OnError = value.OnError
d.Error = value.Error
d.Requires = value.Requires
newResource, resourceErr := d.ResourceTypes.New(fmt.Sprintf("%s://", value.Type))
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr, "type", value.Type, "resource", newResource, "resourcetypes", d.ResourceTypes)
if resourceErr != nil {
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr)
return resourceErr
}
d.Attributes = newResource
d.configBlock = d.Config.GetBlock()
return nil
}
func (d *Declaration) UnmarshalYAML(value *yaml.Node) error {
if d.ResourceTypes == nil {
d.ResourceTypes = DocumentRegistry.ResourceTypes
}
t := &DeclarationType{}
if unmarshalResourceTypeErr := value.Decode(t); unmarshalResourceTypeErr != nil {
return unmarshalResourceTypeErr
}
if err := d.UnmarshalValue(t); err != nil {
return err
}
resourceAttrs := struct {
Attributes yaml.Node `json:"attributes"`
}{}
if unmarshalAttributesErr := value.Decode(&resourceAttrs); unmarshalAttributesErr != nil {
return unmarshalAttributesErr
}
if unmarshalResourceErr := resourceAttrs.Attributes.Decode(d.Attributes); unmarshalResourceErr != nil {
return unmarshalResourceErr
}
return nil
}
func (d *Declaration) UnmarshalJSON(jsonData []byte) error {
if d.ResourceTypes == nil {
d.ResourceTypes = DocumentRegistry.ResourceTypes
}
t := &DeclarationType{}
if unmarshalResourceTypeErr := json.Unmarshal(jsonData, t); unmarshalResourceTypeErr != nil {
return unmarshalResourceTypeErr
}
if err := d.UnmarshalValue(t); err != nil {
return err
}
resourceAttrs := struct {
Attributes data.Resource `json:"attributes"`
}{Attributes: d.Attributes}
if unmarshalAttributesErr := json.Unmarshal(jsonData, &resourceAttrs); unmarshalAttributesErr != nil {
return unmarshalAttributesErr
}
return nil
}
/*
func (d *Declaration) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
buf.WriteByte('"')
buf.WriteString("value"))
buf.WriteByte('"')
return buf.Bytes(), nil
}
*/
/*
func (d *Declaration) MarshalYAML() (any, error) {
return d, nil
}
*/
/*
func (l *LuaWorker) Receive(m message.Envelope) {
s := m.Sender()
switch b := m.Body().(type) {
case *message.Error:
// case *worker.Terminated:
case *CodeExecute:
stackSize := l.runtime.Api().GetTop()
if e := l.runtime.LoadScriptFromString(b.Code); e != nil {
s.Send(message.New(&message.Error{ E: e }, l))
}
returnsCount := l.runtime.Api().GetTop() - stackSize
if len(b.Entrypoint) == 0 {
if ! l.runtime.Api().IsNil(-1) {
if returnsCount == 0 {
s.Send(message.New(&CodeResult{ Result: []interface{}{ 0 } }, l))
} else {
lr,le := l.runtime.CopyReturnValuesFromCall(int(returnsCount))
if le != nil {
s.Send(message.New(&message.Error{ E: le }, l))
} else {
s.Send(message.New(&CodeResult{ Result: lr }, l))
}
}
}
} else {
r,ce := l.runtime.CallFunction(b.Entrypoint, b.Args)
if ce != nil {
s.Send(message.New(&message.Error{ E: ce }, l))
}
s.Send(message.New(&CodeResult{ Result: r }, l))
}
}
}
*/