Skip to content

Commit

Permalink
add line comment for service config
Browse files Browse the repository at this point in the history
  • Loading branch information
mkideal committed Aug 11, 2024
1 parent a637434 commit 8dccd4d
Show file tree
Hide file tree
Showing 4 changed files with 473 additions and 7 deletions.
35 changes: 34 additions & 1 deletion service/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package service

import (
"bufio"
"bytes"
"encoding/json"
"errors"
Expand Down Expand Up @@ -52,7 +53,11 @@ func (c *Config[T]) load(source string) error {
return err
}

return json.NewDecoder(r).Decode(c)
if data, err := stripJSONComments(r); err != nil {
return err
} else {
return json.Unmarshal(data, c)
}
}

// loadFromHTTP loads the configuration from an HTTP source.
Expand Down Expand Up @@ -138,3 +143,31 @@ func (c Config[T]) output() {

fmt.Fprint(os.Stdout, buf.String())
}

func stripJSONComments(r io.Reader) ([]byte, error) {
var buf bytes.Buffer
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Bytes()
trimmed := bytes.TrimSpace(line)
if !bytes.HasPrefix(trimmed, []byte("//")) {
_, err := buf.Write(line)
if err != nil {
return nil, err
}
err = buf.WriteByte('\n')
if err != nil {
return nil, err
}
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
// Remove the last newline if it exists
bytes := buf.Bytes()
if len(bytes) > 0 && bytes[len(bytes)-1] == '\n' {
bytes = bytes[:len(bytes)-1]
}
return bytes, nil
}
116 changes: 116 additions & 0 deletions service/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,119 @@ func ExampleConfig_output() {
// ]
// }
}

func TestStripJSONComments(t *testing.T) {
tests := []struct {
name string
input string
expected string
wantErr bool
}{
{
name: "Basic comment removal",
input: `{
// This is a comment
"name": "John",
"age": 30 // This is an inline comment
}`,
expected: `{
"name": "John",
"age": 30 // This is an inline comment
}`,
wantErr: false,
},
{
name: "Multiple comments",
input: `{
// Comment 1
"a": 1,
// Comment 2
"b": 2,
// Comment 3
"c": 3
}`,
expected: `{
"a": 1,
"b": 2,
"c": 3
}`,
wantErr: false,
},
{
name: "Empty input",
input: "",
expected: "",
wantErr: false,
},
{
name: "Only comments",
input: `// Comment 1
// Comment 2
// Comment 3`,
expected: "",
wantErr: false,
},
{
name: "Comments with varying indentation",
input: `{
"a": 1,
// Indented comment
// More indented comment
"b": 2
}`,
expected: `{
"a": 1,
"b": 2
}`,
wantErr: false,
},
{
name: "Preserve strings with //",
input: `{
"url": "https://example.com",
"comment": "This string contains // which is not a comment"
}`,
expected: `{
"url": "https://example.com",
"comment": "This string contains // which is not a comment"
}`,
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reader := strings.NewReader(tt.input)
result, err := stripJSONComments(reader)

if (err != nil) != tt.wantErr {
t.Errorf("stripJSONComments() error = %v, wantErr %v", err, tt.wantErr)
return
}

if string(result) != tt.expected {
t.Errorf("stripJSONComments() = %v, want %v", string(result), tt.expected)
}
})
}
}

// TestStripJSONCommentsError tests the error handling of stripJSONComments
func TestStripJSONCommentsError(t *testing.T) {
// Create a reader that always returns an error
errReader := &rrrorReader{Err: io.ErrUnexpectedEOF}

_, err := stripJSONComments(errReader)
if err == nil {
t.Errorf("stripJSONComments() error = nil, wantErr = true")
}
}

// rrrorReader is a custom io.Reader that always returns an error
type rrrorReader struct {
Err error
}

func (er *rrrorReader) Read(p []byte) (n int, err error) {
return 0, er.Err
}
15 changes: 9 additions & 6 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"flag"
"fmt"
"io"
"log/slog"
"os"
"strings"
Expand Down Expand Up @@ -38,6 +39,7 @@ type BaseService[T any] struct {
enableTemplate bool // enable template parsing for components config
}
versionFunc func()
stderr io.Writer

config Config[T]
components *component.Group
Expand All @@ -49,6 +51,7 @@ func NewBaseService[T any](config Config[T]) *BaseService[T] {
versionFunc: builder.PrintInfo,
config: config,
components: component.NewGroup(),
stderr: os.Stderr,
}
}

Expand Down Expand Up @@ -98,7 +101,7 @@ func (s *BaseService[T]) setupCommandLineFlags() error {
fmt.Fprintf(&sb, " %s -T app.json\n", name)
fmt.Fprintf(&sb, " %s -p -T app.json\n", name)
fmt.Fprintf(&sb, " %s -t -T app.json\n", name)
fmt.Fprint(os.Stderr, sb.String())
fmt.Fprint(s.stderr, sb.String())
}

flag.BoolVar(&s.flags.version, "v", false, "")
Expand All @@ -116,12 +119,12 @@ func (s *BaseService[T]) setupCommandLineFlags() error {
}

if flag.NArg() == 0 || flag.Arg(0) == "" {
fmt.Fprintf(os.Stderr, "No config source specified!\n\n")
fmt.Fprintf(s.stderr, "No config source specified!\n\n")
flag.Usage()
return errkit.NewExitError(2)
}
if flag.NArg() > 1 {
fmt.Fprintf(os.Stderr, "Too many arguments!\n\n")
fmt.Fprintf(s.stderr, "Too many arguments!\n\n")
flag.Usage()
return errkit.NewExitError(2)
}
Expand All @@ -143,7 +146,7 @@ func (s *BaseService[T]) setupConfig() error {

// Init implements the Service Init method, setting up logging and initializing components.
func (s *BaseService[T]) Init(ctx context.Context) error {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
slog.SetDefault(slog.New(slog.NewTextHandler(s.stderr, &slog.HandlerOptions{
Level: slog.LevelWarn,
})))

Expand All @@ -155,7 +158,7 @@ func (s *BaseService[T]) Init(ctx context.Context) error {

if s.flags.printConfig {
if err != nil {
fmt.Fprintf(os.Stderr, "Config test failed: %v\n", err)
fmt.Fprintf(s.stderr, "Config test failed: %v\n", err)
return errkit.NewExitError(2, err.Error())
}
s.config.output()
Expand All @@ -168,7 +171,7 @@ func (s *BaseService[T]) Init(ctx context.Context) error {

if s.flags.testConfig {
if err != nil {
fmt.Fprintf(os.Stderr, "Config test failed: %v\n", err)
fmt.Fprintf(s.stderr, "Config test failed: %v\n", err)
err = errkit.NewExitError(2, err.Error())
} else {
fmt.Fprintln(os.Stdout, "Config test successful")
Expand Down
Loading

0 comments on commit 8dccd4d

Please sign in to comment.