Skip to content

Commit

Permalink
feat: Simplify Destinations (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbernays authored Jan 24, 2023
1 parent 4a26bed commit 97c02e9
Show file tree
Hide file tree
Showing 10 changed files with 530 additions and 1 deletion.
65 changes: 65 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package filetypes

import (
csvFile "github.com/cloudquery/filetypes/csv"
jsonFile "github.com/cloudquery/filetypes/json"
)

type Client struct {
spec *FileSpec
csv *csvFile.Client
json *jsonFile.Client
csvTransformer *csvFile.Transformer
csvReverseTransformer *csvFile.ReverseTransformer
jsonTransformer *jsonFile.Transformer
jsonReverseTransformer *jsonFile.ReverseTransformer
}

// NewClient creates a new client for the given spec
func NewClient(spec *FileSpec) (*Client, error) {
err := spec.UnmarshalSpec()
if err != nil {
return &Client{}, err
}

spec.SetDefaults()
if err := spec.Validate(); err != nil {
return &Client{}, err
}

switch spec.Format {
case FormatTypeCSV:
opts := []csvFile.Options{
csvFile.WithDelimiter([]rune(spec.csvSpec.Delimiter)[0]),
}
if spec.csvSpec.IncludeHeaders {
opts = append(opts, csvFile.WithHeader())
}

client, err := csvFile.NewClient(opts...)
if err != nil {
return &Client{}, err
}
return &Client{
spec: spec,
csvTransformer: &csvFile.Transformer{},
csvReverseTransformer: &csvFile.ReverseTransformer{},
csv: client,
}, nil

case FormatTypeJSON:
client, err := jsonFile.NewClient()
if err != nil {
return &Client{}, err
}
return &Client{
spec: spec,
jsonTransformer: &jsonFile.Transformer{},
jsonReverseTransformer: &jsonFile.ReverseTransformer{},
json: client,
}, nil

default:
panic("unknown format " + spec.Format)
}
}
21 changes: 21 additions & 0 deletions csv/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package csv

import "fmt"

type Spec struct {
IncludeHeaders bool `json:"include_headers,omitempty"`
Delimiter string `json:"delimiter,omitempty"`
}

func (s *Spec) SetDefaults() {
if s.Delimiter == "" {
s.Delimiter = ","
}
}

func (s *Spec) Validate() error {
if len(s.Delimiter) != 1 {
return fmt.Errorf("delimiter must be a single character")
}
return nil
}
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ module github.com/cloudquery/filetypes

go 1.19

require github.com/cloudquery/plugin-sdk v1.28.0
require (
github.com/cloudquery/plugin-sdk v1.28.0
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.1
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
github.com/ghodss/yaml v1.0.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
Expand Down Expand Up @@ -31,8 +32,13 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw=
github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
Expand All @@ -47,4 +53,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
9 changes: 9 additions & 0 deletions json/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package json

type Spec struct{}

func (*Spec) SetDefaults() {}

func (*Spec) Validate() error {
return nil
}
34 changes: 34 additions & 0 deletions read.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package filetypes

import (
"io"

"github.com/cloudquery/plugin-sdk/schema"
)

func (cl *Client) Read(r io.Reader, table *schema.Table, sourceName string, res chan<- []any) error {
switch cl.spec.Format {
case FormatTypeCSV:
if err := cl.csv.Read(r, table, sourceName, res); err != nil {
return err
}
case FormatTypeJSON:
if err := cl.json.Read(r, table, sourceName, res); err != nil {
return err
}
default:
panic("unknown format " + cl.spec.Format)
}
return nil
}

func (cl *Client) ReverseTransformValues(table *schema.Table, values []any) (schema.CQTypes, error) {
switch cl.spec.Format {
case FormatTypeCSV:
return cl.csvReverseTransformer.ReverseTransformValues(table, values)
case FormatTypeJSON:
return cl.jsonReverseTransformer.ReverseTransformValues(table, values)
default:
panic("unknown format " + cl.spec.Format)
}
}
67 changes: 67 additions & 0 deletions spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package filetypes

import (
"bytes"
"encoding/json"
"fmt"

"github.com/cloudquery/filetypes/csv"
jsonFile "github.com/cloudquery/filetypes/json"
)

type FormatType string

const (
FormatTypeCSV = "csv"
FormatTypeJSON = "json"
)

type FileSpec struct {
Format FormatType `json:"format,omitempty"`
FormatSpec any `json:"format_spec,omitempty"`
csvSpec *csv.Spec
jsonSpec *jsonFile.Spec
}

func (s *FileSpec) SetDefaults() {
switch s.Format {
case FormatTypeCSV:
s.csvSpec.SetDefaults()
case FormatTypeJSON:
s.jsonSpec.SetDefaults()
}
}
func (s *FileSpec) Validate() error {
if s.Format == "" {
return fmt.Errorf("format is required")
}
switch s.Format {
case FormatTypeCSV:
return s.csvSpec.Validate()
case FormatTypeJSON:
return s.jsonSpec.Validate()
default:
return fmt.Errorf("unknown format %s", s.Format)
}
}

func (s *FileSpec) UnmarshalSpec() error {
b, err := json.Marshal(s.FormatSpec)
if err != nil {
return err
}
dec := json.NewDecoder(bytes.NewReader(b))
dec.UseNumber()
dec.DisallowUnknownFields()

switch s.Format {
case FormatTypeCSV:
s.csvSpec = &csv.Spec{}
return dec.Decode(s.csvSpec)
case FormatTypeJSON:
s.jsonSpec = &jsonFile.Spec{}
return dec.Decode(s.jsonSpec)
default:
return fmt.Errorf("unknown format %s", s.Format)
}
}
104 changes: 104 additions & 0 deletions spec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package filetypes

import (
"testing"

"github.com/cloudquery/filetypes/csv"
"github.com/cloudquery/filetypes/json"
"github.com/stretchr/testify/assert"
)

func TestSpecMethods(t *testing.T) {
testCases := []struct {
FileSpec *FileSpec
preDefaultsCSV *csv.Spec
preDefaultsJSON *json.Spec
postDefaultsCSV *csv.Spec
postDefaultsJSON *json.Spec
expectError bool
}{
{
FileSpec: &FileSpec{
Format: FormatTypeCSV,
FormatSpec: map[string]any{},
},
preDefaultsCSV: &csv.Spec{},
postDefaultsCSV: &csv.Spec{
IncludeHeaders: false,
Delimiter: ",",
},
},
{
FileSpec: &FileSpec{
Format: FormatTypeCSV,
FormatSpec: map[string]any{
"delimiter": ",",
"include_headers": true,
},
},
preDefaultsCSV: &csv.Spec{
IncludeHeaders: true,
Delimiter: ",",
},
postDefaultsCSV: &csv.Spec{
IncludeHeaders: true,
Delimiter: ",",
},
},
{
FileSpec: &FileSpec{
Format: FormatTypeCSV,
FormatSpec: map[string]any{},
},
preDefaultsCSV: &csv.Spec{
IncludeHeaders: false,
Delimiter: "",
},
postDefaultsCSV: &csv.Spec{
IncludeHeaders: false,
Delimiter: ",",
},
},
{
FileSpec: &FileSpec{
Format: FormatTypeJSON,
},
preDefaultsJSON: &json.Spec{},
postDefaultsJSON: &json.Spec{},
},

{
FileSpec: &FileSpec{
Format: FormatTypeJSON,
FormatSpec: map[string]any{
"delimiter": ",",
},
},
expectError: true,
},
{
FileSpec: &FileSpec{
Format: FormatTypeCSV,
FormatSpec: map[string]any{
"delimiter22": ",",
},
},
expectError: true,
},
}

for _, tc := range testCases {
err := tc.FileSpec.UnmarshalSpec()
if tc.expectError {
assert.NotNil(t, err)
continue
}
assert.Equal(t, tc.preDefaultsCSV, tc.FileSpec.csvSpec)
assert.Equal(t, tc.preDefaultsJSON, tc.FileSpec.jsonSpec)

tc.FileSpec.SetDefaults()

assert.Equal(t, tc.postDefaultsCSV, tc.FileSpec.csvSpec)
assert.Equal(t, tc.postDefaultsJSON, tc.FileSpec.jsonSpec)
}
}
Loading

0 comments on commit 97c02e9

Please sign in to comment.