add interfaces
This commit is contained in:
parent
d3495f874e
commit
06f3247b08
@ -8,8 +8,9 @@ _ "fmt"
|
||||
_ "github.com/xeipuuv/gojsonschema"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io"
|
||||
_ "log"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
//type JSONDecoder json.Decoder
|
||||
@ -30,6 +31,10 @@ func NewDecoder(r io.Reader, format Format) Decoder {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewStringDecoder(s string, format Format) Decoder {
|
||||
return NewDecoder(strings.NewReader(s), format)
|
||||
}
|
||||
|
||||
func NewJSONDecoder(r io.Reader) Decoder {
|
||||
return json.NewDecoder(r)
|
||||
}
|
||||
@ -39,6 +44,7 @@ func NewJSONStringDecoder(s string) Decoder {
|
||||
}
|
||||
|
||||
func NewYAMLDecoder(r io.Reader) Decoder {
|
||||
slog.Info("NewYAMLDecoder()", "reader", r)
|
||||
return yaml.NewDecoder(r)
|
||||
}
|
||||
|
||||
@ -46,6 +52,17 @@ func NewYAMLStringDecoder(s string) Decoder {
|
||||
return yaml.NewDecoder(strings.NewReader(s))
|
||||
}
|
||||
|
||||
func NewProtoBufDecoder(r io.Reader) Decoder {
|
||||
return nil
|
||||
type ProtoDecoder struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (p *ProtoDecoder) Decode(v any) (err error) {
|
||||
var protoData []byte
|
||||
protoData, err = io.ReadAll(p.reader)
|
||||
err = proto.Unmarshal(protoData, v.(proto.Message))
|
||||
return
|
||||
}
|
||||
|
||||
func NewProtoBufDecoder(r io.Reader) Decoder {
|
||||
return &ProtoDecoder{ reader: r }
|
||||
}
|
||||
|
@ -9,14 +9,17 @@ _ "log"
|
||||
"strings"
|
||||
"testing"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
"io"
|
||||
"bytes"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type TestUser struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Uid string `json:"uid" yaml:"uid"`
|
||||
Group string `json:"group" yaml:"group"`
|
||||
Home string `json:"home" yaml:"home"`
|
||||
State string `json:"state" yaml:"state"`
|
||||
Name string `json:"name" yaml:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
Uid string `json:"uid" yaml:"uid" protobuf:"bytes,2,opt,name=uid"`
|
||||
Group string `json:"group" yaml:"group" protobuf:"bytes,3,opt,name=group"`
|
||||
Home string `json:"home" yaml:"home" protobuf:"bytes,4,opt,name=home"`
|
||||
State string `json:"state" yaml:"state" protobuf:"bytes,5,opt,name=state"`
|
||||
}
|
||||
|
||||
func TestNewYAMLDecoder(t *testing.T) {
|
||||
@ -78,3 +81,31 @@ func TestNewJSONStringDecoder(t *testing.T) {
|
||||
docErr := e.Decode(&TestUser{})
|
||||
assert.Nil(t, docErr)
|
||||
}
|
||||
|
||||
func TestNewDecoder(t *testing.T) {
|
||||
pbData, err := proto.Marshal(&TestUser{ Name: "pb", Uid: "15001", Group: "15005", Home: "/home/pb", State: "present" })
|
||||
assert.Nil(t, err)
|
||||
for _, v := range []struct{ reader io.Reader; format Format; expectedhome string } {
|
||||
{ reader: strings.NewReader(`{
|
||||
"name": "testuser",
|
||||
"uid": "12001",
|
||||
"group": "12001",
|
||||
"home": "/home/testuser",
|
||||
"state": "present" }`), format: FormatJson, expectedhome: "/home/testuser" },
|
||||
{ reader: strings.NewReader(`
|
||||
name: "testuser"
|
||||
uid: "12001"
|
||||
group: "12001"
|
||||
home: "/home/test"
|
||||
state: "present"
|
||||
`), format: FormatYaml, expectedhome: "/home/test" },
|
||||
{ reader: bytes.NewReader(pbData), format: FormatProtoBuf, expectedhome: "/home/pb" },
|
||||
} {
|
||||
|
||||
decoder := NewDecoder(v.reader, v.format)
|
||||
assert.NotNil(t, decoder)
|
||||
u := &TestUser{}
|
||||
assert.Nil(t, decoder.Decode(u))
|
||||
assert.Equal(t, v.expectedhome, u.Home )
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,10 @@ func (f Format) Decoder(r io.Reader) Decoder {
|
||||
return NewDecoder(r, f)
|
||||
}
|
||||
|
||||
func (f Format) StringDecoder(s string) Decoder {
|
||||
return NewStringDecoder(s, f)
|
||||
}
|
||||
|
||||
func (f Format) Serialize(object any, w io.Writer) error {
|
||||
return f.Encoder(w).Encode(object)
|
||||
}
|
||||
|
10
internal/data/config.go
Normal file
10
internal/data/config.go
Normal file
@ -0,0 +1,10 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
)
|
||||
|
||||
type ConfigurationValueGetter interface {
|
||||
GetValue(key string) (any, error)
|
||||
}
|
26
internal/data/converter.go
Normal file
26
internal/data/converter.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
)
|
||||
|
||||
// Convert a resource to a document and a document to a resource
|
||||
|
||||
type Emitter interface {
|
||||
Emit(document Document, filter ResourceSelector) (Resource, error)
|
||||
}
|
||||
|
||||
type Extracter interface {
|
||||
Extract(resource Resource, filter ResourceSelector) (Document, error)
|
||||
}
|
||||
|
||||
type Converter interface {
|
||||
Typer
|
||||
Emitter
|
||||
Extracter
|
||||
}
|
||||
|
||||
type ManyExtractor interface {
|
||||
ExtractMany(resource Resource, filter ResourceSelector) ([]Document, error)
|
||||
}
|
@ -26,7 +26,7 @@ type Deleter interface {
|
||||
Delete(context.Context) error
|
||||
}
|
||||
|
||||
type Crudder struct {
|
||||
type Crudder interface {
|
||||
Creator
|
||||
Reader
|
||||
Updater
|
||||
|
51
internal/data/document.go
Normal file
51
internal/data/document.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"decl/internal/codec"
|
||||
"io"
|
||||
"decl/internal/mapper"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyDocument error = errors.New("Document contains no resources")
|
||||
)
|
||||
|
||||
type Serializer interface {
|
||||
JSON() ([]byte, error)
|
||||
YAML() ([]byte, error)
|
||||
PB() ([]byte, error)
|
||||
Generate(w io.Writer) (error)
|
||||
}
|
||||
|
||||
type Loader interface {
|
||||
LoadString(string, codec.Format) (error)
|
||||
Load([]byte, codec.Format) (error)
|
||||
LoadReader(io.ReadCloser, codec.Format) (error)
|
||||
}
|
||||
|
||||
type DocumentGetter interface {
|
||||
GetDocument() Document
|
||||
}
|
||||
|
||||
type Document interface {
|
||||
Serializer
|
||||
Loader
|
||||
Validator
|
||||
mapper.Mapper
|
||||
|
||||
NewResource(uri string) (Resource, error)
|
||||
Types() (TypesRegistry[Resource])
|
||||
// Resources() []Declaration
|
||||
|
||||
SetConfig(config Document)
|
||||
ConfigDoc() Document
|
||||
Len() int
|
||||
ResolveIds(ctx context.Context)
|
||||
Filter(filter DeclarationSelector) []Declaration
|
||||
|
||||
//Diff(with *Document, output io.Writer) (returnOutput string, diffErr error)
|
||||
}
|
22
internal/data/identifier.go
Normal file
22
internal/data/identifier.go
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidURI error = errors.New("Invalid URI")
|
||||
)
|
||||
|
||||
type Identifier interface {
|
||||
URI() string
|
||||
SetURI(string) error
|
||||
}
|
||||
|
||||
type Selector[Item comparable] func(r Item) bool
|
||||
|
||||
type ResourceSelector Selector[Resource]
|
||||
|
||||
type DeclarationSelector Selector[Declaration]
|
91
internal/data/resource.go
Normal file
91
internal/data/resource.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"decl/internal/mapper"
|
||||
"decl/internal/transport"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"io"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidResource error = errors.New("Invalid resource")
|
||||
)
|
||||
|
||||
type ResourceMapper mapper.Map[string, Declaration]
|
||||
|
||||
type StateTransformer interface {
|
||||
Apply() error
|
||||
}
|
||||
|
||||
type Resource interface {
|
||||
Identifier
|
||||
Type() string
|
||||
StateMachine() machine.Stater
|
||||
UseConfig(config ConfigurationValueGetter)
|
||||
ResolveId(context.Context) string
|
||||
Loader
|
||||
StateTransformer
|
||||
Crudder
|
||||
Validator
|
||||
Clone() Resource
|
||||
SetResourceMapper(ResourceMapper)
|
||||
}
|
||||
|
||||
type Declaration interface {
|
||||
Identifier
|
||||
ResourceType() TypeName
|
||||
ResolveId(context.Context) string
|
||||
Loader
|
||||
Validator
|
||||
StateTransformer
|
||||
Resource() Resource
|
||||
Clone() Declaration
|
||||
}
|
||||
|
||||
func NewResourceMapper() ResourceMapper {
|
||||
return mapper.New[string, Declaration]()
|
||||
}
|
||||
|
||||
type ContentIdentifier interface {
|
||||
ContentType() string
|
||||
}
|
||||
|
||||
type ContentReader interface {
|
||||
ContentReaderStream() (*transport.Reader, error)
|
||||
}
|
||||
|
||||
type ContentWriter interface {
|
||||
ContentWriterStream() (*transport.Writer, error)
|
||||
}
|
||||
|
||||
type ContentReadWriter interface {
|
||||
ContentReader
|
||||
ContentWriter
|
||||
}
|
||||
|
||||
type ContentGetter interface {
|
||||
GetContent(w io.Writer) (contentReader io.ReadCloser, err error)
|
||||
}
|
||||
|
||||
type ContentSetter interface {
|
||||
SetContent(r io.Reader) error
|
||||
}
|
||||
|
||||
type ContentGetSetter interface {
|
||||
ContentGetter
|
||||
ContentSetter
|
||||
}
|
||||
|
||||
type FileResource interface {
|
||||
FilePath() string
|
||||
SetFileInfo(fs.FileInfo) error
|
||||
FileInfo() fs.FileInfo
|
||||
ContentGetSetter
|
||||
SetContentSourceRef(uri string)
|
||||
}
|
||||
|
50
internal/data/stater.go
Normal file
50
internal/data/stater.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
)
|
||||
|
||||
func StorageMachine(sub machine.Subscriber) machine.Stater {
|
||||
// start_destroy -> absent -> start_create -> present -> start_destroy
|
||||
stater := machine.New("unknown")
|
||||
stater.AddStates("initialized", "unkonwn", "absent", "start_create", "present", "start_delete", "start_read", "start_update")
|
||||
stater.AddTransition("construct", machine.States("unknown"), "initialized")
|
||||
stater.AddTransition("create", machine.States("unknown", "initialized", "absent"), "start_create")
|
||||
if e := stater.AddSubscription("create", sub); e != nil {
|
||||
return nil
|
||||
}
|
||||
stater.AddTransition("created", machine.States("start_create"), "present")
|
||||
if e := stater.AddSubscription("created", sub); e != nil {
|
||||
return nil
|
||||
}
|
||||
stater.AddTransition("exists", machine.States("unknown", "initialized", "absent"), "present")
|
||||
if e := stater.AddSubscription("exists", sub); e != nil {
|
||||
return nil
|
||||
}
|
||||
stater.AddTransition("notexists", machine.States("*"), "absent")
|
||||
if e := stater.AddSubscription("notexists", sub); e != nil {
|
||||
return nil
|
||||
}
|
||||
stater.AddTransition("read", machine.States("*"), "start_read")
|
||||
if e := stater.AddSubscription("read", sub); e != nil {
|
||||
return nil
|
||||
}
|
||||
stater.AddTransition("state_read", machine.States("start_read"), "present")
|
||||
stater.AddTransition("update", machine.States("*"), "start_update")
|
||||
if e := stater.AddSubscription("update", sub); e != nil {
|
||||
return nil
|
||||
}
|
||||
stater.AddTransition("updated", machine.States("start_update"), "present")
|
||||
stater.AddTransition("delete", machine.States("*"), "start_delete")
|
||||
if e := stater.AddSubscription("delete", sub); e != nil {
|
||||
return nil
|
||||
}
|
||||
stater.AddTransition("deleted", machine.States("start_delete"), "absent")
|
||||
if e := stater.AddSubscription("deleted", sub); e != nil {
|
||||
return nil
|
||||
}
|
||||
return stater
|
||||
}
|
||||
|
23
internal/data/types.go
Normal file
23
internal/data/types.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Factory[Product comparable] func(*url.URL) Product
|
||||
|
||||
type TypesRegistry[Product comparable] interface {
|
||||
New(uri string) (result Product, err error)
|
||||
Has(typename string) bool
|
||||
//Get(string) Factory[Product]
|
||||
}
|
||||
|
||||
type TypeName string //`json:"type"`
|
||||
|
||||
func (t TypeName) String() string { return string(t) }
|
||||
|
||||
type Typer interface {
|
||||
Type() TypeName
|
||||
}
|
64
internal/folio/uri.go
Normal file
64
internal/folio/uri.go
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package folio
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"decl/internal/transport"
|
||||
"decl/internal/data"
|
||||
"decl/internal/identifier"
|
||||
"errors"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
ErrInvalidURI error = errors.New("Invalid URI")
|
||||
)
|
||||
|
||||
type URI identifier.ID
|
||||
|
||||
func (u URI) NewResource(document data.Document) (newResource data.Resource, err error) {
|
||||
if document == nil {
|
||||
declaration := NewDeclaration()
|
||||
if err = declaration.NewResource((*string)(&u)); err == nil {
|
||||
return declaration.Attributes.(data.Resource), err
|
||||
}
|
||||
} else {
|
||||
newResource, err = document.NewResource(string(u))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u URI) Parse() *url.URL {
|
||||
url, e := url.Parse(string(u))
|
||||
if e == nil {
|
||||
return url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u URI) Exists() bool {
|
||||
return transport.ExistsURI(string(u))
|
||||
}
|
||||
|
||||
func (u URI) ContentReaderStream() (*transport.Reader, error) {
|
||||
return transport.NewReaderURI(string(u))
|
||||
}
|
||||
|
||||
func (u URI) ContentWriterStream() (*transport.Writer, error) {
|
||||
return transport.NewWriterURI(string(u))
|
||||
}
|
||||
|
||||
func (u URI) String() string {
|
||||
return string(u)
|
||||
}
|
||||
|
||||
func (u *URI) SetURL(url *url.URL) {
|
||||
(*identifier.ID)(u).SetURL(url)
|
||||
}
|
||||
|
||||
func (u URI) Extension() (string, string) {
|
||||
return (identifier.ID)(u).Extension()
|
||||
}
|
||||
|
16
internal/folio/uri_test.go
Normal file
16
internal/folio/uri_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package folio
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func TestURI(t *testing.T) {
|
||||
var file URI = URI(fmt.Sprintf("file://%s", TempDir))
|
||||
u := file.Parse()
|
||||
assert.Equal(t, "file", u.Scheme)
|
||||
assert.True(t, file.Exists())
|
||||
}
|
42
internal/identifier/id.go
Normal file
42
internal/identifier/id.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package identifier
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
ErrInvalidURI error = errors.New("Invalid URI")
|
||||
)
|
||||
|
||||
type ID string
|
||||
|
||||
func (i *ID) SetURL(u *url.URL) {
|
||||
*i = ID(u.String())
|
||||
}
|
||||
|
||||
func (i ID) Parse() *url.URL {
|
||||
url, e := url.Parse(string(i))
|
||||
if e == nil {
|
||||
return url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i ID) Extension() (exttype string, fileext string) {
|
||||
elements := strings.Split(string(i), ".")
|
||||
numberOfElements := len(elements)
|
||||
if numberOfElements > 1 {
|
||||
if numberOfElements > 2 {
|
||||
exttype = elements[numberOfElements - 2]
|
||||
fileext = elements[numberOfElements - 1]
|
||||
}
|
||||
exttype = elements[numberOfElements - 1]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
44
internal/identifier/id_test.go
Normal file
44
internal/identifier/id_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package identifier
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"fmt"
|
||||
"os"
|
||||
"log"
|
||||
)
|
||||
|
||||
var TempDir string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
TempDir, err = os.MkdirTemp("", "testidentifier")
|
||||
if err != nil || TempDir == "" {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
rc := m.Run()
|
||||
|
||||
os.RemoveAll(TempDir)
|
||||
os.Exit(rc)
|
||||
}
|
||||
|
||||
func TestID(t *testing.T) {
|
||||
var file ID = ID(fmt.Sprintf("file://%s", TempDir))
|
||||
u := file.Parse()
|
||||
assert.Equal(t, "file", u.Scheme)
|
||||
filetype, fileext := file.Extension()
|
||||
assert.Equal(t, "", filetype)
|
||||
assert.Equal(t, "", fileext)
|
||||
}
|
||||
|
||||
func TestSetID(t *testing.T) {
|
||||
var file ID = ID(fmt.Sprintf("file://%s", TempDir))
|
||||
u := file.Parse()
|
||||
|
||||
var setFile ID
|
||||
setFile.SetURL(u)
|
||||
assert.Equal(t, file, setFile)
|
||||
}
|
Loading…
Reference in New Issue
Block a user