Skip to content

Commit

Permalink
remove toml, yaml marshler and unmarshaler
Browse files Browse the repository at this point in the history
  • Loading branch information
mkideal committed Aug 12, 2024
1 parent 1640cb9 commit 15613a2
Show file tree
Hide file tree
Showing 8 changed files with 38 additions and 243 deletions.
45 changes: 2 additions & 43 deletions component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import (
"fmt"
"log/slog"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"unicode"

"github.com/gopherd/core/encoding"
"github.com/gopherd/core/lifecycle"
"github.com/gopherd/core/types"
)
Expand Down Expand Up @@ -66,9 +64,6 @@ type Container interface {
// GetComponent returns a component by its UUID.
GetComponent(uuid string) Component

// Decoder returns the decoder for decoding component configurations.
Decoder() encoding.Decoder

// Logger returns the logger instance for the container.
Logger() *slog.Logger
}
Expand Down Expand Up @@ -126,7 +121,7 @@ func (c *BaseComponent[T]) Setup(container Container, config Config) error {
c.identifier = config.Name
}

if err := config.Options.Decode(c.container.Decoder(), &c.options); err != nil {
if err := config.Options.Decode(json.Unmarshal, &c.options); err != nil {
return fmt.Errorf("failed to unmarshal options: %w", err)
}
if loaded, ok := any(&c.options).(interface {
Expand Down Expand Up @@ -173,42 +168,6 @@ func (r *Reference[T]) UnmarshalJSON(data []byte) error {
return r.validate()
}

// MarshalTOML marshals the referenced component UUID to TOML.
func (r Reference[T]) MarshalTOML() ([]byte, error) {
return r.Marshal()
}

// UnmarshalTOML unmarshals the referenced component UUID from TOML.
func (r *Reference[T]) UnmarshalTOML(data []byte) error {
return r.Unmarshal(data)
}

// MarshalYAML marshals the referenced component UUID to YAML.
func (r Reference[T]) MarshalYAML() ([]byte, error) {
return r.Marshal()
}

// UnmarshalYAML unmarshals the referenced component UUID from YAML.
func (r *Reference[T]) UnmarshalYAML(data []byte) error {
return r.Unmarshal(data)
}

// Marshal marshals the referenced component UUID to quoted bytes.
func (r Reference[T]) Marshal() ([]byte, error) {
var buf = make([]byte, 0, len(r.uuid)+len(`""`))
return strconv.AppendQuote(buf, r.uuid), nil
}

// Unmarshal unmarshals the referenced component UUID from quoted bytes.
func (r *Reference[T]) Unmarshal(data []byte) error {
s, err := encoding.UnmarshalString(data, '\'', false)
if err != nil {
return err
}
r.uuid = s
return r.validate()
}

func (r Reference[T]) validate() error {
if strings.ContainsFunc(r.uuid, unicode.IsSpace) {
return fmt.Errorf("unexpected whitespace in reference UUID: %q", r.uuid)
Expand Down Expand Up @@ -266,7 +225,7 @@ func (c *BaseComponentWithRefs[T, R]) Setup(container Container, config Config)
if err := c.BaseComponent.Setup(container, config); err != nil {
return err
}
if err := config.Refs.Decode(c.container.Decoder(), &c.refs); err != nil {
if err := config.Refs.Decode(json.Unmarshal, &c.refs); err != nil {
return fmt.Errorf("failed to unmarshal refs: %w", err)
}
return c.resolveRefs(container)
Expand Down
5 changes: 0 additions & 5 deletions component/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"testing"

"github.com/gopherd/core/component"
"github.com/gopherd/core/encoding"
"github.com/gopherd/core/op"
"github.com/gopherd/core/types"
)
Expand Down Expand Up @@ -93,10 +92,6 @@ func (c *mockContainer) GetComponent(uuid string) component.Component {
return c.components[uuid]
}

func (c *mockContainer) Decoder() encoding.Decoder {
return json.Unmarshal
}

func (c *mockContainer) Logger() *slog.Logger {
return c.logger
}
Expand Down
95 changes: 6 additions & 89 deletions encoding/encoding.go
Original file line number Diff line number Diff line change
@@ -1,100 +1,17 @@
// Package encoding provides interfaces and utilities for encoding and decoding data.
package encoding

import (
"bytes"
"errors"
"strconv"
"strings"
"unicode/utf8"
)

// ErrInvalidString is returned when the input string is invalid.
var ErrInvalidString = errors.New("invalid string")

// Encoder is a function type that encodes a value into bytes.
type Encoder func(any) ([]byte, error)

// Decoder is a function type that decodes bytes into a provided value.
type Decoder func([]byte, any) error

// JSONMarshaler is an interface for types that can marshal themselves to JSON.
type JSONMarshaler interface {
MarshalJSON() ([]byte, error)
}

// JSONUnmarshaler is an interface for types that can unmarshal themselves from JSON.
type JSONUnmarshaler interface {
UnmarshalJSON([]byte) error
}

// TOMLMarshaler is an interface for types that can marshal themselves to TOML.
type TOMLMarshaler interface {
MarshalTOML() ([]byte, error)
}

// TOMLUnmarshaler is an interface for types that can unmarshal themselves from TOML.
type TOMLUnmarshaler interface {
UnmarshalTOML([]byte) error
}

// YAMLMarshaler is an interface for types that can marshal themselves to YAML.
type YAMLMarshaler interface {
MarshalYAML() ([]byte, error)
}

// YAMLUnmarshaler is an interface for types that can unmarshal themselves from YAML.
type YAMLUnmarshaler interface {
UnmarshalYAML([]byte) error
}

// UnmarshalString decodes a string from byte slice data.
// It supports both quoted strings and literal strings.
//
// Parameters:
// - data: The byte slice containing the string to unmarshal.
// - literalChar: The character used for literal strings. Use 0 for no literal strings.
// - allowNewline: Whether newlines are allowed in literal strings.
//
// Returns:
// - The unmarshaled string and nil error if successful.
// - An empty string and an error if unmarshaling fails.
func UnmarshalString(data []byte, literalChar byte, allowNewline bool) (string, error) {
data = bytes.TrimSpace(data)
if len(data) < 2 {
return "", errors.New("string too short")
}

switch data[0] {
case '"':
return unquoteString(data)
case literalChar:
if literalChar == 0 || literalChar == '"' {
break
}
return extractLiteralString(data, literalChar, allowNewline)
}

return "", ErrInvalidString
}

func unquoteString(data []byte) (string, error) {
if data[len(data)-1] != '"' {
return "", errors.New("mismatched quotes")
}
return strconv.Unquote(string(data))
}

func extractLiteralString(data []byte, literalChar byte, allowNewline bool) (string, error) {
if data[len(data)-1] != literalChar {
return "", errors.New("mismatched quotes")
}
str := string(data[1 : len(data)-1])
if !allowNewline && strings.ContainsRune(str, '\n') {
return "", errors.New("newlines not allowed in literal string")
}
if !utf8.ValidString(str) {
return "", errors.New("invalid UTF-8 in string")
// Transform decodes data using the provided decoder, then encodes it using the provided encoder.
func Transform(data []byte, encoder Encoder, decoder Decoder) ([]byte, error) {
var v = make(map[string]any)
if err := decoder(data, &v); err != nil {
return nil, err
}
return str, nil
return encoder(v)
}
46 changes: 0 additions & 46 deletions encoding/encoding_test.go

This file was deleted.

28 changes: 24 additions & 4 deletions service/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type Config[T any] struct {

// load processes the configuration based on the provided source.
// It returns an error if the configuration cannot be loaded or decoded.
func (c *Config[T]) load(decoder encoding.Decoder, source string, isJSONC bool) error {
func (c *Config[T]) load(decoder encoding.Decoder, source string) error {
if source == "" {
return nil
}
Expand Down Expand Up @@ -55,7 +55,7 @@ func (c *Config[T]) load(decoder encoding.Decoder, source string, isJSONC bool)
}

var data []byte
if isJSONC {
if decoder == nil {
data, err = stripJSONComments(r)
if err != nil {
return err
Expand All @@ -65,8 +65,17 @@ func (c *Config[T]) load(decoder encoding.Decoder, source string, isJSONC bool)
if err != nil {
return fmt.Errorf("read config data failed: %w", err)
}
data, err = encoding.Transform(data, json.Marshal, decoder)
if err != nil {
return fmt.Errorf("decode config failed: %w", err)
}
}
return decoder(data, c)

if err := json.Unmarshal(data, c); err != nil {
return fmt.Errorf("unmarshal config failed: %w", err)
}

return nil
}

// loadFromHTTP loads the configuration from an HTTP source.
Expand Down Expand Up @@ -140,7 +149,18 @@ func (c *Config[T]) processTemplate(enableTemplate bool, source string) error {
// output encodes the configuration with the encoder and writes it to stdout.
// It uses indentation for better readability.
func (c Config[T]) output(encoder encoding.Encoder) {
if data, err := encoder(c); err != nil {
if encoder == nil {
if data, err := jsonIdentEncoder(c); err != nil {
fmt.Fprintf(os.Stderr, "Encode config failed: %v\n", err)
} else {
fmt.Fprint(os.Stdout, string(data))
}
return
}

if data, err := json.Marshal(c); err != nil {
fmt.Fprintf(os.Stderr, "Encode config failed: %v\n", err)
} else if data, err = encoding.Transform(data, encoder, json.Unmarshal); err != nil {
fmt.Fprintf(os.Stderr, "Encode config failed: %v\n", err)
} else {
fmt.Fprint(os.Stdout, string(data))
Expand Down
6 changes: 3 additions & 3 deletions service/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func TestConfig_Load(t *testing.T) {
}

c := &Config[TestContext]{}
err := c.load(json.Unmarshal, tt.source, true)
err := c.load(json.Unmarshal, tt.source)

if (err != nil) != tt.wantErr {
t.Errorf("load() error = %v, wantErr %v", err, tt.wantErr)
Expand Down Expand Up @@ -341,7 +341,7 @@ func TestConfig_Output(t *testing.T) {
r, w, _ := os.Pipe()
os.Stdout = w

config.output(jsonIdentEncoder)
config.output(nil)

w.Close()
os.Stdout = oldStdout
Expand Down Expand Up @@ -378,7 +378,7 @@ func ExampleConfig_output() {
},
}

config.output(jsonIdentEncoder)
config.output(nil)
// Output:
// {
// "Context": {
Expand Down
Loading

0 comments on commit 15613a2

Please sign in to comment.