mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-29 18:06:15 -06:00
messing about with decoding/encoding
This commit is contained in:
parent
2ec36349c7
commit
3641d47e7e
22 changed files with 2276 additions and 32 deletions
1
go.mod
1
go.mod
|
|
@ -35,6 +35,7 @@ require (
|
|||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // 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
2
go.sum
|
|
@ -346,6 +346,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=
|
||||
|
|
|
|||
66
internal/trans/decoders.go
Normal file
66
internal/trans/decoders.go
Normal 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
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
42
internal/trans/importer.go
Normal file
42
internal/trans/importer.go
Normal 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,
|
||||
}
|
||||
}
|
||||
54
internal/trans/importminimal.go
Normal file
54
internal/trans/importminimal.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
74
internal/trans/importminimal_test.go
Normal file
74
internal/trans/importminimal_test.go
Normal 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
53
internal/trans/input.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
32
internal/trans/model/type.go
Normal file
32
internal/trans/model/type.go
Normal 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{}
|
||||
|
|
@ -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
73
vendor/github.com/mitchellh/mapstructure/CHANGELOG.md
generated
vendored
Normal 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
21
vendor/github.com/mitchellh/mapstructure/LICENSE
generated
vendored
Normal 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
46
vendor/github.com/mitchellh/mapstructure/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# mapstructure [](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.
|
||||
256
vendor/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal file
256
vendor/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal 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
50
vendor/github.com/mitchellh/mapstructure/error.go
generated
vendored
Normal 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
3
vendor/github.com/mitchellh/mapstructure/go.mod
generated
vendored
Normal 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
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
3
vendor/modules.txt
vendored
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue