From 15613a2d75fdc731cc840d3f507fe259c995ed69 Mon Sep 17 00:00:00 2001 From: wangjin Date: Mon, 12 Aug 2024 17:51:51 +0800 Subject: [PATCH] remove toml, yaml marshler and unmarshaler --- component/component.go | 45 +----------------- component/component_test.go | 5 -- encoding/encoding.go | 95 +++---------------------------------- encoding/encoding_test.go | 46 ------------------ service/config.go | 28 +++++++++-- service/config_test.go | 6 +-- service/service.go | 36 ++------------ types/types.go | 20 -------- 8 files changed, 38 insertions(+), 243 deletions(-) delete mode 100644 encoding/encoding_test.go diff --git a/component/component.go b/component/component.go index 2a90748..b1a71d0 100644 --- a/component/component.go +++ b/component/component.go @@ -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" ) @@ -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 } @@ -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 { @@ -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) @@ -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) diff --git a/component/component_test.go b/component/component_test.go index f8ed727..0fb7415 100644 --- a/component/component_test.go +++ b/component/component_test.go @@ -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" ) @@ -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 } diff --git a/encoding/encoding.go b/encoding/encoding.go index 943da16..61d75bb 100644 --- a/encoding/encoding.go +++ b/encoding/encoding.go @@ -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) } diff --git a/encoding/encoding_test.go b/encoding/encoding_test.go deleted file mode 100644 index 72e91c1..0000000 --- a/encoding/encoding_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package encoding - -import ( - "testing" -) - -func TestUnmarshalString(t *testing.T) { - tests := []struct { - name string - input []byte - literalChar byte - allowNewline bool - want string - wantErr bool - }{ - {"Empty input", []byte{}, 0, false, "", true}, - {"Single character", []byte{'"'}, 0, false, "", true}, - {"Valid quoted string", []byte(`"hello"`), 0, false, "hello", false}, - {"Valid literal string", []byte(`'hello'`), '\'', false, "hello", false}, - {"Quoted string with escapes", []byte(`"he\"llo"`), 0, false, `he"llo`, false}, - {"Literal string with newline allowed", []byte("'hello\nworld'"), '\'', true, "hello\nworld", false}, - {"Literal string with newline disallowed", []byte("'hello\nworld'"), '\'', false, "", true}, - {"Invalid UTF-8 in literal string", []byte{'\'', 0xFF, '\''}, '\'', false, "", true}, - {"Mismatched quotes in quoted string", []byte(`"hello`), 0, false, "", true}, - {"Mismatched quotes in literal string", []byte(`'hello`), '\'', false, "", true}, - {"Invalid string start", []byte(`hello`), 0, false, "", true}, - {"Quoted string with literal char", []byte(`"'hello'"`), '\'', false, "'hello'", false}, - {"Literal string with quote char", []byte(`'"hello"'`), '\'', false, `"hello"`, false}, - {"Literal char is quote", []byte(`"hello"`), '"', false, "hello", false}, - {"Whitespace before valid string", []byte(" 'hello'"), '\'', false, "hello", false}, - {"Whitespace after valid string", []byte("'hello' "), '\'', false, "hello", false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := UnmarshalString(tt.input, tt.literalChar, tt.allowNewline) - if (err != nil) != tt.wantErr { - t.Errorf("UnmarshalString() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("UnmarshalString() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/service/config.go b/service/config.go index 33011cf..3e8e482 100644 --- a/service/config.go +++ b/service/config.go @@ -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 } @@ -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 @@ -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. @@ -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)) diff --git a/service/config_test.go b/service/config_test.go index 0ef38d8..f9ddd27 100644 --- a/service/config_test.go +++ b/service/config_test.go @@ -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) @@ -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 @@ -378,7 +378,7 @@ func ExampleConfig_output() { }, } - config.output(jsonIdentEncoder) + config.output(nil) // Output: // { // "Context": { diff --git a/service/service.go b/service/service.go index dd7f0b2..8ebd446 100644 --- a/service/service.go +++ b/service/service.go @@ -6,7 +6,6 @@ package service import ( "context" - "encoding/json" "flag" "fmt" "io" @@ -44,7 +43,6 @@ type BaseService[T any] struct { stderr io.Writer encoder encoding.Encoder decoder encoding.Decoder - isJSONC bool config Config[T] components *component.Group @@ -55,9 +53,6 @@ func NewBaseService[T any](config Config[T]) *BaseService[T] { return &BaseService[T]{ versionFunc: builder.PrintInfo, stderr: os.Stderr, - encoder: json.Marshal, - decoder: json.Unmarshal, - isJSONC: true, config: config, components: component.NewGroup(), } @@ -74,27 +69,6 @@ func (s *BaseService[T]) GetComponent(uuid string) component.Component { return s.components.GetComponent(uuid) } -// Encoder returns the encoder function for the service. -func (s *BaseService[T]) Encoder() encoding.Encoder { - return s.encoder -} - -// Decoder returns the decoder function for the service. -func (s *BaseService[T]) Decoder() encoding.Decoder { - return s.decoder -} - -// SetEncoder sets the encoder functions for the service. -func (s *BaseService[T]) SetEncoder(encoder encoding.Encoder) { - s.encoder = encoder -} - -// SetDecoder sets the decoder functions for the service. -func (s *BaseService[T]) SetDecoder(decoder encoding.Decoder) { - s.decoder = decoder - s.isJSONC = false -} - // Logger returns the logger instance for the service. func (s *BaseService[T]) Logger() *slog.Logger { return slog.Default() @@ -164,7 +138,7 @@ func (s *BaseService[T]) setupCommandLineFlags() error { // setupConfig loads and sets up the service configuration based on command-line flags. func (s *BaseService[T]) setupConfig() error { - if err := s.config.load(s.decoder, s.flags.source, s.isJSONC); err != nil { + if err := s.config.load(s.decoder, s.flags.source); err != nil { return err } if err := s.config.processTemplate(s.flags.enableTemplate, s.flags.source); err != nil { @@ -290,12 +264,8 @@ func Run(opts ...RunOption) { var o runOptions o.apply(opts) s := NewBaseService(Config[context]{Context: context{}}) - if o.encoder != nil { - s.SetEncoder(o.encoder) - } - if o.decoder != nil { - s.SetDecoder(o.decoder) - } + s.encoder = o.encoder + s.decoder = o.decoder if err := RunService(s); err != nil { if exitCode, ok := errkit.ExitCode(err); ok { os.Exit(exitCode) diff --git a/types/types.go b/types/types.go index aea5f4c..9f9189a 100644 --- a/types/types.go +++ b/types/types.go @@ -70,26 +70,6 @@ func (o *RawObject) UnmarshalJSON(data []byte) error { return o.unmarshal("UnmarshalJSON", data) } -// MarshalTOML implements the encoding.TOMLMarshaler interface. -func (o RawObject) MarshalTOML() ([]byte, error) { - return o.marshal([]byte("{}")) -} - -// UnmarshalTOML implements the encoding.TOMLUnmarshaler interface. -func (o *RawObject) UnmarshalTOML(data []byte) error { - return o.unmarshal("UnmarshalTOML", data) -} - -// MarshalYAML implements the encoding.YAMLMarshaler interface. -func (o RawObject) MarshalYAML() ([]byte, error) { - return o.marshal([]byte("null")) -} - -// UnmarshalYAML implements the encoding.YAMLUnmarshaler interface. -func (o *RawObject) UnmarshalYAML(data []byte) error { - return o.unmarshal("UnmarshalYAML", data) -} - // MarshalText implements the encoding.TextMarshaler interface. // It returns the base64 encoding of the Object's data. func (o RawObject) MarshalText() ([]byte, error) {