diff --git a/internal/encoding/decoder.go b/internal/encoding/decoder.go new file mode 100644 index 0000000..08b1bb6 --- /dev/null +++ b/internal/encoding/decoder.go @@ -0,0 +1,61 @@ +package encoding + +import ( + "sync" +) + +// Decoder decodes the contents of b into a v representation. +// It's primarily used for decoding contents of a file into a map[string]interface{}. +type Decoder interface { + Decode(b []byte, v interface{}) error +} + +const ( + // ErrDecoderNotFound is returned when there is no decoder registered for a format. + ErrDecoderNotFound = encodingError("decoder not found for this format") + + // ErrDecoderFormatAlreadyRegistered is returned when an decoder is already registered for a format. + ErrDecoderFormatAlreadyRegistered = encodingError("decoder already registered for this format") +) + +// DecoderRegistry can choose an appropriate Decoder based on the provided format. +type DecoderRegistry struct { + decoders map[string]Decoder + + mu sync.RWMutex +} + +// NewDecoderRegistry returns a new, initialized DecoderRegistry. +func NewDecoderRegistry() *DecoderRegistry { + return &DecoderRegistry{ + decoders: make(map[string]Decoder), + } +} + +// RegisterDecoder registers a Decoder for a format. +// Registering a Decoder for an already existing format is not supported. +func (e *DecoderRegistry) RegisterDecoder(format string, enc Decoder) error { + e.mu.Lock() + defer e.mu.Unlock() + + if _, ok := e.decoders[format]; ok { + return ErrDecoderFormatAlreadyRegistered + } + + e.decoders[format] = enc + + return nil +} + +// Decode calls the underlying Decoder based on the format. +func (e *DecoderRegistry) Decode(format string, b []byte, v interface{}) error { + e.mu.RLock() + decoder, ok := e.decoders[format] + e.mu.RUnlock() + + if !ok { + return ErrDecoderNotFound + } + + return decoder.Decode(b, v) +} diff --git a/internal/encoding/decoder_test.go b/internal/encoding/decoder_test.go new file mode 100644 index 0000000..80e6688 --- /dev/null +++ b/internal/encoding/decoder_test.go @@ -0,0 +1,77 @@ +package encoding + +import ( + "testing" +) + +type decoder struct { + v interface{} +} + +func (d decoder) Decode(_ []byte, v interface{}) error { + rv := v.(*string) + *rv = d.v.(string) + + return nil +} + +func TestDecoderRegistry_RegisterDecoder(t *testing.T) { + t.Run("OK", func(t *testing.T) { + registry := NewDecoderRegistry() + + err := registry.RegisterDecoder("myformat", decoder{}) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("AlreadyRegistered", func(t *testing.T) { + registry := NewDecoderRegistry() + + err := registry.RegisterDecoder("myformat", decoder{}) + if err != nil { + t.Fatal(err) + } + + err = registry.RegisterDecoder("myformat", decoder{}) + if err != ErrDecoderFormatAlreadyRegistered { + t.Fatalf("expected ErrDecoderFormatAlreadyRegistered, got: %v", err) + } + }) +} + +func TestDecoderRegistry_Decode(t *testing.T) { + t.Run("OK", func(t *testing.T) { + registry := NewDecoderRegistry() + decoder := decoder{ + v: "decoded value", + } + + err := registry.RegisterDecoder("myformat", decoder) + if err != nil { + t.Fatal(err) + } + + var v string + + err = registry.Decode("myformat", []byte("some value"), &v) + if err != nil { + t.Fatal(err) + } + + if v != "decoded value" { + t.Fatalf("expected 'decoded value', got: %#v", v) + } + }) + + t.Run("DecoderNotFound", func(t *testing.T) { + registry := NewDecoderRegistry() + + var v string + + err := registry.Decode("myformat", []byte("some value"), &v) + if err != ErrDecoderNotFound { + t.Fatalf("expected ErrDecoderNotFound, got: %v", err) + } + }) +} diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go new file mode 100644 index 0000000..82c7996 --- /dev/null +++ b/internal/encoding/encoder.go @@ -0,0 +1,60 @@ +package encoding + +import ( + "sync" +) + +// Encoder encodes the contents of v into a byte representation. +// It's primarily used for encoding a map[string]interface{} into a file format. +type Encoder interface { + Encode(v interface{}) ([]byte, error) +} + +const ( + // ErrEncoderNotFound is returned when there is no encoder registered for a format. + ErrEncoderNotFound = encodingError("encoder not found for this format") + + // ErrEncoderFormatAlreadyRegistered is returned when an encoder is already registered for a format. + ErrEncoderFormatAlreadyRegistered = encodingError("encoder already registered for this format") +) + +// EncoderRegistry can choose an appropriate Encoder based on the provided format. +type EncoderRegistry struct { + encoders map[string]Encoder + + mu sync.RWMutex +} + +// NewEncoderRegistry returns a new, initialized EncoderRegistry. +func NewEncoderRegistry() *EncoderRegistry { + return &EncoderRegistry{ + encoders: make(map[string]Encoder), + } +} + +// RegisterEncoder registers an Encoder for a format. +// Registering a Encoder for an already existing format is not supported. +func (e *EncoderRegistry) RegisterEncoder(format string, enc Encoder) error { + e.mu.Lock() + defer e.mu.Unlock() + + if _, ok := e.encoders[format]; ok { + return ErrEncoderFormatAlreadyRegistered + } + + e.encoders[format] = enc + + return nil +} + +func (e *EncoderRegistry) Encode(format string, v interface{}) ([]byte, error) { + e.mu.RLock() + encoder, ok := e.encoders[format] + e.mu.RUnlock() + + if !ok { + return nil, ErrEncoderNotFound + } + + return encoder.Encode(v) +} diff --git a/internal/encoding/encoder_test.go b/internal/encoding/encoder_test.go new file mode 100644 index 0000000..e2472ad --- /dev/null +++ b/internal/encoding/encoder_test.go @@ -0,0 +1,70 @@ +package encoding + +import ( + "testing" +) + +type encoder struct { + b []byte +} + +func (e encoder) Encode(_ interface{}) ([]byte, error) { + return e.b, nil +} + +func TestEncoderRegistry_RegisterEncoder(t *testing.T) { + t.Run("OK", func(t *testing.T) { + registry := NewEncoderRegistry() + + err := registry.RegisterEncoder("myformat", encoder{}) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("AlreadyRegistered", func(t *testing.T) { + registry := NewEncoderRegistry() + + err := registry.RegisterEncoder("myformat", encoder{}) + if err != nil { + t.Fatal(err) + } + + err = registry.RegisterEncoder("myformat", encoder{}) + if err != ErrEncoderFormatAlreadyRegistered { + t.Fatalf("expected ErrEncoderFormatAlreadyRegistered, got: %v", err) + } + }) +} + +func TestEncoderRegistry_Decode(t *testing.T) { + t.Run("OK", func(t *testing.T) { + registry := NewEncoderRegistry() + encoder := encoder{ + b: []byte("encoded value"), + } + + err := registry.RegisterEncoder("myformat", encoder) + if err != nil { + t.Fatal(err) + } + + b, err := registry.Encode("myformat", "some value") + if err != nil { + t.Fatal(err) + } + + if string(b) != "encoded value" { + t.Fatalf("expected 'encoded value', got: %#v", string(b)) + } + }) + + t.Run("EncoderNotFound", func(t *testing.T) { + registry := NewEncoderRegistry() + + _, err := registry.Encode("myformat", "some value") + if err != ErrEncoderNotFound { + t.Fatalf("expected ErrEncoderNotFound, got: %v", err) + } + }) +} diff --git a/internal/encoding/error.go b/internal/encoding/error.go new file mode 100644 index 0000000..e4cde02 --- /dev/null +++ b/internal/encoding/error.go @@ -0,0 +1,7 @@ +package encoding + +type encodingError string + +func (e encodingError) Error() string { + return string(e) +}