add interfaces
Some checks failed
Lint / golangci-lint (push) Has been cancelled
Declarative Tests / test (push) Has been cancelled
Declarative Tests / build-fedora (push) Has been cancelled
Declarative Tests / build-ubuntu-focal (push) Has been cancelled

This commit is contained in:
Matthew Rich 2024-08-15 08:12:42 -07:00
parent d3495f874e
commit 06f3247b08
15 changed files with 500 additions and 9 deletions

View File

@ -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 }
}

View File

@ -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 )
}
}

View File

@ -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
View 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)
}

View 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)
}

View File

@ -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
View 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)
}

View 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
View 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
View 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
View 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
View 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()
}

View 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
View 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
}

View 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)
}