Skip to content

Commit

Permalink
common: add ReadFileToStruct function
Browse files Browse the repository at this point in the history
  • Loading branch information
Tommi2Day committed Nov 25, 2024
1 parent 160ce59 commit 9d04cd8
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 72 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Go Library

# [v1.15.0 - 2024-11-25]
### New
- common: add ReadFileToStruct function and tests
### Changed
- update dependencies

## [v1.14.11 - 2024-11-08]
### New
- common: add ReadStdinToString and ReadStdinByLine functions and tests
Expand Down
35 changes: 35 additions & 0 deletions common/file_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ package common

import (
"bufio"
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"os"
"path/filepath"
"strings"

log "github.com/sirupsen/logrus"
"golang.org/x/net/html/charset"
"gopkg.in/yaml.v3"
)

// ReadFileToString read a file and return a string
Expand Down Expand Up @@ -104,6 +109,36 @@ func ReadStdinByLine() ([]string, error) {
return lines, err
}

// ReadFileToStruct reads a file or from stdin and fills a given struct
func ReadFileToStruct(filename string, data any) (err error) {
content := ""
if filename == "-" {
content, err = ReadStdinToString()
} else {
content, err = ReadFileToString(filename)
}
if err != nil {
err = fmt.Errorf("error reading input: %v", err)
return
}
content = strings.TrimLeft(content, "\n\t ")
c := []byte(content)
switch {
case strings.HasPrefix(content, "{"):
err = json.Unmarshal(c, data)
case strings.HasPrefix(content, "<"):
decoder := xml.NewDecoder(bytes.NewReader(c))
decoder.CharsetReader = charset.NewReaderLabel
err = decoder.Decode(data)
default:
err = yaml.Unmarshal(c, data)
}
if err != nil {
err = fmt.Errorf("error parsing input: %v", err)
}
return
}

// ChdirToFile change working directory to the filename
func ChdirToFile(file string) error {
a, _ := filepath.Abs(file)
Expand Down
201 changes: 201 additions & 0 deletions common/file_helper_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

import (
"encoding/xml"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -331,3 +332,203 @@ func TestReadStdinToString(t *testing.T) {
assert.Empty(t, result)
})
}
func TestReadFileToStruct(t *testing.T) {
test.InitTestDirs()
type TestStruct struct {
XMLName xml.Name `xml:"test"`
Name string `json:"name" yaml:"name" xml:"name"`
Value int `json:"value" yaml:"value" xml:"value"`
Enabled bool `json:"enabled" yaml:"enabled" xml:"enabled,attr"`
}
tests := []struct {
name string
content string
filename string
wantErr bool
data interface{}
}{
{
name: "Valid JSON file",
filename: test.TestData + "/valid.json",
content: `{"name": "test", "value": 10, "enabled": true}`,
data: &TestStruct{},
wantErr: false,
},
{
name: "Valid YAML file",
filename: test.TestData + "/valid.yaml",
content: "name: test\nvalue: 10\nenabled: true",
data: &TestStruct{},
wantErr: false,
},
{
name: "Valid XML file",
filename: test.TestData + "/valid.xml",
content: `<test enabled="true">\n<name>test</name>\n<value>10</value>\n</test>`,
data: &TestStruct{},
wantErr: false,
},

{
name: "invalid json",
filename: test.TestData + "/invalid.json",
content: `{"name": "test", "value": invalid, "enabled": true}`,
data: &TestStruct{},
wantErr: true,
},
{
name: "invalid yaml",
filename: test.TestData + "/invalid.yaml",
content: "name: test\nvalue: 'invalid': true",
data: &TestStruct{},
wantErr: true,
},
{
name: "invalid xml",
filename: test.TestData + "/invalid.xml",
content: `<test><name>test</name><value>invalid</invalid></test>`,
data: &TestStruct{},
wantErr: true,
},
{
name: "nil pointer",
filename: test.TestData + "/invalid.xml",
content: `<test><name>test</name><value>10</invalid></test>`,
data: nil,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = os.Remove(tt.filename)
err := WriteStringToFile(tt.filename, tt.content)
if err != nil {
t.Errorf("error writing file: %v", err)
return
}
val := tt.data
err = ReadFileToStruct(tt.filename, val)
if (err != nil) != tt.wantErr {
t.Errorf("ReadFileToStruct() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
assert.NotNil(t, val)
d, ok := val.(*TestStruct)
assert.True(t, ok)
if ok {
assert.Equal(t, "test", d.Name)
assert.Equal(t, 10, d.Value)
assert.Equal(t, true, d.Enabled)
}
}
})
}
}

func TestReadFileToStructComplex(t *testing.T) {
test.InitTestDirs()
type Address struct {
Street string `json:"street,omitempty" yaml:"street,omitempty" xml:"street,omitempty"`
City string `json:"city" yaml:"city" xml:"city"`
PostalCode string `json:"postal_code,omitempty" yaml:"postal_code,omitempty" xml:"postal_code,omitempty"`
}

type Person struct {
FirstName string `json:"first_name,omitempty" yaml:"first_name,omitempty" xml:"first_name,omitempty"`
LastName string `json:"last_name" yaml:"last_name" xml:"last_name"`
Age int `json:"age,omitempty" yaml:"age,omitempty" xml:"age,omitempty"`
Address Address `json:"address" yaml:"address" xml:"address"`
}
expected := &Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "Anytown",
PostalCode: "12345",
},
}
tests := []struct {
name string
content string
filename string
wantErr bool
data interface{}
expected *Person
}{
{
name: "Valid JSON file",
filename: test.TestData + "/valid2.json",
content: `{
"first_name": "John",
"last_name": "Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown",
"postal_code": "12345"
}
}`,
data: &Person{},
wantErr: false,
expected: expected,
},
{
name: "Valid YAML file",
filename: test.TestData + "/valid2.yaml",
content: `---
first_name: John
last_name: Doe
age: 30
address:
street: "123 Main St"
city: "Anytown"
postal_code: "12345"
`,
data: &Person{},
wantErr: false,
expected: expected,
},
{
name: "Valid XML file",
filename: test.TestData + "/valid2.xml",
content: `<?xml version="1.0" encoding="UTF-8"?>
<person>
<first_name>John</first_name>
<last_name>Doe</last_name>
<age>30</age>
<address>
<street>123 Main St</street>
<city>Anytown</city>
<postal_code>12345</postal_code>
</address>
</person>`,
data: &Person{},
wantErr: false,
expected: expected,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = os.Remove(tt.filename)
err := WriteStringToFile(tt.filename, tt.content)
if err != nil {
t.Errorf("error writing file: %v", err)
return
}
val := tt.data
err = ReadFileToStruct(tt.filename, val)
if (err != nil) != tt.wantErr {
t.Errorf("ReadFileToStruct() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
assert.NotNil(t, val)
assert.Equal(t, tt.expected, val)
}
})
}
}
44 changes: 22 additions & 22 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,31 @@ toolchain go1.22.5

require (
github.com/Luzifer/go-openssl/v4 v4.2.2
github.com/ProtonMail/go-crypto v1.1.2
github.com/aws/aws-sdk-go-v2 v1.32.4
github.com/aws/aws-sdk-go-v2/config v1.28.3
github.com/aws/aws-sdk-go-v2/service/kms v1.37.5
github.com/aws/smithy-go v1.22.0
github.com/ProtonMail/go-crypto v1.1.3
github.com/aws/aws-sdk-go-v2 v1.32.5
github.com/aws/aws-sdk-go-v2/config v1.28.5
github.com/aws/aws-sdk-go-v2/service/kms v1.37.6
github.com/aws/smithy-go v1.22.1
github.com/emersion/go-imap v1.2.1
github.com/emersion/go-message v0.18.1
github.com/glebarez/go-sqlite v1.22.0
github.com/go-git/go-git/v5 v5.12.0
github.com/go-ldap/ldap/v3 v3.4.8
github.com/go-resty/resty/v2 v2.15.3
github.com/go-resty/resty/v2 v2.16.2
github.com/hashicorp/vault/api v1.15.0
github.com/jarcoal/httpmock v1.3.1
github.com/jmoiron/sqlx v1.4.0
github.com/ory/dockertest/v3 v3.11.0
github.com/sijms/go-ora/v2 v2.8.22
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
github.com/wneessen/go-mail v0.5.2
github.com/xdg-go/scram v1.1.2
github.com/xlzd/gotp v0.1.0
golang.org/x/net v0.31.0
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -38,19 +39,19 @@ require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.44 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.46 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.32.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/containerd/continuity v0.4.4 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/cyphar/filepath-securejoin v0.3.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v27.3.1+incompatible // indirect
Expand Down Expand Up @@ -90,7 +91,7 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.2.1 // indirect
github.com/opencontainers/runc v1.2.2 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
Expand All @@ -106,15 +107,14 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/time v0.8.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.61.0 // indirect
modernc.org/libc v1.61.2 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.33.1 // indirect
modernc.org/sqlite v1.34.1 // indirect
)
Loading

0 comments on commit 9d04cd8

Please sign in to comment.