messing about with decoding/encoding

This commit is contained in:
tsmethurst 2021-09-07 11:43:47 +02:00
commit 7d371eba3e
22 changed files with 2276 additions and 32 deletions

1
go.mod
View file

@ -35,6 +35,7 @@ require (
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/microcosm-cc/bluemonday v1.0.15
github.com/mitchellh/mapstructure v1.4.1
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646

2
go.sum
View file

@ -345,6 +345,8 @@ github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

View file

@ -0,0 +1,66 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package trans
import (
"crypto/rsa"
"fmt"
"net"
"reflect"
"time"
"github.com/mitchellh/mapstructure"
transmodel "github.com/superseriousbusiness/gotosocial/internal/trans/model"
)
func accountDecode(e transmodel.TransEntry) (*transmodel.Account, error) {
a := &transmodel.Account{}
decoderConfig := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeHookFunc(time.RFC3339),
PrivateKeyHookFunc(),
),
Result: a,
}
decoder, err := mapstructure.NewDecoder(decoderConfig)
if err != nil {
return nil, fmt.Errorf("accountDecode: error creating decoder: %s", err)
}
if err := decoder.Decode(&e); err != nil {
return nil, fmt.Errorf("accountDecode: error decoding account: %s", err)
}
return a, nil
}
var PrivateKeyHookFunc mapstructure.DecodeHookFunc = func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if t != reflect.TypeOf(rsa.PrivateKey{}) {
return data, nil
}
rsa.
// Convert it by parsing
_, net, err := net.ParseCIDR(data.(string))
return net, err
}

View file

@ -21,6 +21,7 @@ package trans
import (
"context"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
)
@ -29,11 +30,13 @@ type Exporter interface {
}
type exporter struct {
db db.DB
db db.DB
log *logrus.Logger
}
func NewExporter(db db.DB) Exporter {
func NewExporter(db db.DB, log *logrus.Logger) Exporter {
return &exporter{
db: db,
log: log,
}
}

View file

@ -19,7 +19,6 @@
package trans
import (
"bufio"
"context"
"encoding/json"
"fmt"
@ -35,26 +34,25 @@ func (e *exporter) ExportMinimal(ctx context.Context, path string) error {
return err
}
w := bufio.NewWriter(f)
encoder := json.NewEncoder(w)
encoder := json.NewEncoder(f)
accounts := []*transmodel.Account{}
if err := e.db.GetWhere(ctx, []db.Where{{Key: "domain", Value: nil}}, &accounts); err != nil {
return fmt.Errorf("error selecting accounts: %s", err)
return fmt.Errorf("ExportMinimal: error selecting accounts: %s", err)
}
for _, a := range accounts {
encoder.Encode(a)
a.Type = transmodel.TransAccount
if err := encoder.Encode(a); err != nil {
return fmt.Errorf("ExportMinimal: error encoding account: %s", err)
}
e.log.Infof("ExportMinimal: exported account %s to %s", a.ID, path)
}
return neatClose(w, f)
return neatClose(f)
}
func neatClose(w *bufio.Writer, f *os.File) error {
if err := w.Flush(); err != nil {
return fmt.Errorf("error flushing writer: %s", err)
}
func neatClose(f *os.File) error {
if err := f.Close(); err != nil {
return fmt.Errorf("error closing file: %s", err)
}

View file

@ -38,7 +38,7 @@ func (suite *ExportMinimalTestSuite) TestExportMinimalOK() {
tempFilePath := fmt.Sprintf("%s/%s", suite.T().TempDir(), uuid.NewString())
// export to the tempFilePath
exporter := trans.NewExporter(suite.db)
exporter := trans.NewExporter(suite.db, suite.log)
err := exporter.ExportMinimal(context.Background(), tempFilePath)
suite.NoError(err)

View file

@ -0,0 +1,42 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package trans
import (
"context"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
)
type Importer interface {
ImportMinimal(ctx context.Context, path string) error
}
type importer struct {
db db.DB
log *logrus.Logger
}
func NewImporter(db db.DB, log *logrus.Logger) Importer {
return &importer{
db: db,
log: log,
}
}

View file

@ -0,0 +1,54 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package trans
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
transmodel "github.com/superseriousbusiness/gotosocial/internal/trans/model"
)
func (i *importer) ImportMinimal(ctx context.Context, path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("ImportMinimal: error opening file %s: %s", path, err)
}
decoder := json.NewDecoder(f)
decoder.UseNumber()
for {
entry := transmodel.TransEntry{}
err := decoder.Decode(&entry)
if err != nil {
if err == io.EOF {
i.log.Infof("ImportMinimal: reached end of file")
return neatClose(f)
}
return fmt.Errorf("ImportMinimal: error decoding in readLoop: %s", err)
}
if err := i.inputEntry(ctx, entry); err != nil {
return fmt.Errorf("ImportMinimal: error inputting entry: %s", err)
}
}
}

View file

@ -0,0 +1,74 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package trans_test
import (
"context"
"fmt"
"os"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/trans"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type ImportMinimalTestSuite struct {
TransTestSuite
}
func (suite *ImportMinimalTestSuite) TestImportMinimalOK() {
ctx := context.Background()
// use a temporary file path
tempFilePath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString())
// export to the tempFilePath
exporter := trans.NewExporter(suite.db, suite.log)
err := exporter.ExportMinimal(ctx, tempFilePath)
suite.NoError(err)
// we should have some bytes in that file now
b, err := os.ReadFile(tempFilePath)
suite.NoError(err)
suite.NotEmpty(b)
suite.T().Log(string(b))
// now that the file is stored, tear down the database...
testrig.StandardDBTeardown(suite.db)
// and create just the tables -- no entries!
testrig.CreateTestTables(suite.db)
importer := trans.NewImporter(suite.db, suite.log)
err = importer.ImportMinimal(ctx, tempFilePath)
suite.NoError(err)
// we should now have some accounts in the database
accounts := []*gtsmodel.Account{}
err = suite.db.GetWhere(ctx, []db.Where{{Key: "domain", Value: nil}}, &accounts)
suite.NoError(err)
suite.NotEmpty(accounts)
}
func TestImportMinimalTestSuite(t *testing.T) {
suite.Run(t, &ImportMinimalTestSuite{})
}

53
internal/trans/input.go Normal file
View file

@ -0,0 +1,53 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package trans
import (
"context"
"errors"
"fmt"
transmodel "github.com/superseriousbusiness/gotosocial/internal/trans/model"
)
func (i *importer) inputEntry(ctx context.Context, entry transmodel.TransEntry) error {
t, ok := entry[transmodel.TypeKey].(string)
if !ok {
return errors.New("inputEntry: could not derive entry type: missing or malformed 'type' key in json")
}
switch transmodel.TransType(t) {
case transmodel.TransAccount:
account, err := accountDecode(entry)
if err != nil {
return fmt.Errorf("inputEntry: error decoding entry into account: %s", err)
}
if err := i.putInDB(ctx, account); err != nil {
return fmt.Errorf("inputEntry: error adding account to database: %s", err)
}
i.log.Infof("inputEntry: added account with id %s", account.ID)
return nil
}
return fmt.Errorf("inputEntry: didn't recognize transtype %s", t)
}
func (i *importer) putInDB(ctx context.Context, entry interface{}) error {
return i.db.Put(ctx, entry)
}

View file

@ -25,24 +25,25 @@ import (
// Account represents the minimum viable representation of an account for export/import.
type Account struct {
Type TransType `json:"type" bun:"-"`
ID string `json:"id"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
CreatedAt *time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt"`
Username string `json:"username"`
Domain string `json:"domain,omitempty"`
Locked bool `json:"locked"`
Language string `json:"language,omitempty"`
URI string `json:"uri"`
URL string `json:"url"`
InboxURI string `json:"inbox_uri"`
OutboxURI string `json:"outbox_uri"`
FollowingURI string `json:"following_uri"`
FollowersURI string `json:"followers_uri"`
FeaturedCollectionURI string `json:"featured_collection_uri"`
ActorType string `json:"actor_type"`
PrivateKey *rsa.PrivateKey `json:"private_key,omitempty"`
PublicKey *rsa.PublicKey `json:"public_key"`
PublicKeyURI string `json:"public_key_uri"`
SuspendedAt *time.Time `json:"suspended_at,omitempty"`
SuspensionOrigin string `json:"suspension_origin,omitempty"`
InboxURI string `json:"inboxURI"`
OutboxURI string `json:"outboxURI"`
FollowingURI string `json:"followingUri"`
FollowersURI string `json:"followersUri"`
FeaturedCollectionURI string `json:"featuredCollectionUri"`
ActorType string `json:"actorType"`
PrivateKey *rsa.PrivateKey `json:"privateKey,omitempty"`
PublicKey *rsa.PublicKey `json:"publicKey"`
PublicKeyURI string `json:"publicKeyUri"`
SuspendedAt *time.Time `json:"suspendedAt,omitempty"`
SuspensionOrigin string `json:"suspensionOrigin,omitempty"`
}

View file

@ -21,10 +21,11 @@ package trans
import "time"
type Block struct {
Type TransType `json:"type" bun:"-"`
ID string `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
URI string `json:"uri"`
AccountID string `json:"account_id"`
TargetAccountID string `json:"target_account_id"`
AccountID string `json:"accountId"`
TargetAccountID string `json:"targetAccountId"`
}

View file

@ -0,0 +1,32 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package trans
const TypeKey = "type"
// TransType describes the type of a trans entry, and how it should be read/serialized.
type TransType string
// Type of the trans entry. Describes how it should be read from file.
const (
TransAccount TransType = "account"
TransBlock TransType = "block"
)
type TransEntry map[string]interface{}

View file

@ -19,6 +19,7 @@
package trans_test
import (
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/testrig"
@ -26,11 +27,13 @@ import (
type TransTestSuite struct {
suite.Suite
db db.DB
db db.DB
log *logrus.Logger
}
func (suite *TransTestSuite) SetupTest() {
suite.db = testrig.NewTestDB()
suite.log = testrig.NewTestLog()
testrig.StandardDBSetup(suite.db, nil)
}

73
vendor/github.com/mitchellh/mapstructure/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,73 @@
## unreleased
* Fix regression where `*time.Time` value would be set to empty and not be sent
to decode hooks properly [GH-232]
## 1.4.0
* A new decode hook type `DecodeHookFuncValue` has been added that has
access to the full values. [GH-183]
* Squash is now supported with embedded fields that are struct pointers [GH-205]
* Empty strings will convert to 0 for all numeric types when weakly decoding [GH-206]
## 1.3.3
* Decoding maps from maps creates a settable value for decode hooks [GH-203]
## 1.3.2
* Decode into interface type with a struct value is supported [GH-187]
## 1.3.1
* Squash should only squash embedded structs. [GH-194]
## 1.3.0
* Added `",omitempty"` support. This will ignore zero values in the source
structure when encoding. [GH-145]
## 1.2.3
* Fix duplicate entries in Keys list with pointer values. [GH-185]
## 1.2.2
* Do not add unsettable (unexported) values to the unused metadata key
or "remain" value. [GH-150]
## 1.2.1
* Go modules checksum mismatch fix
## 1.2.0
* Added support to capture unused values in a field using the `",remain"` value
in the mapstructure tag. There is an example to showcase usage.
* Added `DecoderConfig` option to always squash embedded structs
* `json.Number` can decode into `uint` types
* Empty slices are preserved and not replaced with nil slices
* Fix panic that can occur in when decoding a map into a nil slice of structs
* Improved package documentation for godoc
## 1.1.2
* Fix error when decode hook decodes interface implementation into interface
type. [GH-140]
## 1.1.1
* Fix panic that can happen in `decodePtr`
## 1.1.0
* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133]
* Support struct to struct decoding [GH-137]
* If source map value is nil, then destination map value is nil (instead of empty)
* If source slice value is nil, then destination slice value is nil (instead of empty)
* If source pointer is nil, then destination pointer is set to nil (instead of
allocated zero value of type)
## 1.0.0
* Initial tagged stable release.

21
vendor/github.com/mitchellh/mapstructure/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

46
vendor/github.com/mitchellh/mapstructure/README.md generated vendored Normal file
View file

@ -0,0 +1,46 @@
# mapstructure [![Godoc](https://godoc.org/github.com/mitchellh/mapstructure?status.svg)](https://godoc.org/github.com/mitchellh/mapstructure)
mapstructure is a Go library for decoding generic map values to structures
and vice versa, while providing helpful error handling.
This library is most useful when decoding values from some data stream (JSON,
Gob, etc.) where you don't _quite_ know the structure of the underlying data
until you read a part of it. You can therefore read a `map[string]interface{}`
and use this library to decode it into the proper underlying native Go
structure.
## Installation
Standard `go get`:
```
$ go get github.com/mitchellh/mapstructure
```
## Usage & Example
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).
The `Decode` function has examples associated with it there.
## But Why?!
Go offers fantastic standard libraries for decoding formats such as JSON.
The standard method is to have a struct pre-created, and populate that struct
from the bytes of the encoded format. This is great, but the problem is if
you have configuration or an encoding that changes slightly depending on
specific fields. For example, consider this JSON:
```json
{
"type": "person",
"name": "Mitchell"
}
```
Perhaps we can't populate a specific structure without first reading
the "type" field from the JSON. We could always do two passes over the
decoding of the JSON (reading the "type" first, and the rest later).
However, it is much simpler to just decode this into a `map[string]interface{}`
structure, read the "type" key, then use something like this library
to decode it into the proper structure.

View file

@ -0,0 +1,256 @@
package mapstructure
import (
"encoding"
"errors"
"fmt"
"net"
"reflect"
"strconv"
"strings"
"time"
)
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
// Create variables here so we can reference them with the reflect pkg
var f1 DecodeHookFuncType
var f2 DecodeHookFuncKind
var f3 DecodeHookFuncValue
// Fill in the variables into this interface and the rest is done
// automatically using the reflect package.
potential := []interface{}{f1, f2, f3}
v := reflect.ValueOf(h)
vt := v.Type()
for _, raw := range potential {
pt := reflect.ValueOf(raw).Type()
if vt.ConvertibleTo(pt) {
return v.Convert(pt).Interface()
}
}
return nil
}
// DecodeHookExec executes the given decode hook. This should be used
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
// that took reflect.Kind instead of reflect.Type.
func DecodeHookExec(
raw DecodeHookFunc,
from reflect.Value, to reflect.Value) (interface{}, error) {
switch f := typedDecodeHook(raw).(type) {
case DecodeHookFuncType:
return f(from.Type(), to.Type(), from.Interface())
case DecodeHookFuncKind:
return f(from.Kind(), to.Kind(), from.Interface())
case DecodeHookFuncValue:
return f(from, to)
default:
return nil, errors.New("invalid decode hook signature")
}
}
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
// automatically composes multiple DecodeHookFuncs.
//
// The composed funcs are called in order, with the result of the
// previous transformation.
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
var err error
var data interface{}
newFrom := f
for _, f1 := range fs {
data, err = DecodeHookExec(f1, newFrom, t)
if err != nil {
return nil, err
}
newFrom = reflect.ValueOf(data)
}
return data, nil
}
}
// StringToSliceHookFunc returns a DecodeHookFunc that converts
// string to []string by splitting on the given sep.
func StringToSliceHookFunc(sep string) DecodeHookFunc {
return func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
if f != reflect.String || t != reflect.Slice {
return data, nil
}
raw := data.(string)
if raw == "" {
return []string{}, nil
}
return strings.Split(raw, sep), nil
}
}
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
// strings to time.Duration.
func StringToTimeDurationHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Duration(5)) {
return data, nil
}
// Convert it by parsing
return time.ParseDuration(data.(string))
}
}
// StringToIPHookFunc returns a DecodeHookFunc that converts
// strings to net.IP
func StringToIPHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IP{}) {
return data, nil
}
// Convert it by parsing
ip := net.ParseIP(data.(string))
if ip == nil {
return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
}
return ip, nil
}
}
// StringToIPNetHookFunc returns a DecodeHookFunc that converts
// strings to net.IPNet
func StringToIPNetHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IPNet{}) {
return data, nil
}
// Convert it by parsing
_, net, err := net.ParseCIDR(data.(string))
return net, err
}
}
// StringToTimeHookFunc returns a DecodeHookFunc that converts
// strings to time.Time.
func StringToTimeHookFunc(layout string) DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Time{}) {
return data, nil
}
// Convert it by parsing
return time.Parse(layout, data.(string))
}
}
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
// the decoder.
//
// Note that this is significantly different from the WeaklyTypedInput option
// of the DecoderConfig.
func WeaklyTypedHook(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
dataVal := reflect.ValueOf(data)
switch t {
case reflect.String:
switch f {
case reflect.Bool:
if dataVal.Bool() {
return "1", nil
}
return "0", nil
case reflect.Float32:
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
case reflect.Int:
return strconv.FormatInt(dataVal.Int(), 10), nil
case reflect.Slice:
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
if elemKind == reflect.Uint8 {
return string(dataVal.Interface().([]uint8)), nil
}
case reflect.Uint:
return strconv.FormatUint(dataVal.Uint(), 10), nil
}
}
return data, nil
}
func RecursiveStructToMapHookFunc() DecodeHookFunc {
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
if f.Kind() != reflect.Struct {
return f.Interface(), nil
}
var i interface{} = struct{}{}
if t.Type() != reflect.TypeOf(&i).Elem() {
return f.Interface(), nil
}
m := make(map[string]interface{})
t.Set(reflect.ValueOf(m))
return f.Interface(), nil
}
}
// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
// strings to the UnmarshalText function, when the target type
// implements the encoding.TextUnmarshaler interface
func TextUnmarshallerHookFunc() DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
result := reflect.New(t).Interface()
unmarshaller, ok := result.(encoding.TextUnmarshaler)
if !ok {
return data, nil
}
if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil {
return nil, err
}
return result, nil
}
}

50
vendor/github.com/mitchellh/mapstructure/error.go generated vendored Normal file
View file

@ -0,0 +1,50 @@
package mapstructure
import (
"errors"
"fmt"
"sort"
"strings"
)
// Error implements the error interface and can represents multiple
// errors that occur in the course of a single decode.
type Error struct {
Errors []string
}
func (e *Error) Error() string {
points := make([]string, len(e.Errors))
for i, err := range e.Errors {
points[i] = fmt.Sprintf("* %s", err)
}
sort.Strings(points)
return fmt.Sprintf(
"%d error(s) decoding:\n\n%s",
len(e.Errors), strings.Join(points, "\n"))
}
// WrappedErrors implements the errwrap.Wrapper interface to make this
// return value more useful with the errwrap and go-multierror libraries.
func (e *Error) WrappedErrors() []error {
if e == nil {
return nil
}
result := make([]error, len(e.Errors))
for i, e := range e.Errors {
result[i] = errors.New(e)
}
return result
}
func appendErrors(errors []string, err error) []string {
switch e := err.(type) {
case *Error:
return append(errors, e.Errors...)
default:
return append(errors, e.Error())
}
}

3
vendor/github.com/mitchellh/mapstructure/go.mod generated vendored Normal file
View file

@ -0,0 +1,3 @@
module github.com/mitchellh/mapstructure
go 1.14

1462
vendor/github.com/mitchellh/mapstructure/mapstructure.go generated vendored Normal file

File diff suppressed because it is too large Load diff

3
vendor/modules.txt vendored
View file

@ -338,6 +338,9 @@ github.com/mattn/go-isatty
## explicit
github.com/microcosm-cc/bluemonday
github.com/microcosm-cc/bluemonday/css
# github.com/mitchellh/mapstructure v1.4.1
## explicit
github.com/mitchellh/mapstructure
# github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
## explicit
github.com/modern-go/concurrent