mirror of https://github.com/goharbor/harbor.git
feat: add cache library and enable it in config manager (#13525)
Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
9f8a743da9
commit
e92674a42a
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/goharbor/harbor/src/common/http/modifier/auth"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/lib/cache"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
)
|
||||
|
||||
|
@ -36,7 +37,14 @@ type CfgManager struct {
|
|||
|
||||
// NewDBCfgManager - create DB config manager
|
||||
func NewDBCfgManager() *CfgManager {
|
||||
manager := &CfgManager{store: store.NewConfigStore(&driver.Database{})}
|
||||
cfgDriver := (driver.Driver)(&driver.Database{})
|
||||
|
||||
if cache.Default() != nil {
|
||||
log.Debug("create DB config manager with cache enabled")
|
||||
cfgDriver = driver.NewCacheDriver(cache.Default(), cfgDriver)
|
||||
}
|
||||
|
||||
manager := &CfgManager{store: store.NewConfigStore(cfgDriver)}
|
||||
// load default value
|
||||
manager.loadDefault()
|
||||
// load system config from env
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/cache"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
)
|
||||
|
||||
const cacheKey = "cfgs"
|
||||
|
||||
// Cache - Used to load/save configuration with cache
|
||||
type Cache struct {
|
||||
cache cache.Cache
|
||||
driver Driver
|
||||
}
|
||||
|
||||
// Load - load config from database, only user setting will be load from database.
|
||||
func (d *Cache) Load() (map[string]interface{}, error) {
|
||||
f := func() (interface{}, error) {
|
||||
return d.driver.Load()
|
||||
}
|
||||
|
||||
result := map[string]interface{}{}
|
||||
|
||||
// let the cache expired after one minute
|
||||
// because the there no way to rollback the config items been saved when invalidate the cache failed
|
||||
if err := cache.FetchOrSave(d.cache, cacheKey, &result, f, time.Minute); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Save - Only save user config items in the cfgs map
|
||||
func (d *Cache) Save(cfgs map[string]interface{}) error {
|
||||
if err := d.driver.Save(cfgs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.cache.Delete(cacheKey); err != nil {
|
||||
log.Warningf("failed to invalidate the cache of the configurations immediately, error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewCacheDriver returns driver with cache
|
||||
func NewCacheDriver(cache cache.Cache, driver Driver) Driver {
|
||||
return &Cache{
|
||||
cache: cache,
|
||||
driver: driver,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/config/metadata"
|
||||
"github.com/goharbor/harbor/src/lib/cache"
|
||||
_ "github.com/goharbor/harbor/src/lib/cache/redis" // redis cache
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCacheLoadAndSave(t *testing.T) {
|
||||
cache, _ := cache.New("redis")
|
||||
driver := NewCacheDriver(cache, &Database{})
|
||||
|
||||
cfgs := map[string]interface{}{
|
||||
common.AUTHMode: "db_auth",
|
||||
common.LDAPURL: "ldap://ldap.vmware.com",
|
||||
}
|
||||
driver.Save(cfgs)
|
||||
|
||||
cf, err := driver.Load()
|
||||
if err != nil {
|
||||
fmt.Printf("load failed %v", err)
|
||||
}
|
||||
|
||||
assert.Contains(t, cf, common.AUTHMode)
|
||||
assert.Contains(t, cf, common.LDAPURL)
|
||||
}
|
||||
|
||||
func BenchmarkCacheLoad(b *testing.B) {
|
||||
cfgs := map[string]interface{}{}
|
||||
for _, item := range metadata.Instance().GetAll() {
|
||||
cfgs[item.Name] = item.DefaultValue
|
||||
}
|
||||
|
||||
driver := Database{}
|
||||
driver.Save(cfgs)
|
||||
|
||||
cache, _ := cache.New("redis")
|
||||
c := Cache{cache: cache, driver: &driver}
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := c.Load(); err != nil {
|
||||
fmt.Printf("load failed, %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if err := cache.Delete(cacheKey); err != nil {
|
||||
fmt.Printf("Delete cache failed, %v\n", err)
|
||||
}
|
||||
}
|
|
@ -14,11 +14,14 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/config/metadata"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -62,3 +65,21 @@ func TestDatabase_Save(t *testing.T) {
|
|||
driver.Save(prevCfg)
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkDatabaseLoad(b *testing.B) {
|
||||
cfgs := map[string]interface{}{}
|
||||
for _, item := range metadata.Instance().GetAll() {
|
||||
cfgs[item.Name] = item.DefaultValue
|
||||
}
|
||||
|
||||
driver := Database{}
|
||||
driver.Save(cfgs)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := driver.Load(); err != nil {
|
||||
fmt.Printf("load failed %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ import (
|
|||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/middlewares"
|
||||
"github.com/goharbor/harbor/src/core/service/token"
|
||||
"github.com/goharbor/harbor/src/lib/cache"
|
||||
_ "github.com/goharbor/harbor/src/lib/cache/memory" // memory cache
|
||||
_ "github.com/goharbor/harbor/src/lib/cache/redis" // redis cache
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/metric"
|
||||
"github.com/goharbor/harbor/src/migration"
|
||||
|
@ -157,6 +160,11 @@ func main() {
|
|||
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = strings.Join(ss, ",")
|
||||
}
|
||||
|
||||
log.Info("initializing cache ...")
|
||||
if err := cache.Initialize(u.Scheme, redisURL); err != nil {
|
||||
log.Fatalf("failed to initialize cache: %v", err)
|
||||
}
|
||||
}
|
||||
beego.AddTemplateExt("htm")
|
||||
|
||||
|
|
|
@ -65,9 +65,10 @@ require (
|
|||
github.com/robfig/cron v1.0.0
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
|
||||
github.com/spf13/viper v1.4.0 // indirect
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.62
|
||||
github.com/theupdateframework/notary v0.6.1
|
||||
github.com/vmihailenco/msgpack/v5 v5.0.0-rc.2
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
|
|
|
@ -764,6 +764,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
|
|||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.62 h1:Vnr3IqaafEuQUciG6D6EaeLJm26Mg8sjAfbI4OoeauM=
|
||||
|
@ -780,6 +782,10 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
|||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vmihailenco/msgpack/v5 v5.0.0-rc.2 h1:ognci8XPlosGhIHK1OLYSpSpnlhSFeBklfe18zIEwcU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.0.0-rc.2/go.mod h1:HVxBVPUK/+fZMonk4bi1islLa8V3cfnBug0+4dykPzo=
|
||||
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
||||
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
|
@ -1101,6 +1107,8 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound error returns when the key value not found in the cache
|
||||
ErrNotFound = errors.New("key not found")
|
||||
)
|
||||
|
||||
// Cache cache interface
|
||||
type Cache interface {
|
||||
// Contains returns true if key exists
|
||||
Contains(key string) bool
|
||||
|
||||
// Delete delete item from cache by key
|
||||
Delete(key string) error
|
||||
|
||||
// Fetch retrieve the cached key value
|
||||
Fetch(key string, value interface{}) error
|
||||
|
||||
// Ping ping the cache
|
||||
Ping() error
|
||||
|
||||
// Save cache the value by key
|
||||
Save(key string, value interface{}, expiration ...time.Duration) error
|
||||
}
|
||||
|
||||
var (
|
||||
factories = map[string]func(opts Options) (Cache, error){}
|
||||
factoriesMu sync.RWMutex
|
||||
)
|
||||
|
||||
// Register register cache factory for type
|
||||
func Register(typ string, factory func(opts Options) (Cache, error)) {
|
||||
factoriesMu.Lock()
|
||||
defer factoriesMu.Unlock()
|
||||
|
||||
factories[typ] = factory
|
||||
}
|
||||
|
||||
// New returns cache from addr
|
||||
func New(typ string, opt ...Option) (Cache, error) {
|
||||
opts := newOptions(opt...)
|
||||
opts.Codec = codec // use the default codec for the cache
|
||||
|
||||
factoriesMu.Lock()
|
||||
defer factoriesMu.Unlock()
|
||||
|
||||
factory, ok := factories[typ]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cache type %s not support", typ)
|
||||
}
|
||||
|
||||
return factory(opts)
|
||||
}
|
||||
|
||||
var (
|
||||
cache Cache
|
||||
)
|
||||
|
||||
// Initialize initialize the default cache from the addr
|
||||
func Initialize(typ, addr string) error {
|
||||
c, err := New(typ, Address(addr), Prefix("cache:"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
redactedAddr := addr
|
||||
if u, err := url.Parse(addr); err == nil {
|
||||
redactedAddr = redacted(u)
|
||||
}
|
||||
|
||||
options := []lib.RetryOption{
|
||||
lib.RetryInitialInterval(time.Millisecond * 500),
|
||||
lib.RetryMaxInterval(time.Second * 10),
|
||||
lib.RetryTimeout(time.Minute),
|
||||
lib.RetryCallback(func(err error, sleep time.Duration) {
|
||||
log.Errorf("failed to ping %s, retry after %s : %v", redactedAddr, sleep, err)
|
||||
}),
|
||||
}
|
||||
|
||||
if err := lib.RetryUntil(c.Ping, options...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cache = c
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Default returns the default cache
|
||||
func Default() Cache {
|
||||
return cache
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
cachetesting "github.com/goharbor/harbor/src/testing/lib/cache"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type CacheTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) SetupSuite() {
|
||||
Register("mock", func(opts Options) (Cache, error) {
|
||||
return &cachetesting.Cache{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestNew() {
|
||||
{
|
||||
c, err := New("")
|
||||
suite.Nil(c)
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
{
|
||||
c, err := New("mocks")
|
||||
suite.Nil(c)
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
{
|
||||
c, err := New("mock")
|
||||
suite.NotNil(c)
|
||||
suite.Nil(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestInitialize() {
|
||||
{
|
||||
err := Initialize("cache", "")
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
{
|
||||
Register("cache", func(opts Options) (Cache, error) {
|
||||
c := &cachetesting.Cache{}
|
||||
c.On("Ping").Return(fmt.Errorf("oops"))
|
||||
|
||||
return c, nil
|
||||
})
|
||||
|
||||
err := Initialize("cache", "cache://user:pass@localhost")
|
||||
suite.Error(err)
|
||||
suite.Nil(Default())
|
||||
}
|
||||
|
||||
{
|
||||
Register("cache", func(opts Options) (Cache, error) {
|
||||
c := &cachetesting.Cache{}
|
||||
c.On("Ping").Return(nil)
|
||||
|
||||
return c, nil
|
||||
})
|
||||
|
||||
err := Initialize("cache", "")
|
||||
suite.Nil(err)
|
||||
suite.NotNil(Default())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(CacheTestSuite))
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
// Codec codec interface for cache
|
||||
type Codec interface {
|
||||
// Encode returns the encoded byte array of v.
|
||||
Encode(v interface{}) ([]byte, error)
|
||||
|
||||
// Decode analyzes the encoded data and stores the result into the v.
|
||||
Decode(data []byte, v interface{}) error
|
||||
}
|
||||
|
||||
var (
|
||||
// codec the default codec for the cache
|
||||
codec Codec = &msgpackCodec{}
|
||||
)
|
||||
|
||||
type msgpackCodec struct{}
|
||||
|
||||
func (*msgpackCodec) Encode(v interface{}) ([]byte, error) {
|
||||
return msgpack.Marshal(v)
|
||||
}
|
||||
|
||||
func (*msgpackCodec) Decode(data []byte, v interface{}) error {
|
||||
return msgpack.Unmarshal(data, v)
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkDefaultCodecEncode(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
codec.Encode("abcdefghigklmopqrztuvwxyz")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDefaultCodecDecode(b *testing.B) {
|
||||
data := []byte("abcdefghigklmopqrztuvwxyz")
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var str string
|
||||
codec.Decode(data, &str)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type cacheKey struct{}
|
||||
|
||||
// FromContext returns cache from context
|
||||
func FromContext(ctx context.Context) (Cache, bool) {
|
||||
c, ok := ctx.Value(cacheKey{}).(Cache)
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// NewContext returns new context with cache
|
||||
func NewContext(ctx context.Context, c Cache) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, cacheKey{}, c)
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
)
|
||||
|
||||
var (
|
||||
fetchOrSaveMu = keyMutex{m: &sync.Map{}}
|
||||
)
|
||||
|
||||
// FetchOrSave retrieves the value for the key if present in the cache.
|
||||
// Otherwise, it saves the value from the builder and retrieves the value for the key again.
|
||||
func FetchOrSave(c Cache, key string, value interface{}, builder func() (interface{}, error), expiration ...time.Duration) error {
|
||||
err := c.Fetch(key, value)
|
||||
// value found from the cache
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// internal error
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
// lock the key in cache and try to build the value for the key
|
||||
lockKey := fmt.Sprintf("%p:%s", c, key)
|
||||
fetchOrSaveMu.Lock(lockKey)
|
||||
|
||||
defer fetchOrSaveMu.Unlock(lockKey)
|
||||
|
||||
// fetch again to avoid to build the value multi-times
|
||||
err = c.Fetch(key, value)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// internal error
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
val, err := builder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Save(key, val, expiration...); err != nil {
|
||||
log.Warningf("failed to save value to cache, error: %v", err)
|
||||
|
||||
// save the val to cache failed, copy it to the value using the default codec
|
||||
data, err := codec.Encode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return codec.Decode(data, value)
|
||||
}
|
||||
|
||||
return c.Fetch(key, value) // after the building, fetch value again
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cachetesting "github.com/goharbor/harbor/src/testing/lib/cache"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type Foobar struct {
|
||||
Foo string
|
||||
Bar int
|
||||
}
|
||||
|
||||
type FetchOrSaveTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *FetchOrSaveTestSuite) TestFetchInternalError() {
|
||||
c := &cachetesting.Cache{}
|
||||
|
||||
mock.OnAnything(c, "Fetch").Return(fmt.Errorf("oops"))
|
||||
|
||||
var str string
|
||||
err := FetchOrSave(c, "key", &str, func() (interface{}, error) {
|
||||
return "str", nil
|
||||
})
|
||||
|
||||
suite.Equal(fmt.Errorf("oops"), err)
|
||||
}
|
||||
|
||||
func (suite *FetchOrSaveTestSuite) TestBuildError() {
|
||||
c := &cachetesting.Cache{}
|
||||
|
||||
mock.OnAnything(c, "Fetch").Return(ErrNotFound)
|
||||
|
||||
var str string
|
||||
err := FetchOrSave(c, "key", &str, func() (interface{}, error) {
|
||||
return nil, fmt.Errorf("oops")
|
||||
})
|
||||
|
||||
suite.Equal(fmt.Errorf("oops"), err)
|
||||
}
|
||||
|
||||
func (suite *FetchOrSaveTestSuite) TestSaveError() {
|
||||
c := &cachetesting.Cache{}
|
||||
|
||||
mock.OnAnything(c, "Fetch").Return(ErrNotFound)
|
||||
mock.OnAnything(c, "Save").Return(fmt.Errorf("oops"))
|
||||
|
||||
var str string
|
||||
err := FetchOrSave(c, "key", &str, func() (interface{}, error) {
|
||||
return "str", nil
|
||||
})
|
||||
|
||||
suite.Nil(err)
|
||||
suite.Equal("str", str)
|
||||
}
|
||||
|
||||
func (suite *FetchOrSaveTestSuite) TestSaveCalledOnlyOneTime() {
|
||||
c := &cachetesting.Cache{}
|
||||
|
||||
var data sync.Map
|
||||
|
||||
mock.OnAnything(c, "Fetch").Return(func(key string, value interface{}) error {
|
||||
_, ok := data.Load(key)
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNotFound
|
||||
})
|
||||
|
||||
mock.OnAnything(c, "Save").Return(func(key string, value interface{}, exp ...time.Duration) error {
|
||||
data.Store(key, value)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
var str string
|
||||
FetchOrSave(c, "key", &str, func() (interface{}, error) {
|
||||
return "str", nil
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
c.AssertNumberOfCalls(suite.T(), "Save", 1)
|
||||
}
|
||||
|
||||
func TestFetchOrSaveTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(FetchOrSaveTestSuite))
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package memory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/cache"
|
||||
)
|
||||
|
||||
type entry struct {
|
||||
data []byte
|
||||
expiratedAt int64
|
||||
}
|
||||
|
||||
func (e *entry) isExpirated() bool {
|
||||
return e.expiratedAt < time.Now().UnixNano()
|
||||
}
|
||||
|
||||
var _ cache.Cache = (*Cache)(nil)
|
||||
|
||||
// Cache memory cache
|
||||
type Cache struct {
|
||||
opts *cache.Options
|
||||
storage sync.Map
|
||||
}
|
||||
|
||||
// Contains returns true if key exists
|
||||
func (c *Cache) Contains(key string) bool {
|
||||
e, ok := c.storage.Load(c.opts.Key(key))
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if e.(*entry).isExpirated() {
|
||||
c.Delete(c.opts.Key(key))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Delete delete item from cache by key
|
||||
func (c *Cache) Delete(key string) error {
|
||||
c.storage.Delete(c.opts.Key(key))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch retrieve the cached key value
|
||||
func (c *Cache) Fetch(key string, value interface{}) error {
|
||||
v, ok := c.storage.Load(c.opts.Key(key))
|
||||
if !ok {
|
||||
return cache.ErrNotFound
|
||||
}
|
||||
|
||||
e := v.(*entry)
|
||||
if e.isExpirated() {
|
||||
c.Delete(c.opts.Key(key))
|
||||
return cache.ErrNotFound
|
||||
}
|
||||
|
||||
if err := c.opts.Codec.Decode(e.data, value); err != nil {
|
||||
return fmt.Errorf("failed to decode cached value to dest, key %s, error: %v", key, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ping ping the cache
|
||||
func (c *Cache) Ping() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save cache the value by key
|
||||
func (c *Cache) Save(key string, value interface{}, expiration ...time.Duration) error {
|
||||
data, err := c.opts.Codec.Encode(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode value, key %s, error: %v", key, err)
|
||||
}
|
||||
|
||||
var expiratedAt int64
|
||||
if len(expiration) > 0 {
|
||||
expiratedAt = time.Now().Add(expiration[0]).UnixNano()
|
||||
} else if c.opts.Expiration > 0 {
|
||||
expiratedAt = time.Now().Add(c.opts.Expiration).UnixNano()
|
||||
} else {
|
||||
expiratedAt = math.MaxInt64
|
||||
}
|
||||
|
||||
c.storage.Store(c.opts.Key(key), &entry{
|
||||
data: data,
|
||||
expiratedAt: expiratedAt,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// New returns memory cache
|
||||
func New(opts cache.Options) (cache.Cache, error) {
|
||||
return &Cache{opts: &opts}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cache.Register("memory", New)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package memory
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/cache"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type CacheTestSuite struct {
|
||||
suite.Suite
|
||||
cache cache.Cache
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) SetupSuite() {
|
||||
suite.cache, _ = cache.New("memory", cache.Expiration(time.Second*5))
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestContains() {
|
||||
key := "contains"
|
||||
suite.False(suite.cache.Contains(key))
|
||||
|
||||
suite.cache.Save(key, "value")
|
||||
suite.True(suite.cache.Contains(key))
|
||||
|
||||
suite.cache.Delete(key)
|
||||
suite.False(suite.cache.Contains(key))
|
||||
|
||||
suite.cache.Save(key, "value", time.Second*5)
|
||||
suite.True(suite.cache.Contains(key))
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
suite.False(suite.cache.Contains(key))
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestDelete() {
|
||||
key := "delete"
|
||||
|
||||
suite.cache.Save(key, "value")
|
||||
suite.True(suite.cache.Contains(key))
|
||||
|
||||
suite.cache.Delete(key)
|
||||
suite.False(suite.cache.Contains(key))
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestFetch() {
|
||||
key := "fetch"
|
||||
|
||||
suite.cache.Save(key, map[string]interface{}{"name": "harbor", "version": "1.10"})
|
||||
|
||||
mp := map[string]interface{}{}
|
||||
suite.cache.Fetch(key, &mp)
|
||||
suite.Len(mp, 2)
|
||||
suite.Equal("harbor", mp["name"])
|
||||
suite.Equal("1.10", mp["version"])
|
||||
|
||||
var str string
|
||||
suite.Error(suite.cache.Fetch(key, &str))
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestSave() {
|
||||
key := "save"
|
||||
|
||||
{
|
||||
suite.cache.Save(key, "hello, save")
|
||||
|
||||
var value string
|
||||
suite.cache.Fetch(key, &value)
|
||||
suite.Equal("hello, save", value)
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
value = ""
|
||||
suite.Error(suite.cache.Fetch(key, &value))
|
||||
suite.Equal("", value)
|
||||
}
|
||||
|
||||
{
|
||||
suite.cache.Save(key, "hello, save", time.Second)
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
var value string
|
||||
suite.Error(suite.cache.Fetch(key, &value))
|
||||
suite.Equal("", value)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestPing() {
|
||||
suite.NoError(suite.cache.Ping())
|
||||
}
|
||||
|
||||
func TestCacheTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(CacheTestSuite))
|
||||
}
|
||||
|
||||
func BenchmarkCacheFetchParallel(b *testing.B) {
|
||||
key := "benchmark"
|
||||
cache, _ := cache.New("memory")
|
||||
cache.Save(key, "hello, benchmark")
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var value string
|
||||
cache.Fetch(key, &value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCacheSaveParallel(b *testing.B) {
|
||||
key := "benchmark"
|
||||
cache, _ := cache.New("memory")
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
cache.Save(key, "hello, benchmark")
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Option function to set the options of the cache
|
||||
type Option func(*Options)
|
||||
|
||||
// Options options of the cache
|
||||
type Options struct {
|
||||
Address string // the address of the cache
|
||||
Codec Codec // the codec for the cache
|
||||
Expiration time.Duration // the default expiration for the cache
|
||||
Prefix string // the prefix for all the keys in the cache
|
||||
}
|
||||
|
||||
// Key returns the real cache key
|
||||
func (opts *Options) Key(key string) string {
|
||||
return opts.Prefix + key
|
||||
}
|
||||
|
||||
func newOptions(opt ...Option) Options {
|
||||
opts := Options{}
|
||||
|
||||
for _, o := range opt {
|
||||
o(&opts)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
// Address sets the address
|
||||
func Address(addr string) Option {
|
||||
return func(o *Options) {
|
||||
o.Address = addr
|
||||
}
|
||||
}
|
||||
|
||||
// Expiration sets the default expiration
|
||||
func Expiration(d time.Duration) Option {
|
||||
return func(o *Options) {
|
||||
o.Expiration = d
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix sets the prefix
|
||||
func Prefix(prefix string) Option {
|
||||
return func(o *Options) {
|
||||
o.Prefix = prefix
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/cache"
|
||||
libredis "github.com/goharbor/harbor/src/lib/redis"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
)
|
||||
|
||||
var _ cache.Cache = (*Cache)(nil)
|
||||
|
||||
// Cache redis cache
|
||||
type Cache struct {
|
||||
opts *cache.Options
|
||||
pool *redis.Pool
|
||||
}
|
||||
|
||||
// Contains returns true if key exists
|
||||
func (c *Cache) Contains(key string) bool {
|
||||
reply, err := redis.Int(c.do("EXISTS", c.opts.Key(key)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return reply == 1
|
||||
}
|
||||
|
||||
// Delete delete item from cache by key
|
||||
func (c *Cache) Delete(key string) error {
|
||||
_, err := c.do("DEL", c.opts.Key(key))
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch retrieve the cached key value
|
||||
func (c *Cache) Fetch(key string, value interface{}) error {
|
||||
data, err := redis.Bytes(c.do("GET", c.opts.Key(key)))
|
||||
if err != nil {
|
||||
// convert internal or Timeout error to be ErrNotFound
|
||||
// so that the caller can continue working without breaking
|
||||
return cache.ErrNotFound
|
||||
}
|
||||
|
||||
if err := c.opts.Codec.Decode(data, value); err != nil {
|
||||
return fmt.Errorf("failed to decode cached value to dest, key %s, error: %v", key, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ping ping the cache
|
||||
func (c *Cache) Ping() error {
|
||||
_, err := c.do("PING")
|
||||
return err
|
||||
}
|
||||
|
||||
// Save cache the value by key
|
||||
func (c *Cache) Save(key string, value interface{}, expiration ...time.Duration) error {
|
||||
data, err := c.opts.Codec.Encode(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode value, key %s, error: %v", key, err)
|
||||
}
|
||||
|
||||
args := []interface{}{c.opts.Key(key), data}
|
||||
|
||||
var exp time.Duration
|
||||
if len(expiration) > 0 {
|
||||
exp = expiration[0]
|
||||
} else if c.opts.Expiration > 0 {
|
||||
exp = c.opts.Expiration
|
||||
}
|
||||
|
||||
if exp > 0 {
|
||||
args = append(args, "EX", int64(exp/time.Second))
|
||||
}
|
||||
|
||||
_, err = c.do("SET", args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
conn := c.pool.Get()
|
||||
defer conn.Close()
|
||||
|
||||
return conn.Do(commandName, args...)
|
||||
}
|
||||
|
||||
// New returns redis cache
|
||||
func New(opts cache.Options) (cache.Cache, error) {
|
||||
if opts.Address == "" {
|
||||
opts.Address = "redis://localhost:6379/0"
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%x", md5.Sum([]byte(opts.Address)))
|
||||
|
||||
param := &libredis.PoolParam{
|
||||
PoolMaxIdle: 100,
|
||||
PoolMaxActive: 1000,
|
||||
PoolIdleTimeout: 10 * time.Minute,
|
||||
DialConnectionTimeout: time.Second,
|
||||
DialReadTimeout: time.Second * 2,
|
||||
DialWriteTimeout: time.Second * 5,
|
||||
}
|
||||
|
||||
pool, err := libredis.GetRedisPool(name, opts.Address, param)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Cache{opts: &opts, pool: pool}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cache.Register("redis", New)
|
||||
cache.Register("redis+sentinel", New)
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/cache"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type CacheTestSuite struct {
|
||||
suite.Suite
|
||||
cache cache.Cache
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) SetupSuite() {
|
||||
suite.cache, _ = cache.New("redis", cache.Expiration(time.Second*5))
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestContains() {
|
||||
key := "contains"
|
||||
suite.False(suite.cache.Contains(key))
|
||||
|
||||
suite.cache.Save(key, "value")
|
||||
suite.True(suite.cache.Contains(key))
|
||||
|
||||
suite.cache.Delete(key)
|
||||
suite.False(suite.cache.Contains(key))
|
||||
|
||||
suite.cache.Save(key, "value", time.Second*5)
|
||||
suite.True(suite.cache.Contains(key))
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
suite.False(suite.cache.Contains(key))
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestDelete() {
|
||||
key := "delete"
|
||||
|
||||
suite.cache.Save(key, "value")
|
||||
suite.True(suite.cache.Contains(key))
|
||||
|
||||
suite.cache.Delete(key)
|
||||
suite.False(suite.cache.Contains(key))
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestFetch() {
|
||||
key := "fetch"
|
||||
|
||||
suite.cache.Save(key, map[string]interface{}{"name": "harbor", "version": "1.10"})
|
||||
|
||||
mp := map[string]interface{}{}
|
||||
suite.cache.Fetch(key, &mp)
|
||||
suite.Len(mp, 2)
|
||||
suite.Equal("harbor", mp["name"])
|
||||
suite.Equal("1.10", mp["version"])
|
||||
|
||||
var str string
|
||||
suite.Error(suite.cache.Fetch(key, &str))
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestSave() {
|
||||
key := "save"
|
||||
|
||||
{
|
||||
suite.cache.Save(key, "hello, save")
|
||||
|
||||
var value string
|
||||
suite.cache.Fetch(key, &value)
|
||||
suite.Equal("hello, save", value)
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
value = ""
|
||||
suite.Error(suite.cache.Fetch(key, &value))
|
||||
suite.Equal("", value)
|
||||
}
|
||||
|
||||
{
|
||||
suite.cache.Save(key, "hello, save", time.Second)
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
var value string
|
||||
suite.Error(suite.cache.Fetch(key, &value))
|
||||
suite.Equal("", value)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *CacheTestSuite) TestPing() {
|
||||
suite.NoError(suite.cache.Ping())
|
||||
}
|
||||
|
||||
func TestCacheTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(CacheTestSuite))
|
||||
}
|
||||
|
||||
func BenchmarkCacheFetchParallel(b *testing.B) {
|
||||
key := "benchmark"
|
||||
cache, _ := cache.New("redis")
|
||||
cache.Save(key, "hello, benchmark")
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var value string
|
||||
err := cache.Fetch(key, &value)
|
||||
if err != nil {
|
||||
fmt.Printf("failed, error %v\n", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCacheSaveParallel(b *testing.B) {
|
||||
key := "benchmark"
|
||||
cache, _ := cache.New("redis")
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
cache.Save(key, "hello, benchmark")
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// TODO: use the URL.Redacted when golang upgrade to 1.15
|
||||
func redacted(u *url.URL) string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
ru := *u
|
||||
if _, has := ru.User.Password(); has {
|
||||
ru.User = url.UserPassword(ru.User.Username(), "xxxxx")
|
||||
}
|
||||
return ru.String()
|
||||
}
|
||||
|
||||
type keyMutex struct {
|
||||
m *sync.Map
|
||||
}
|
||||
|
||||
func (km keyMutex) Lock(key interface{}) {
|
||||
m := sync.Mutex{}
|
||||
act, _ := km.m.LoadOrStore(key, &m)
|
||||
|
||||
mm := act.(*sync.Mutex)
|
||||
mm.Lock()
|
||||
if mm != &m {
|
||||
mm.Unlock()
|
||||
km.Lock(key)
|
||||
}
|
||||
}
|
||||
|
||||
func (km keyMutex) Unlock(key interface{}) {
|
||||
act, exist := km.m.Load(key)
|
||||
if !exist {
|
||||
panic("unlock of unlocked mutex")
|
||||
}
|
||||
m := act.(*sync.Mutex)
|
||||
km.m.Delete(key)
|
||||
m.Unlock()
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKeyMutex(t *testing.T) {
|
||||
km := keyMutex{m: &sync.Map{}}
|
||||
key := "key"
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
km.Lock(key)
|
||||
km.Unlock(key)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
assert.Panics(t, func() {
|
||||
km.Unlock(key)
|
||||
})
|
||||
}
|
|
@ -20,7 +20,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
)
|
||||
|
||||
|
@ -54,7 +53,8 @@ func DefaultPool() *redis.Pool {
|
|||
idleTimeout = poolIdleTimeout
|
||||
}
|
||||
|
||||
url := config.GetRedisOfRegURL()
|
||||
// get _REDIS_URL_REG from environment directly here to avoid cyclic dependency
|
||||
url := os.Getenv("_REDIS_URL_REG")
|
||||
if url == "" {
|
||||
url = "redis://localhost:6379/1"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrRetryTimeout timeout error for retrying
|
||||
ErrRetryTimeout = errors.New("retry timeout")
|
||||
)
|
||||
|
||||
// RetryOptions options for the retry functions
|
||||
type RetryOptions struct {
|
||||
InitialInterval time.Duration // the initial interval for retring after failure, default 100 milliseconds
|
||||
MaxInterval time.Duration // the max interval for retring after failure, default 1 second
|
||||
Timeout time.Duration // the total time before returning if something is wrong, default 1 minute
|
||||
Callback func(err error, sleep time.Duration) // the callback function for Retry when the f called failed
|
||||
}
|
||||
|
||||
// RetryOption ...
|
||||
type RetryOption func(*RetryOptions)
|
||||
|
||||
// RetryInitialInterval set initial interval
|
||||
func RetryInitialInterval(initial time.Duration) RetryOption {
|
||||
return func(opts *RetryOptions) {
|
||||
opts.InitialInterval = initial
|
||||
}
|
||||
}
|
||||
|
||||
// RetryMaxInterval set max interval
|
||||
func RetryMaxInterval(max time.Duration) RetryOption {
|
||||
return func(opts *RetryOptions) {
|
||||
opts.MaxInterval = max
|
||||
}
|
||||
}
|
||||
|
||||
// RetryTimeout set timeout interval
|
||||
func RetryTimeout(timeout time.Duration) RetryOption {
|
||||
return func(opts *RetryOptions) {
|
||||
opts.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// RetryCallback set callback
|
||||
func RetryCallback(callback func(err error, sleep time.Duration)) RetryOption {
|
||||
return func(opts *RetryOptions) {
|
||||
opts.Callback = callback
|
||||
}
|
||||
}
|
||||
|
||||
// RetryUntil retry until f run successfully or timeout
|
||||
//
|
||||
// NOTE: This function will use exponential backoff and jitter for retrying, see
|
||||
// https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for more information
|
||||
func RetryUntil(f func() error, options ...RetryOption) error {
|
||||
opts := &RetryOptions{}
|
||||
|
||||
for _, o := range options {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
if opts.InitialInterval <= 0 {
|
||||
opts.InitialInterval = time.Millisecond * 100
|
||||
}
|
||||
|
||||
if opts.MaxInterval <= 0 {
|
||||
opts.MaxInterval = time.Second
|
||||
}
|
||||
|
||||
if opts.Timeout <= 0 {
|
||||
opts.Timeout = time.Minute
|
||||
}
|
||||
|
||||
timeout := time.After(opts.Timeout)
|
||||
for attempt := 1; ; attempt++ {
|
||||
select {
|
||||
case <-timeout:
|
||||
return ErrRetryTimeout
|
||||
default:
|
||||
if err := f(); err != nil {
|
||||
sleep := getBackoff(attempt, opts.InitialInterval, opts.MaxInterval, true)
|
||||
if opts.Callback != nil {
|
||||
opts.Callback(err, sleep)
|
||||
}
|
||||
|
||||
time.Sleep(sleep)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getBackoff(attempt int, initialInterval, maxInterval time.Duration, equalJitter bool) time.Duration {
|
||||
max := float64(maxInterval)
|
||||
base := float64(initialInterval)
|
||||
|
||||
dur := base * math.Pow(2, float64(attempt))
|
||||
if equalJitter {
|
||||
dur = dur/2 + float64(rand.Int63n(int64(dur))/2)
|
||||
}
|
||||
|
||||
if dur < base {
|
||||
dur = base
|
||||
}
|
||||
|
||||
if dur > max {
|
||||
dur = max
|
||||
}
|
||||
|
||||
return time.Duration(dur)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRetryUntil(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
i := 0
|
||||
f1 := func() error {
|
||||
i++
|
||||
return fmt.Errorf("failed")
|
||||
}
|
||||
assert.Error(RetryUntil(f1, RetryInitialInterval(time.Second), RetryMaxInterval(time.Second), RetryTimeout(time.Second*5)))
|
||||
assert.LessOrEqual(i, 5)
|
||||
|
||||
f2 := func() error {
|
||||
return nil
|
||||
}
|
||||
assert.Nil(RetryUntil(f2))
|
||||
|
||||
i = 0
|
||||
f3 := func() error {
|
||||
defer func() {
|
||||
i++
|
||||
}()
|
||||
|
||||
if i < 2 {
|
||||
return fmt.Errorf("failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
assert.Nil(RetryUntil(f3))
|
||||
|
||||
RetryUntil(
|
||||
f1,
|
||||
RetryTimeout(time.Second*5),
|
||||
RetryCallback(func(err error, sleep time.Duration) {
|
||||
fmt.Printf("failed to exec f1 retry after %s : %v\n", sleep, err)
|
||||
}),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Cache is an autogenerated mock type for the Cache type
|
||||
type Cache struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Contains provides a mock function with given fields: key
|
||||
func (_m *Cache) Contains(key string) bool {
|
||||
ret := _m.Called(key)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string) bool); ok {
|
||||
r0 = rf(key)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: key
|
||||
func (_m *Cache) Delete(key string) error {
|
||||
ret := _m.Called(key)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(key)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Fetch provides a mock function with given fields: key, value
|
||||
func (_m *Cache) Fetch(key string, value interface{}) error {
|
||||
ret := _m.Called(key, value)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, interface{}) error); ok {
|
||||
r0 = rf(key, value)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Ping provides a mock function with given fields:
|
||||
func (_m *Cache) Ping() error {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: key, value, expiration
|
||||
func (_m *Cache) Save(key string, value interface{}, expiration ...time.Duration) error {
|
||||
_va := make([]interface{}, len(expiration))
|
||||
for _i := range expiration {
|
||||
_va[_i] = expiration[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, key, value)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, interface{}, ...time.Duration) error); ok {
|
||||
r0 = rf(key, value, expiration...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
|
@ -15,3 +15,4 @@
|
|||
package lib
|
||||
|
||||
//go:generate mockery --case snake --dir ../../lib/orm --name Creator --output ./orm --outpkg orm
|
||||
//go:generate mockery --case snake --dir ../../lib/cache --name Cache --output ./cache --outpkg cache
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
|
||||
Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -5,20 +5,28 @@ import (
|
|||
"reflect"
|
||||
)
|
||||
|
||||
func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
||||
type CompareType int
|
||||
|
||||
const (
|
||||
compareLess CompareType = iota - 1
|
||||
compareEqual
|
||||
compareGreater
|
||||
)
|
||||
|
||||
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
{
|
||||
intobj1 := obj1.(int)
|
||||
intobj2 := obj2.(int)
|
||||
if intobj1 > intobj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if intobj1 == intobj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if intobj1 < intobj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Int8:
|
||||
|
@ -26,13 +34,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
int8obj1 := obj1.(int8)
|
||||
int8obj2 := obj2.(int8)
|
||||
if int8obj1 > int8obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if int8obj1 == int8obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if int8obj1 < int8obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Int16:
|
||||
|
@ -40,13 +48,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
int16obj1 := obj1.(int16)
|
||||
int16obj2 := obj2.(int16)
|
||||
if int16obj1 > int16obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if int16obj1 == int16obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if int16obj1 < int16obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Int32:
|
||||
|
@ -54,13 +62,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
int32obj1 := obj1.(int32)
|
||||
int32obj2 := obj2.(int32)
|
||||
if int32obj1 > int32obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if int32obj1 == int32obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if int32obj1 < int32obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Int64:
|
||||
|
@ -68,13 +76,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
int64obj1 := obj1.(int64)
|
||||
int64obj2 := obj2.(int64)
|
||||
if int64obj1 > int64obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if int64obj1 == int64obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if int64obj1 < int64obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint:
|
||||
|
@ -82,13 +90,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
uintobj1 := obj1.(uint)
|
||||
uintobj2 := obj2.(uint)
|
||||
if uintobj1 > uintobj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if uintobj1 == uintobj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if uintobj1 < uintobj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint8:
|
||||
|
@ -96,13 +104,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
uint8obj1 := obj1.(uint8)
|
||||
uint8obj2 := obj2.(uint8)
|
||||
if uint8obj1 > uint8obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if uint8obj1 == uint8obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if uint8obj1 < uint8obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint16:
|
||||
|
@ -110,13 +118,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
uint16obj1 := obj1.(uint16)
|
||||
uint16obj2 := obj2.(uint16)
|
||||
if uint16obj1 > uint16obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if uint16obj1 == uint16obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if uint16obj1 < uint16obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint32:
|
||||
|
@ -124,13 +132,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
uint32obj1 := obj1.(uint32)
|
||||
uint32obj2 := obj2.(uint32)
|
||||
if uint32obj1 > uint32obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if uint32obj1 == uint32obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if uint32obj1 < uint32obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint64:
|
||||
|
@ -138,13 +146,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
uint64obj1 := obj1.(uint64)
|
||||
uint64obj2 := obj2.(uint64)
|
||||
if uint64obj1 > uint64obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if uint64obj1 == uint64obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if uint64obj1 < uint64obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Float32:
|
||||
|
@ -152,13 +160,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
float32obj1 := obj1.(float32)
|
||||
float32obj2 := obj2.(float32)
|
||||
if float32obj1 > float32obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if float32obj1 == float32obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if float32obj1 < float32obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Float64:
|
||||
|
@ -166,13 +174,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
float64obj1 := obj1.(float64)
|
||||
float64obj2 := obj2.(float64)
|
||||
if float64obj1 > float64obj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if float64obj1 == float64obj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if float64obj1 < float64obj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.String:
|
||||
|
@ -180,18 +188,18 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
stringobj1 := obj1.(string)
|
||||
stringobj2 := obj2.(string)
|
||||
if stringobj1 > stringobj2 {
|
||||
return -1, true
|
||||
return compareGreater, true
|
||||
}
|
||||
if stringobj1 == stringobj2 {
|
||||
return 0, true
|
||||
return compareEqual, true
|
||||
}
|
||||
if stringobj1 < stringobj2 {
|
||||
return 1, true
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
return compareEqual, false
|
||||
}
|
||||
|
||||
// Greater asserts that the first element is greater than the second
|
||||
|
@ -200,26 +208,7 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
|||
// assert.Greater(t, float64(2), float64(1))
|
||||
// assert.Greater(t, "b", "a")
|
||||
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
e1Kind := reflect.ValueOf(e1).Kind()
|
||||
e2Kind := reflect.ValueOf(e2).Kind()
|
||||
if e1Kind != e2Kind {
|
||||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
res, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if res != -1 {
|
||||
return Fail(t, fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
|
||||
}
|
||||
|
||||
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||
|
@ -229,26 +218,7 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface
|
|||
// assert.GreaterOrEqual(t, "b", "a")
|
||||
// assert.GreaterOrEqual(t, "b", "b")
|
||||
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
e1Kind := reflect.ValueOf(e1).Kind()
|
||||
e2Kind := reflect.ValueOf(e2).Kind()
|
||||
if e1Kind != e2Kind {
|
||||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
res, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if res != -1 && res != 0 {
|
||||
return Fail(t, fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
|
||||
}
|
||||
|
||||
// Less asserts that the first element is less than the second
|
||||
|
@ -257,26 +227,7 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in
|
|||
// assert.Less(t, float64(1), float64(2))
|
||||
// assert.Less(t, "a", "b")
|
||||
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
e1Kind := reflect.ValueOf(e1).Kind()
|
||||
e2Kind := reflect.ValueOf(e2).Kind()
|
||||
if e1Kind != e2Kind {
|
||||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
res, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if res != 1 {
|
||||
return Fail(t, fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
|
||||
}
|
||||
|
||||
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||
|
@ -286,6 +237,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{})
|
|||
// assert.LessOrEqual(t, "a", "b")
|
||||
// assert.LessOrEqual(t, "b", "b")
|
||||
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
|
||||
}
|
||||
|
||||
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
@ -296,14 +251,24 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter
|
|||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
res, isComparable := compare(e1, e2, e1Kind)
|
||||
compareResult, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if res != 1 && res != 0 {
|
||||
return Fail(t, fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2), msgAndArgs...)
|
||||
if !containsValue(allowedComparesResults, compareResult) {
|
||||
return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func containsValue(values []CompareType, value CompareType) bool {
|
||||
for _, v := range values {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -93,7 +93,7 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
|
|||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
|
||||
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
|
||||
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -127,7 +127,7 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick
|
|||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
|
||||
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
|
||||
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -173,7 +173,7 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool
|
|||
// Greaterf asserts that the first element is greater than the second
|
||||
//
|
||||
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.Greaterf(t, float64(2, "error message %s", "formatted"), float64(1))
|
||||
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
|
||||
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
@ -225,7 +225,7 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u
|
|||
//
|
||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -237,7 +237,7 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string,
|
|||
//
|
||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -245,6 +245,18 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri
|
|||
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPStatusCode(t, handler, method, url, values, statuscode, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
|
@ -259,7 +271,7 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin
|
|||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -341,7 +353,7 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf
|
|||
// Lessf asserts that the first element is less than the second
|
||||
//
|
||||
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.Lessf(t, float64(1, "error message %s", "formatted"), float64(2))
|
||||
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
|
||||
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
@ -454,6 +466,16 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string,
|
|||
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
|
||||
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||
|
@ -476,7 +498,7 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo
|
|||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
@ -552,7 +574,7 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str
|
|||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
|
|
@ -169,7 +169,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn
|
|||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
|
||||
// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted")
|
||||
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -251,7 +251,7 @@ func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArg
|
|||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
|
||||
// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted")
|
||||
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -370,7 +370,7 @@ func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string,
|
|||
// Greaterf asserts that the first element is greater than the second
|
||||
//
|
||||
// a.Greaterf(2, 1, "error message %s", "formatted")
|
||||
// a.Greaterf(float64(2, "error message %s", "formatted"), float64(1))
|
||||
// a.Greaterf(float64(2), float64(1), "error message %s", "formatted")
|
||||
// a.Greaterf("b", "a", "error message %s", "formatted")
|
||||
func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
@ -447,7 +447,7 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri
|
|||
//
|
||||
// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -471,7 +471,7 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s
|
|||
//
|
||||
// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -479,6 +479,30 @@ func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url
|
|||
return HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPStatusCode asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPStatusCode(a.t, handler, method, url, values, statuscode, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPStatusCodef(a.t, handler, method, url, values, statuscode, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
|
||||
|
@ -515,7 +539,7 @@ func (a *Assertions) Implements(interfaceObject interface{}, object interface{},
|
|||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -706,7 +730,7 @@ func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, ar
|
|||
// Lessf asserts that the first element is less than the second
|
||||
//
|
||||
// a.Lessf(1, 2, "error message %s", "formatted")
|
||||
// a.Lessf(float64(1, "error message %s", "formatted"), float64(2))
|
||||
// a.Lessf(float64(1), float64(2), "error message %s", "formatted")
|
||||
// a.Lessf("a", "b", "error message %s", "formatted")
|
||||
func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
@ -884,6 +908,26 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr
|
|||
return NotEqual(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEqualValues asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// a.NotEqualValues(obj1, obj2)
|
||||
func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqualValues(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted")
|
||||
func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqualValuesf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
|
||||
|
@ -950,7 +994,7 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in
|
|||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
@ -1102,7 +1146,7 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter
|
|||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl"
|
||||
|
@ -45,7 +45,7 @@ type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool
|
|||
// for table driven tests.
|
||||
type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool
|
||||
|
||||
// Comparison a custom function that returns true on success and false on failure
|
||||
// Comparison is a custom function that returns true on success and false on failure
|
||||
type Comparison func() (success bool)
|
||||
|
||||
/*
|
||||
|
@ -104,11 +104,11 @@ the problem actually occurred in calling code.*/
|
|||
// failed.
|
||||
func CallerInfo() []string {
|
||||
|
||||
pc := uintptr(0)
|
||||
file := ""
|
||||
line := 0
|
||||
ok := false
|
||||
name := ""
|
||||
var pc uintptr
|
||||
var ok bool
|
||||
var file string
|
||||
var line int
|
||||
var name string
|
||||
|
||||
callers := []string{}
|
||||
for i := 0; ; i++ {
|
||||
|
@ -429,14 +429,27 @@ func samePointers(first, second interface{}) bool {
|
|||
// to a type conversion in the Go grammar.
|
||||
func formatUnequalValues(expected, actual interface{}) (e string, a string) {
|
||||
if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
|
||||
return fmt.Sprintf("%T(%#v)", expected, expected),
|
||||
fmt.Sprintf("%T(%#v)", actual, actual)
|
||||
return fmt.Sprintf("%T(%s)", expected, truncatingFormat(expected)),
|
||||
fmt.Sprintf("%T(%s)", actual, truncatingFormat(actual))
|
||||
}
|
||||
switch expected.(type) {
|
||||
case time.Duration:
|
||||
return fmt.Sprintf("%v", expected), fmt.Sprintf("%v", actual)
|
||||
}
|
||||
return fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual)
|
||||
return truncatingFormat(expected), truncatingFormat(actual)
|
||||
}
|
||||
|
||||
// truncatingFormat formats the data and truncates it if it's too long.
|
||||
//
|
||||
// This helps keep formatted error messages lines from exceeding the
|
||||
// bufio.MaxScanTokenSize max line length that the go testing framework imposes.
|
||||
func truncatingFormat(data interface{}) string {
|
||||
value := fmt.Sprintf("%#v", data)
|
||||
max := bufio.MaxScanTokenSize - 100 // Give us some space the type info too if needed.
|
||||
if len(value) > max {
|
||||
value = value[0:max] + "<... truncated>"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// EqualValues asserts that two objects are equal or convertable to the same types
|
||||
|
@ -483,12 +496,12 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}
|
|||
//
|
||||
// assert.NotNil(t, err)
|
||||
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if !isNil(object) {
|
||||
return true
|
||||
}
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, "Expected value not to be nil.", msgAndArgs...)
|
||||
}
|
||||
|
||||
|
@ -529,12 +542,12 @@ func isNil(object interface{}) bool {
|
|||
//
|
||||
// assert.Nil(t, err)
|
||||
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if isNil(object) {
|
||||
return true
|
||||
}
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...)
|
||||
}
|
||||
|
||||
|
@ -571,12 +584,11 @@ func isEmpty(object interface{}) bool {
|
|||
//
|
||||
// assert.Empty(t, obj)
|
||||
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
pass := isEmpty(object)
|
||||
if !pass {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...)
|
||||
}
|
||||
|
||||
|
@ -591,12 +603,11 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
pass := !isEmpty(object)
|
||||
if !pass {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...)
|
||||
}
|
||||
|
||||
|
@ -639,16 +650,10 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{})
|
|||
//
|
||||
// assert.True(t, myBool)
|
||||
func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if h, ok := t.(interface {
|
||||
Helper()
|
||||
}); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
if value != true {
|
||||
if !value {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, "Should be true", msgAndArgs...)
|
||||
}
|
||||
|
||||
|
@ -660,11 +665,10 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
|||
//
|
||||
// assert.False(t, myBool)
|
||||
func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
if value != false {
|
||||
if value {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, "Should be false", msgAndArgs...)
|
||||
}
|
||||
|
||||
|
@ -695,6 +699,21 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{
|
|||
|
||||
}
|
||||
|
||||
// NotEqualValues asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// assert.NotEqualValues(t, obj1, obj2)
|
||||
func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
if ObjectsAreEqualValues(expected, actual) {
|
||||
return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// containsElement try loop over the list check if the list includes the element.
|
||||
// return (false, false) if impossible.
|
||||
// return (true, false) if element was not found.
|
||||
|
@ -747,10 +766,10 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo
|
|||
|
||||
ok, found := includeElement(s, contains)
|
||||
if !ok {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...)
|
||||
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
|
||||
}
|
||||
if !found {
|
||||
return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", s, contains), msgAndArgs...)
|
||||
return Fail(t, fmt.Sprintf("%#v does not contain %#v", s, contains), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -881,27 +900,39 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
|
|||
return true
|
||||
}
|
||||
|
||||
aKind := reflect.TypeOf(listA).Kind()
|
||||
bKind := reflect.TypeOf(listB).Kind()
|
||||
|
||||
if aKind != reflect.Array && aKind != reflect.Slice {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...)
|
||||
if !isList(t, listA, msgAndArgs...) || !isList(t, listB, msgAndArgs...) {
|
||||
return false
|
||||
}
|
||||
|
||||
if bKind != reflect.Array && bKind != reflect.Slice {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...)
|
||||
extraA, extraB := diffLists(listA, listB)
|
||||
|
||||
if len(extraA) == 0 && len(extraB) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return Fail(t, formatListDiff(listA, listB, extraA, extraB), msgAndArgs...)
|
||||
}
|
||||
|
||||
// isList checks that the provided value is array or slice.
|
||||
func isList(t TestingT, list interface{}, msgAndArgs ...interface{}) (ok bool) {
|
||||
kind := reflect.TypeOf(list).Kind()
|
||||
if kind != reflect.Array && kind != reflect.Slice {
|
||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s, expecting array or slice", list, kind),
|
||||
msgAndArgs...)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// diffLists diffs two arrays/slices and returns slices of elements that are only in A and only in B.
|
||||
// If some element is present multiple times, each instance is counted separately (e.g. if something is 2x in A and
|
||||
// 5x in B, it will be 0x in extraA and 3x in extraB). The order of items in both lists is ignored.
|
||||
func diffLists(listA, listB interface{}) (extraA, extraB []interface{}) {
|
||||
aValue := reflect.ValueOf(listA)
|
||||
bValue := reflect.ValueOf(listB)
|
||||
|
||||
aLen := aValue.Len()
|
||||
bLen := bValue.Len()
|
||||
|
||||
if aLen != bLen {
|
||||
return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...)
|
||||
}
|
||||
|
||||
// Mark indexes in bValue that we already used
|
||||
visited := make([]bool, bLen)
|
||||
for i := 0; i < aLen; i++ {
|
||||
|
@ -918,11 +949,38 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
|
|||
}
|
||||
}
|
||||
if !found {
|
||||
return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...)
|
||||
extraA = append(extraA, element)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
for j := 0; j < bLen; j++ {
|
||||
if visited[j] {
|
||||
continue
|
||||
}
|
||||
extraB = append(extraB, bValue.Index(j).Interface())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func formatListDiff(listA, listB interface{}, extraA, extraB []interface{}) string {
|
||||
var msg bytes.Buffer
|
||||
|
||||
msg.WriteString("elements differ")
|
||||
if len(extraA) > 0 {
|
||||
msg.WriteString("\n\nextra elements in list A:\n")
|
||||
msg.WriteString(spewConfig.Sdump(extraA))
|
||||
}
|
||||
if len(extraB) > 0 {
|
||||
msg.WriteString("\n\nextra elements in list B:\n")
|
||||
msg.WriteString(spewConfig.Sdump(extraB))
|
||||
}
|
||||
msg.WriteString("\n\nlistA:\n")
|
||||
msg.WriteString(spewConfig.Sdump(listA))
|
||||
msg.WriteString("\n\nlistB:\n")
|
||||
msg.WriteString(spewConfig.Sdump(listB))
|
||||
|
||||
return msg.String()
|
||||
}
|
||||
|
||||
// Condition uses a Comparison to assert a complex condition.
|
||||
|
@ -1058,6 +1116,8 @@ func toFloat(x interface{}) (float64, bool) {
|
|||
xok := true
|
||||
|
||||
switch xn := x.(type) {
|
||||
case uint:
|
||||
xf = float64(xn)
|
||||
case uint8:
|
||||
xf = float64(xn)
|
||||
case uint16:
|
||||
|
@ -1079,7 +1139,7 @@ func toFloat(x interface{}) (float64, bool) {
|
|||
case float32:
|
||||
xf = float64(xn)
|
||||
case float64:
|
||||
xf = float64(xn)
|
||||
xf = xn
|
||||
case time.Duration:
|
||||
xf = float64(xn)
|
||||
default:
|
||||
|
@ -1193,6 +1253,9 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
|
|||
if !aok {
|
||||
return 0, fmt.Errorf("expected value %q cannot be converted to float", expected)
|
||||
}
|
||||
if math.IsNaN(af) {
|
||||
return 0, errors.New("expected value must not be NaN")
|
||||
}
|
||||
if af == 0 {
|
||||
return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error")
|
||||
}
|
||||
|
@ -1200,6 +1263,9 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
|
|||
if !bok {
|
||||
return 0, fmt.Errorf("actual value %q cannot be converted to float", actual)
|
||||
}
|
||||
if math.IsNaN(bf) {
|
||||
return 0, errors.New("actual value must not be NaN")
|
||||
}
|
||||
|
||||
return math.Abs(af-bf) / math.Abs(af), nil
|
||||
}
|
||||
|
@ -1209,6 +1275,9 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd
|
|||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if math.IsNaN(epsilon) {
|
||||
return Fail(t, "epsilon must not be NaN")
|
||||
}
|
||||
actualEpsilon, err := calcRelativeError(expected, actual)
|
||||
if err != nil {
|
||||
return Fail(t, err.Error(), msgAndArgs...)
|
||||
|
@ -1256,10 +1325,10 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m
|
|||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if err != nil {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...)
|
||||
}
|
||||
|
||||
|
@ -1273,11 +1342,10 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
|||
// assert.Equal(t, expectedError, err)
|
||||
// }
|
||||
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, "An error is expected but got nil.", msgAndArgs...)
|
||||
}
|
||||
|
||||
|
@ -1553,6 +1621,7 @@ var spewConfig = spew.ConfigState{
|
|||
DisablePointerAddresses: true,
|
||||
DisableCapacities: true,
|
||||
SortKeys: true,
|
||||
DisableMethods: true,
|
||||
}
|
||||
|
||||
type tHelper interface {
|
||||
|
|
|
@ -33,7 +33,6 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
|
|||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
|
||||
|
@ -56,7 +55,6 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
|
|||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
|
||||
|
@ -79,7 +77,6 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values
|
|||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isErrorCode := code >= http.StatusBadRequest
|
||||
|
@ -90,6 +87,28 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values
|
|||
return isErrorCode
|
||||
}
|
||||
|
||||
// HTTPStatusCode asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
}
|
||||
|
||||
successful := code == statuscode
|
||||
if !successful {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return successful
|
||||
}
|
||||
|
||||
// HTTPBody is a helper that returns HTTP body of the response. It returns
|
||||
// empty string if building a new request fails.
|
||||
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
|
||||
|
|
|
@ -65,6 +65,11 @@ type Call struct {
|
|||
// reference. It's useful when mocking methods such as unmarshalers or
|
||||
// decoders.
|
||||
RunFn func(Arguments)
|
||||
|
||||
// PanicMsg holds msg to be used to mock panic on the function call
|
||||
// if the PanicMsg is set to a non nil string the function call will panic
|
||||
// irrespective of other settings
|
||||
PanicMsg *string
|
||||
}
|
||||
|
||||
func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call {
|
||||
|
@ -77,6 +82,7 @@ func newCall(parent *Mock, methodName string, callerInfo []string, methodArgumen
|
|||
Repeatability: 0,
|
||||
WaitFor: nil,
|
||||
RunFn: nil,
|
||||
PanicMsg: nil,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +106,18 @@ func (c *Call) Return(returnArguments ...interface{}) *Call {
|
|||
return c
|
||||
}
|
||||
|
||||
// Panic specifies if the functon call should fail and the panic message
|
||||
//
|
||||
// Mock.On("DoSomething").Panic("test panic")
|
||||
func (c *Call) Panic(msg string) *Call {
|
||||
c.lock()
|
||||
defer c.unlock()
|
||||
|
||||
c.PanicMsg = &msg
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Once indicates that that the mock should only return the value once.
|
||||
//
|
||||
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once()
|
||||
|
@ -150,7 +168,7 @@ func (c *Call) After(d time.Duration) *Call {
|
|||
// mocking a method (such as an unmarshaler) that takes a pointer to a struct and
|
||||
// sets properties in such struct
|
||||
//
|
||||
// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) {
|
||||
// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}")).Return().Run(func(args Arguments) {
|
||||
// arg := args.Get(0).(*map[string]interface{})
|
||||
// arg["foo"] = "bar"
|
||||
// })
|
||||
|
@ -392,6 +410,13 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
|
|||
time.Sleep(call.waitTime)
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
panicMsg := call.PanicMsg
|
||||
m.mutex.Unlock()
|
||||
if panicMsg != nil {
|
||||
panic(*panicMsg)
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
runFn := call.RunFn
|
||||
m.mutex.Unlock()
|
||||
|
@ -527,6 +552,45 @@ func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...inter
|
|||
return true
|
||||
}
|
||||
|
||||
// IsMethodCallable checking that the method can be called
|
||||
// If the method was called more than `Repeatability` return false
|
||||
func (m *Mock) IsMethodCallable(t TestingT, methodName string, arguments ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
for _, v := range m.ExpectedCalls {
|
||||
if v.Method != methodName {
|
||||
continue
|
||||
}
|
||||
if len(arguments) != len(v.Arguments) {
|
||||
continue
|
||||
}
|
||||
if v.Repeatability < v.totalCalls {
|
||||
continue
|
||||
}
|
||||
if isArgsEqual(v.Arguments, arguments) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isArgsEqual compares arguments
|
||||
func isArgsEqual(expected Arguments, args []interface{}) bool {
|
||||
if len(expected) != len(args) {
|
||||
return false
|
||||
}
|
||||
for i, v := range args {
|
||||
if !reflect.DeepEqual(expected[i], v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool {
|
||||
for _, call := range m.calls() {
|
||||
if call.Method == methodName {
|
||||
|
@ -791,7 +855,7 @@ func (args Arguments) String(indexOrNil ...int) string {
|
|||
// normal String() method - return a string representation of the args
|
||||
var argsStr []string
|
||||
for _, arg := range args {
|
||||
argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg)))
|
||||
argsStr = append(argsStr, fmt.Sprintf("%T", arg)) // handles nil nicely
|
||||
}
|
||||
return strings.Join(argsStr, ",")
|
||||
} else if len(indexOrNil) == 1 {
|
||||
|
|
|
@ -212,7 +212,7 @@ func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArg
|
|||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
|
||||
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
|
||||
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -315,7 +315,7 @@ func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ..
|
|||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
|
||||
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
|
||||
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -470,7 +470,7 @@ func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, arg
|
|||
// Greaterf asserts that the first element is greater than the second
|
||||
//
|
||||
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.Greaterf(t, float64(2, "error message %s", "formatted"), float64(1))
|
||||
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
|
||||
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
@ -565,7 +565,7 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string,
|
|||
//
|
||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -595,7 +595,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url strin
|
|||
//
|
||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -606,6 +606,36 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri
|
|||
t.FailNow()
|
||||
}
|
||||
|
||||
// HTTPStatusCode asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if assert.HTTPStatusCode(t, handler, method, url, values, statuscode, msgAndArgs...) {
|
||||
return
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if assert.HTTPStatusCodef(t, handler, method, url, values, statuscode, msg, args...) {
|
||||
return
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
||||
|
@ -651,7 +681,7 @@ func Implements(t TestingT, interfaceObject interface{}, object interface{}, msg
|
|||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -902,7 +932,7 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args .
|
|||
// Lessf asserts that the first element is less than the second
|
||||
//
|
||||
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.Lessf(t, float64(1, "error message %s", "formatted"), float64(2))
|
||||
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
|
||||
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
@ -1128,6 +1158,32 @@ func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs .
|
|||
t.FailNow()
|
||||
}
|
||||
|
||||
// NotEqualValues asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// assert.NotEqualValues(t, obj1, obj2)
|
||||
func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if assert.NotEqualValues(t, expected, actual, msgAndArgs...) {
|
||||
return
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
|
||||
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if assert.NotEqualValuesf(t, expected, actual, msg, args...) {
|
||||
return
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
|
||||
|
@ -1212,7 +1268,7 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf
|
|||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
@ -1406,7 +1462,7 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface
|
|||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
|
|
|
@ -170,7 +170,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn
|
|||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
|
||||
// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted")
|
||||
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -252,7 +252,7 @@ func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArg
|
|||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
|
||||
// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted")
|
||||
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -371,7 +371,7 @@ func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string,
|
|||
// Greaterf asserts that the first element is greater than the second
|
||||
//
|
||||
// a.Greaterf(2, 1, "error message %s", "formatted")
|
||||
// a.Greaterf(float64(2, "error message %s", "formatted"), float64(1))
|
||||
// a.Greaterf(float64(2), float64(1), "error message %s", "formatted")
|
||||
// a.Greaterf("b", "a", "error message %s", "formatted")
|
||||
func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
@ -448,7 +448,7 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri
|
|||
//
|
||||
// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -472,7 +472,7 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s
|
|||
//
|
||||
// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -480,6 +480,30 @@ func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url
|
|||
HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPStatusCode asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPStatusCode(a.t, handler, method, url, values, statuscode, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPStatusCodef(a.t, handler, method, url, values, statuscode, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
|
||||
|
@ -516,7 +540,7 @@ func (a *Assertions) Implements(interfaceObject interface{}, object interface{},
|
|||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
|
@ -707,7 +731,7 @@ func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, ar
|
|||
// Lessf asserts that the first element is less than the second
|
||||
//
|
||||
// a.Lessf(1, 2, "error message %s", "formatted")
|
||||
// a.Lessf(float64(1, "error message %s", "formatted"), float64(2))
|
||||
// a.Lessf(float64(1), float64(2), "error message %s", "formatted")
|
||||
// a.Lessf("a", "b", "error message %s", "formatted")
|
||||
func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
@ -885,6 +909,26 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr
|
|||
NotEqual(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEqualValues asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// a.NotEqualValues(obj1, obj2)
|
||||
func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEqualValues(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted")
|
||||
func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEqualValuesf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
|
||||
|
@ -951,7 +995,7 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in
|
|||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
@ -1103,7 +1147,7 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter
|
|||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
|
|
|
@ -44,3 +44,10 @@ type BeforeTest interface {
|
|||
type AfterTest interface {
|
||||
AfterTest(suiteName, testName string)
|
||||
}
|
||||
|
||||
// WithStats implements HandleStats, a function that will be executed
|
||||
// when a test suite is finished. The stats contain information about
|
||||
// the execution of that suite and its tests.
|
||||
type WithStats interface {
|
||||
HandleStats(suiteName string, stats *SuiteInformation)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package suite
|
||||
|
||||
import "time"
|
||||
|
||||
// SuiteInformation stats stores stats for the whole suite execution.
|
||||
type SuiteInformation struct {
|
||||
Start, End time.Time
|
||||
TestStats map[string]*TestInformation
|
||||
}
|
||||
|
||||
// TestInformation stores information about the execution of each test.
|
||||
type TestInformation struct {
|
||||
TestName string
|
||||
Start, End time.Time
|
||||
Passed bool
|
||||
}
|
||||
|
||||
func newSuiteInformation() *SuiteInformation {
|
||||
testStats := make(map[string]*TestInformation)
|
||||
|
||||
return &SuiteInformation{
|
||||
TestStats: testStats,
|
||||
}
|
||||
}
|
||||
|
||||
func (s SuiteInformation) start(testName string) {
|
||||
s.TestStats[testName] = &TestInformation{
|
||||
TestName: testName,
|
||||
Start: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s SuiteInformation) end(testName string, passed bool) {
|
||||
s.TestStats[testName].End = time.Now()
|
||||
s.TestStats[testName].Passed = passed
|
||||
}
|
||||
|
||||
func (s SuiteInformation) Passed() bool {
|
||||
for _, stats := range s.TestStats {
|
||||
if !stats.Passed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -7,8 +7,8 @@ import (
|
|||
"reflect"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -81,43 +81,68 @@ func (suite *Suite) Run(name string, subtest func()) bool {
|
|||
// Run takes a testing suite and runs all of the tests attached
|
||||
// to it.
|
||||
func Run(t *testing.T, suite TestingSuite) {
|
||||
testsSync := &sync.WaitGroup{}
|
||||
suite.SetT(t)
|
||||
defer failOnPanic(t)
|
||||
|
||||
suiteSetupDone := false
|
||||
suite.SetT(t)
|
||||
|
||||
var suiteSetupDone bool
|
||||
|
||||
var stats *SuiteInformation
|
||||
if _, ok := suite.(WithStats); ok {
|
||||
stats = newSuiteInformation()
|
||||
}
|
||||
|
||||
methodFinder := reflect.TypeOf(suite)
|
||||
tests := []testing.InternalTest{}
|
||||
for index := 0; index < methodFinder.NumMethod(); index++ {
|
||||
method := methodFinder.Method(index)
|
||||
methodFinder := reflect.TypeOf(suite)
|
||||
suiteName := methodFinder.Elem().Name()
|
||||
|
||||
for i := 0; i < methodFinder.NumMethod(); i++ {
|
||||
method := methodFinder.Method(i)
|
||||
|
||||
ok, err := methodFilter(method.Name)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !suiteSetupDone {
|
||||
if stats != nil {
|
||||
stats.Start = time.Now()
|
||||
}
|
||||
|
||||
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
|
||||
setupAllSuite.SetupSuite()
|
||||
}
|
||||
defer func() {
|
||||
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
|
||||
testsSync.Wait()
|
||||
tearDownAllSuite.TearDownSuite()
|
||||
}
|
||||
}()
|
||||
|
||||
suiteSetupDone = true
|
||||
}
|
||||
|
||||
test := testing.InternalTest{
|
||||
Name: method.Name,
|
||||
F: func(t *testing.T) {
|
||||
defer testsSync.Done()
|
||||
parentT := suite.T()
|
||||
suite.SetT(t)
|
||||
defer failOnPanic(t)
|
||||
defer func() {
|
||||
if stats != nil {
|
||||
passed := !t.Failed()
|
||||
stats.end(method.Name, passed)
|
||||
}
|
||||
|
||||
if afterTestSuite, ok := suite.(AfterTest); ok {
|
||||
afterTestSuite.AfterTest(suiteName, method.Name)
|
||||
}
|
||||
|
||||
if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
|
||||
tearDownTestSuite.TearDownTest()
|
||||
}
|
||||
|
||||
suite.SetT(parentT)
|
||||
}()
|
||||
|
||||
if setupTestSuite, ok := suite.(SetupTestSuite); ok {
|
||||
setupTestSuite.SetupTest()
|
||||
|
@ -125,25 +150,47 @@ func Run(t *testing.T, suite TestingSuite) {
|
|||
if beforeTestSuite, ok := suite.(BeforeTest); ok {
|
||||
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
|
||||
}
|
||||
defer func() {
|
||||
if afterTestSuite, ok := suite.(AfterTest); ok {
|
||||
afterTestSuite.AfterTest(methodFinder.Elem().Name(), method.Name)
|
||||
}
|
||||
if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
|
||||
tearDownTestSuite.TearDownTest()
|
||||
}
|
||||
suite.SetT(parentT)
|
||||
}()
|
||||
|
||||
if stats != nil {
|
||||
stats.start(method.Name)
|
||||
}
|
||||
|
||||
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
|
||||
},
|
||||
}
|
||||
tests = append(tests, test)
|
||||
testsSync.Add(1)
|
||||
}
|
||||
if suiteSetupDone {
|
||||
defer func() {
|
||||
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
|
||||
tearDownAllSuite.TearDownSuite()
|
||||
}
|
||||
|
||||
if suiteWithStats, measureStats := suite.(WithStats); measureStats {
|
||||
stats.End = time.Now()
|
||||
suiteWithStats.HandleStats(suiteName, stats)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
runTests(t, tests)
|
||||
}
|
||||
|
||||
// Filtering method according to set regular expression
|
||||
// specified command-line argument -m
|
||||
func methodFilter(name string) (bool, error) {
|
||||
if ok, _ := regexp.MatchString("^Test", name); !ok {
|
||||
return false, nil
|
||||
}
|
||||
return regexp.MatchString(*matchMethod, name)
|
||||
}
|
||||
|
||||
func runTests(t testing.TB, tests []testing.InternalTest) {
|
||||
if len(tests) == 0 {
|
||||
t.Log("warning: no tests to run")
|
||||
return
|
||||
}
|
||||
|
||||
r, ok := t.(runner)
|
||||
if !ok { // backwards compatibility with Go 1.6 and below
|
||||
if !testing.RunTests(allTestsFilter, tests) {
|
||||
|
@ -157,15 +204,6 @@ func runTests(t testing.TB, tests []testing.InternalTest) {
|
|||
}
|
||||
}
|
||||
|
||||
// Filtering method according to set regular expression
|
||||
// specified command-line argument -m
|
||||
func methodFilter(name string) (bool, error) {
|
||||
if ok, _ := regexp.MatchString("^Test", name); !ok {
|
||||
return false, nil
|
||||
}
|
||||
return regexp.MatchString(*matchMethod, name)
|
||||
}
|
||||
|
||||
type runner interface {
|
||||
Run(name string, f func(t *testing.T)) bool
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
run:
|
||||
concurrency: 8
|
||||
deadline: 5m
|
||||
tests: false
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- gochecknoglobals
|
||||
- gocognit
|
||||
- godox
|
||||
- wsl
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- gomnd
|
||||
- nlreturn
|
||||
- goerr113
|
||||
- exhaustive
|
||||
- nestif
|
|
@ -0,0 +1,4 @@
|
|||
semi: false
|
||||
singleQuote: true
|
||||
proseWrap: always
|
||||
printWidth: 100
|
|
@ -0,0 +1,20 @@
|
|||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
go_import_path: github.com/vmihailenco/msgpack
|
||||
|
||||
before_install:
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go
|
||||
env GOPATH)/bin v1.31.0
|
|
@ -0,0 +1,3 @@
|
|||
# Changelog
|
||||
|
||||
See https://msgpack.uptrace.dev/changelog/
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2013 The github.com/vmihailenco/msgpack Authors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,7 @@
|
|||
all:
|
||||
go test ./...
|
||||
go test ./... -short -race
|
||||
go test ./... -run=NONE -bench=. -benchmem
|
||||
env GOOS=linux GOARCH=386 go test ./...
|
||||
go vet
|
||||
golangci-lint run
|
|
@ -0,0 +1,81 @@
|
|||
# MessagePack encoding for Golang
|
||||
|
||||
[![Build Status](https://travis-ci.org/vmihailenco/msgpack.svg)](https://travis-ci.org/vmihailenco/msgpack)
|
||||
[![PkgGoDev](https://pkg.go.dev/badge/github.com/vmihailenco/msgpack/v5)](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5)
|
||||
[![Documentation](https://img.shields.io/badge/msgpack-documentation-informational)](https://msgpack.uptrace.dev/)
|
||||
[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj)
|
||||
|
||||
> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev)
|
||||
|
||||
- Join [Discord](https://discord.gg/rWtp5Aj) to ask questions.
|
||||
- [Documentation](https://msgpack.uptrace.dev)
|
||||
- [Reference](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5)
|
||||
- [Examples](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#pkg-examples)
|
||||
|
||||
## Features
|
||||
|
||||
- Primitives, arrays, maps, structs, time.Time and interface{}.
|
||||
- Appengine \*datastore.Key and datastore.Cursor.
|
||||
- [CustomEncoder](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-CustomEncoder)/CustomDecoder
|
||||
interfaces for custom encoding.
|
||||
- [Extensions](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-RegisterExt) to encode
|
||||
type information.
|
||||
- Renaming fields via `msgpack:"my_field_name"` and alias via `msgpack:"alias:another_name"`.
|
||||
- Omitting individual empty fields via `msgpack:",omitempty"` tag or all
|
||||
[empty fields in a struct](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-Marshal--OmitEmpty).
|
||||
- [Map keys sorting](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Encoder.SortMapKeys).
|
||||
- Encoding/decoding all
|
||||
[structs as arrays](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Encoder.UseArrayForStructs)
|
||||
or
|
||||
[individual structs](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-Marshal--AsArray).
|
||||
- [Encoder.UseJSONTag](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Encoder.UseJSONTag) with
|
||||
[Decoder.UseJSONTag](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Decoder.UseJSONTag) can
|
||||
turn msgpack into drop-in replacement for JSON.
|
||||
- Simple but very fast and efficient
|
||||
[queries](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#example-Decoder-Query).
|
||||
|
||||
## Installation
|
||||
|
||||
msgpack supports 2 last Go versions and requires support for
|
||||
[Go modules](https://github.com/golang/go/wiki/Modules). So make sure to initialize a Go module:
|
||||
|
||||
```shell
|
||||
go mod init github.com/my/repo
|
||||
```
|
||||
|
||||
And then install msgpack/v5 (note _v5_ in the import; omitting it is a popular mistake):
|
||||
|
||||
```shell
|
||||
go get github.com/vmihailenco/msgpack/v5
|
||||
```
|
||||
|
||||
## Quickstart
|
||||
|
||||
```go
|
||||
import "github.com/vmihailenco/msgpack/v5"
|
||||
|
||||
func ExampleMarshal() {
|
||||
type Item struct {
|
||||
Foo string
|
||||
}
|
||||
|
||||
b, err := msgpack.Marshal(&Item{Foo: "bar"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var item Item
|
||||
err = msgpack.Unmarshal(b, &item)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(item.Foo)
|
||||
// Output: bar
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [Fast and flexible HTTP router](https://github.com/vmihailenco/treemux)
|
||||
- [Golang PostgreSQL ORM](https://github.com/go-pg/pg)
|
||||
- [Golang message task queue](https://github.com/vmihailenco/taskq)
|
|
@ -0,0 +1,657 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
const (
|
||||
looseInterfaceDecodingFlag uint32 = 1 << iota
|
||||
disallowUnknownFieldsFlag
|
||||
)
|
||||
|
||||
const (
|
||||
bytesAllocLimit = 1e6 // 1mb
|
||||
sliceAllocLimit = 1e4
|
||||
maxMapSize = 1e6
|
||||
)
|
||||
|
||||
type bufReader interface {
|
||||
io.Reader
|
||||
io.ByteScanner
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var decPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return NewDecoder(nil)
|
||||
},
|
||||
}
|
||||
|
||||
func GetDecoder() *Decoder {
|
||||
return decPool.Get().(*Decoder)
|
||||
}
|
||||
|
||||
func PutDecoder(dec *Decoder) {
|
||||
dec.r = nil
|
||||
dec.s = nil
|
||||
decPool.Put(dec)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Unmarshal decodes the MessagePack-encoded data and stores the result
|
||||
// in the value pointed to by v.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
dec := GetDecoder()
|
||||
|
||||
dec.Reset(bytes.NewReader(data))
|
||||
err := dec.Decode(v)
|
||||
|
||||
PutDecoder(dec)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// A Decoder reads and decodes MessagePack values from an input stream.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
s io.ByteScanner
|
||||
buf []byte
|
||||
|
||||
rec []byte // accumulates read data if not nil
|
||||
|
||||
dict []string
|
||||
flags uint32
|
||||
structTag string
|
||||
decodeMapFunc func(*Decoder) (interface{}, error)
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read data from r
|
||||
// beyond the requested msgpack values. Buffering can be disabled
|
||||
// by passing a reader that implements io.ByteScanner interface.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
d := new(Decoder)
|
||||
d.Reset(r)
|
||||
return d
|
||||
}
|
||||
|
||||
// Reset discards any buffered data, resets all state, and switches the buffered
|
||||
// reader to read from r.
|
||||
func (d *Decoder) Reset(r io.Reader) {
|
||||
d.ResetDict(r, nil)
|
||||
}
|
||||
|
||||
// ResetDict is like Reset, but also resets the dict.
|
||||
func (d *Decoder) ResetDict(r io.Reader, dict []string) {
|
||||
d.resetReader(r)
|
||||
d.flags = 0
|
||||
d.structTag = ""
|
||||
d.decodeMapFunc = nil
|
||||
|
||||
if len(dict) > 0 {
|
||||
d.dict = dict
|
||||
} else {
|
||||
d.dict = d.dict[:0]
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Decoder) resetReader(r io.Reader) {
|
||||
if br, ok := r.(bufReader); ok {
|
||||
d.r = br
|
||||
d.s = br
|
||||
} else {
|
||||
br := bufio.NewReader(r)
|
||||
d.r = br
|
||||
d.s = br
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Decoder) SetMapDecoder(fn func(*Decoder) (interface{}, error)) {
|
||||
d.decodeMapFunc = fn
|
||||
}
|
||||
|
||||
// UseLooseInterfaceDecoding causes decoder to use DecodeInterfaceLoose
|
||||
// to decode msgpack value into Go interface{}.
|
||||
func (d *Decoder) UseLooseInterfaceDecoding(on bool) {
|
||||
if on {
|
||||
d.flags |= looseInterfaceDecodingFlag
|
||||
} else {
|
||||
d.flags &= ^looseInterfaceDecodingFlag
|
||||
}
|
||||
}
|
||||
|
||||
// SetCustomStructTag causes the decoder to use the supplied tag as a fallback option
|
||||
// if there is no msgpack tag.
|
||||
func (d *Decoder) SetCustomStructTag(tag string) {
|
||||
d.structTag = tag
|
||||
}
|
||||
|
||||
// DisallowUnknownFields causes the Decoder to return an error when the destination
|
||||
// is a struct and the input contains object keys which do not match any
|
||||
// non-ignored, exported fields in the destination.
|
||||
func (d *Decoder) DisallowUnknownFields(on bool) {
|
||||
if on {
|
||||
d.flags |= disallowUnknownFieldsFlag
|
||||
} else {
|
||||
d.flags &= ^disallowUnknownFieldsFlag
|
||||
}
|
||||
}
|
||||
|
||||
// UseInternedStrings enables support for decoding interned strings.
|
||||
func (d *Decoder) UseInternedStrings(on bool) {
|
||||
if on {
|
||||
d.flags |= useInternedStringsFlag
|
||||
} else {
|
||||
d.flags &= ^useInternedStringsFlag
|
||||
}
|
||||
}
|
||||
|
||||
// Buffered returns a reader of the data remaining in the Decoder's buffer.
|
||||
// The reader is valid until the next call to Decode.
|
||||
func (d *Decoder) Buffered() io.Reader {
|
||||
return d.r
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (d *Decoder) Decode(v interface{}) error {
|
||||
var err error
|
||||
switch v := v.(type) {
|
||||
case *string:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeString()
|
||||
return err
|
||||
}
|
||||
case *[]byte:
|
||||
if v != nil {
|
||||
return d.decodeBytesPtr(v)
|
||||
}
|
||||
case *int:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeInt()
|
||||
return err
|
||||
}
|
||||
case *int8:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeInt8()
|
||||
return err
|
||||
}
|
||||
case *int16:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeInt16()
|
||||
return err
|
||||
}
|
||||
case *int32:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeInt32()
|
||||
return err
|
||||
}
|
||||
case *int64:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeInt64()
|
||||
return err
|
||||
}
|
||||
case *uint:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeUint()
|
||||
return err
|
||||
}
|
||||
case *uint8:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeUint8()
|
||||
return err
|
||||
}
|
||||
case *uint16:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeUint16()
|
||||
return err
|
||||
}
|
||||
case *uint32:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeUint32()
|
||||
return err
|
||||
}
|
||||
case *uint64:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeUint64()
|
||||
return err
|
||||
}
|
||||
case *bool:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeBool()
|
||||
return err
|
||||
}
|
||||
case *float32:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeFloat32()
|
||||
return err
|
||||
}
|
||||
case *float64:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeFloat64()
|
||||
return err
|
||||
}
|
||||
case *[]string:
|
||||
return d.decodeStringSlicePtr(v)
|
||||
case *map[string]string:
|
||||
return d.decodeMapStringStringPtr(v)
|
||||
case *map[string]interface{}:
|
||||
return d.decodeMapStringInterfacePtr(v)
|
||||
case *time.Duration:
|
||||
if v != nil {
|
||||
vv, err := d.DecodeInt64()
|
||||
*v = time.Duration(vv)
|
||||
return err
|
||||
}
|
||||
case *time.Time:
|
||||
if v != nil {
|
||||
*v, err = d.DecodeTime()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
vv := reflect.ValueOf(v)
|
||||
if !vv.IsValid() {
|
||||
return errors.New("msgpack: Decode(nil)")
|
||||
}
|
||||
if vv.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("msgpack: Decode(non-pointer %T)", v)
|
||||
}
|
||||
if vv.IsNil() {
|
||||
return fmt.Errorf("msgpack: Decode(non-settable %T)", v)
|
||||
}
|
||||
|
||||
vv = vv.Elem()
|
||||
if vv.Kind() == reflect.Interface {
|
||||
if !vv.IsNil() {
|
||||
vv = vv.Elem()
|
||||
if vv.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("msgpack: Decode(non-pointer %s)", vv.Type().String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return d.DecodeValue(vv)
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeMulti(v ...interface{}) error {
|
||||
for _, vv := range v {
|
||||
if err := d.Decode(vv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeInterfaceCond() (interface{}, error) {
|
||||
if d.flags&looseInterfaceDecodingFlag != 0 {
|
||||
return d.DecodeInterfaceLoose()
|
||||
}
|
||||
return d.DecodeInterface()
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeValue(v reflect.Value) error {
|
||||
decode := getDecoder(v.Type())
|
||||
return decode(d, v)
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeNil() error {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != msgpcode.Nil {
|
||||
return fmt.Errorf("msgpack: invalid code=%x decoding nil", c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeNilValue(v reflect.Value) error {
|
||||
err := d.DecodeNil()
|
||||
if v.IsNil() {
|
||||
return err
|
||||
}
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeBool() (bool, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return d.bool(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) bool(c byte) (bool, error) {
|
||||
if c == msgpcode.False {
|
||||
return false, nil
|
||||
}
|
||||
if c == msgpcode.True {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("msgpack: invalid code=%x decoding bool", c)
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeDuration() (time.Duration, error) {
|
||||
n, err := d.DecodeInt64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.Duration(n), nil
|
||||
}
|
||||
|
||||
// DecodeInterface decodes value into interface. It returns following types:
|
||||
// - nil,
|
||||
// - bool,
|
||||
// - int8, int16, int32, int64,
|
||||
// - uint8, uint16, uint32, uint64,
|
||||
// - float32 and float64,
|
||||
// - string,
|
||||
// - []byte,
|
||||
// - slices of any of the above,
|
||||
// - maps of any of the above.
|
||||
//
|
||||
// DecodeInterface should be used only when you don't know the type of value
|
||||
// you are decoding. For example, if you are decoding number it is better to use
|
||||
// DecodeInt64 for negative numbers and DecodeUint64 for positive numbers.
|
||||
func (d *Decoder) DecodeInterface() (interface{}, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if msgpcode.IsFixedNum(c) {
|
||||
return int8(c), nil
|
||||
}
|
||||
if msgpcode.IsFixedMap(c) {
|
||||
err = d.s.UnreadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.decodeMapDefault()
|
||||
}
|
||||
if msgpcode.IsFixedArray(c) {
|
||||
return d.decodeSlice(c)
|
||||
}
|
||||
if msgpcode.IsFixedString(c) {
|
||||
return d.string(c)
|
||||
}
|
||||
|
||||
switch c {
|
||||
case msgpcode.Nil:
|
||||
return nil, nil
|
||||
case msgpcode.False, msgpcode.True:
|
||||
return d.bool(c)
|
||||
case msgpcode.Float:
|
||||
return d.float32(c)
|
||||
case msgpcode.Double:
|
||||
return d.float64(c)
|
||||
case msgpcode.Uint8:
|
||||
return d.uint8()
|
||||
case msgpcode.Uint16:
|
||||
return d.uint16()
|
||||
case msgpcode.Uint32:
|
||||
return d.uint32()
|
||||
case msgpcode.Uint64:
|
||||
return d.uint64()
|
||||
case msgpcode.Int8:
|
||||
return d.int8()
|
||||
case msgpcode.Int16:
|
||||
return d.int16()
|
||||
case msgpcode.Int32:
|
||||
return d.int32()
|
||||
case msgpcode.Int64:
|
||||
return d.int64()
|
||||
case msgpcode.Bin8, msgpcode.Bin16, msgpcode.Bin32:
|
||||
return d.bytes(c, nil)
|
||||
case msgpcode.Str8, msgpcode.Str16, msgpcode.Str32:
|
||||
return d.string(c)
|
||||
case msgpcode.Array16, msgpcode.Array32:
|
||||
return d.decodeSlice(c)
|
||||
case msgpcode.Map16, msgpcode.Map32:
|
||||
err = d.s.UnreadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.decodeMapDefault()
|
||||
case msgpcode.FixExt1, msgpcode.FixExt2, msgpcode.FixExt4, msgpcode.FixExt8, msgpcode.FixExt16,
|
||||
msgpcode.Ext8, msgpcode.Ext16, msgpcode.Ext32:
|
||||
return d.decodeInterfaceExt(c)
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("msgpack: unknown code %x decoding interface{}", c)
|
||||
}
|
||||
|
||||
// DecodeInterfaceLoose is like DecodeInterface except that:
|
||||
// - int8, int16, and int32 are converted to int64,
|
||||
// - uint8, uint16, and uint32 are converted to uint64,
|
||||
// - float32 is converted to float64.
|
||||
func (d *Decoder) DecodeInterfaceLoose() (interface{}, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if msgpcode.IsFixedNum(c) {
|
||||
return int64(int8(c)), nil
|
||||
}
|
||||
if msgpcode.IsFixedMap(c) {
|
||||
err = d.s.UnreadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.decodeMapDefault()
|
||||
}
|
||||
if msgpcode.IsFixedArray(c) {
|
||||
return d.decodeSlice(c)
|
||||
}
|
||||
if msgpcode.IsFixedString(c) {
|
||||
return d.string(c)
|
||||
}
|
||||
|
||||
switch c {
|
||||
case msgpcode.Nil:
|
||||
return nil, nil
|
||||
case msgpcode.False, msgpcode.True:
|
||||
return d.bool(c)
|
||||
case msgpcode.Float, msgpcode.Double:
|
||||
return d.float64(c)
|
||||
case msgpcode.Uint8, msgpcode.Uint16, msgpcode.Uint32, msgpcode.Uint64:
|
||||
return d.uint(c)
|
||||
case msgpcode.Int8, msgpcode.Int16, msgpcode.Int32, msgpcode.Int64:
|
||||
return d.int(c)
|
||||
case msgpcode.Bin8, msgpcode.Bin16, msgpcode.Bin32:
|
||||
return d.bytes(c, nil)
|
||||
case msgpcode.Str8, msgpcode.Str16, msgpcode.Str32:
|
||||
return d.string(c)
|
||||
case msgpcode.Array16, msgpcode.Array32:
|
||||
return d.decodeSlice(c)
|
||||
case msgpcode.Map16, msgpcode.Map32:
|
||||
err = d.s.UnreadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.decodeMapDefault()
|
||||
case msgpcode.FixExt1, msgpcode.FixExt2, msgpcode.FixExt4, msgpcode.FixExt8, msgpcode.FixExt16,
|
||||
msgpcode.Ext8, msgpcode.Ext16, msgpcode.Ext32:
|
||||
return d.decodeInterfaceExt(c)
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("msgpack: unknown code %x decoding interface{}", c)
|
||||
}
|
||||
|
||||
// Skip skips next value.
|
||||
func (d *Decoder) Skip() error {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msgpcode.IsFixedNum(c) {
|
||||
return nil
|
||||
}
|
||||
if msgpcode.IsFixedMap(c) {
|
||||
return d.skipMap(c)
|
||||
}
|
||||
if msgpcode.IsFixedArray(c) {
|
||||
return d.skipSlice(c)
|
||||
}
|
||||
if msgpcode.IsFixedString(c) {
|
||||
return d.skipBytes(c)
|
||||
}
|
||||
|
||||
switch c {
|
||||
case msgpcode.Nil, msgpcode.False, msgpcode.True:
|
||||
return nil
|
||||
case msgpcode.Uint8, msgpcode.Int8:
|
||||
return d.skipN(1)
|
||||
case msgpcode.Uint16, msgpcode.Int16:
|
||||
return d.skipN(2)
|
||||
case msgpcode.Uint32, msgpcode.Int32, msgpcode.Float:
|
||||
return d.skipN(4)
|
||||
case msgpcode.Uint64, msgpcode.Int64, msgpcode.Double:
|
||||
return d.skipN(8)
|
||||
case msgpcode.Bin8, msgpcode.Bin16, msgpcode.Bin32:
|
||||
return d.skipBytes(c)
|
||||
case msgpcode.Str8, msgpcode.Str16, msgpcode.Str32:
|
||||
return d.skipBytes(c)
|
||||
case msgpcode.Array16, msgpcode.Array32:
|
||||
return d.skipSlice(c)
|
||||
case msgpcode.Map16, msgpcode.Map32:
|
||||
return d.skipMap(c)
|
||||
case msgpcode.FixExt1, msgpcode.FixExt2, msgpcode.FixExt4, msgpcode.FixExt8, msgpcode.FixExt16,
|
||||
msgpcode.Ext8, msgpcode.Ext16, msgpcode.Ext32:
|
||||
return d.skipExt(c)
|
||||
}
|
||||
|
||||
return fmt.Errorf("msgpack: unknown code %x", c)
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeRaw() (RawMessage, error) {
|
||||
d.rec = make([]byte, 0)
|
||||
if err := d.Skip(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg := RawMessage(d.rec)
|
||||
d.rec = nil
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// PeekCode returns the next MessagePack code without advancing the reader.
|
||||
// Subpackage msgpack/codes defines the list of available msgpcode.
|
||||
func (d *Decoder) PeekCode() (byte, error) {
|
||||
c, err := d.s.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c, d.s.UnreadByte()
|
||||
}
|
||||
|
||||
// ReadFull reads exactly len(buf) bytes into the buf.
|
||||
func (d *Decoder) ReadFull(buf []byte) error {
|
||||
_, err := readN(d.r, buf, len(buf))
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Decoder) hasNilCode() bool {
|
||||
code, err := d.PeekCode()
|
||||
return err == nil && code == msgpcode.Nil
|
||||
}
|
||||
|
||||
func (d *Decoder) readCode() (byte, error) {
|
||||
c, err := d.s.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if d.rec != nil {
|
||||
d.rec = append(d.rec, c)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) readFull(b []byte) error {
|
||||
_, err := io.ReadFull(d.r, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.rec != nil {
|
||||
d.rec = append(d.rec, b...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) readN(n int) ([]byte, error) {
|
||||
var err error
|
||||
d.buf, err = readN(d.r, d.buf, n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.rec != nil {
|
||||
// TODO: read directly into d.rec?
|
||||
d.rec = append(d.rec, d.buf...)
|
||||
}
|
||||
return d.buf, nil
|
||||
}
|
||||
|
||||
func readN(r io.Reader, b []byte, n int) ([]byte, error) {
|
||||
if b == nil {
|
||||
if n == 0 {
|
||||
return make([]byte, 0), nil
|
||||
}
|
||||
switch {
|
||||
case n < 64:
|
||||
b = make([]byte, 0, 64)
|
||||
case n <= bytesAllocLimit:
|
||||
b = make([]byte, 0, n)
|
||||
default:
|
||||
b = make([]byte, 0, bytesAllocLimit)
|
||||
}
|
||||
}
|
||||
|
||||
if n <= cap(b) {
|
||||
b = b[:n]
|
||||
_, err := io.ReadFull(r, b)
|
||||
return b, err
|
||||
}
|
||||
b = b[:cap(b)]
|
||||
|
||||
var pos int
|
||||
for {
|
||||
alloc := min(n-len(b), bytesAllocLimit)
|
||||
b = append(b, make([]byte, alloc)...)
|
||||
|
||||
_, err := io.ReadFull(r, b[pos:])
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
|
||||
if len(b) == n {
|
||||
break
|
||||
}
|
||||
pos = len(b)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func min(a, b int) int { //nolint:unparam
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
|
@ -0,0 +1,349 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
var (
|
||||
mapStringStringPtrType = reflect.TypeOf((*map[string]string)(nil))
|
||||
mapStringStringType = mapStringStringPtrType.Elem()
|
||||
)
|
||||
|
||||
var (
|
||||
mapStringInterfacePtrType = reflect.TypeOf((*map[string]interface{})(nil))
|
||||
mapStringInterfaceType = mapStringInterfacePtrType.Elem()
|
||||
)
|
||||
|
||||
func decodeMapValue(d *Decoder, v reflect.Value) error {
|
||||
n, err := d.DecodeMapLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
typ := v.Type()
|
||||
if n == -1 {
|
||||
v.Set(reflect.Zero(typ))
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.MakeMap(typ))
|
||||
}
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.decodeTypedMapValue(v, n)
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeMapDefault() (interface{}, error) {
|
||||
if d.decodeMapFunc != nil {
|
||||
return d.decodeMapFunc(d)
|
||||
}
|
||||
|
||||
n, err := d.DecodeMapLen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
code, err := d.PeekCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if msgpcode.IsString(code) || msgpcode.IsBin(code) || msgpcode.IsExt(code) {
|
||||
return d.decodeMap(n)
|
||||
}
|
||||
return d.decodeUntypedMap(n)
|
||||
}
|
||||
|
||||
// DecodeMapLen decodes map length. Length is -1 when map is nil.
|
||||
func (d *Decoder) DecodeMapLen() (int, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if msgpcode.IsExt(c) {
|
||||
if err = d.skipExtHeader(c); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
c, err = d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return d.mapLen(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) mapLen(c byte) (int, error) {
|
||||
if c == msgpcode.Nil {
|
||||
return -1, nil
|
||||
}
|
||||
if c >= msgpcode.FixedMapLow && c <= msgpcode.FixedMapHigh {
|
||||
return int(c & msgpcode.FixedMapMask), nil
|
||||
}
|
||||
if c == msgpcode.Map16 {
|
||||
size, err := d.uint16()
|
||||
return int(size), err
|
||||
}
|
||||
if c == msgpcode.Map32 {
|
||||
size, err := d.uint32()
|
||||
return int(size), err
|
||||
}
|
||||
return 0, unexpectedCodeError{code: c, hint: "map length"}
|
||||
}
|
||||
|
||||
func decodeMapStringStringValue(d *Decoder, v reflect.Value) error {
|
||||
mptr := v.Addr().Convert(mapStringStringPtrType).Interface().(*map[string]string)
|
||||
return d.decodeMapStringStringPtr(mptr)
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeMapStringStringPtr(ptr *map[string]string) error {
|
||||
size, err := d.DecodeMapLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size == -1 {
|
||||
*ptr = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
m := *ptr
|
||||
if m == nil {
|
||||
*ptr = make(map[string]string, min(size, maxMapSize))
|
||||
m = *ptr
|
||||
}
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
mk, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mv, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m[mk] = mv
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeMapStringInterfaceValue(d *Decoder, v reflect.Value) error {
|
||||
ptr := v.Addr().Convert(mapStringInterfacePtrType).Interface().(*map[string]interface{})
|
||||
return d.decodeMapStringInterfacePtr(ptr)
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeMapStringInterfacePtr(ptr *map[string]interface{}) error {
|
||||
m, err := d.DecodeMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*ptr = m
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeMap() (map[string]interface{}, error) {
|
||||
n, err := d.DecodeMapLen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
return d.decodeMap(n)
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeMap(n int) (map[string]interface{}, error) {
|
||||
m := make(map[string]interface{}, min(n, maxMapSize))
|
||||
for i := 0; i < n; i++ {
|
||||
mk, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mv, err := d.decodeInterfaceCond()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[mk] = mv
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeUntypedMap() (interface{}, error) {
|
||||
n, err := d.DecodeMapLen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
return d.decodeUntypedMap(n)
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeUntypedMap(n int) (interface{}, error) {
|
||||
m := make(map[interface{}]interface{}, min(n, maxMapSize))
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
mk, err := d.decodeInterfaceCond()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mv, err := d.decodeInterfaceCond()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m[mk] = mv
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeTypedMap() (interface{}, error) {
|
||||
n, err := d.DecodeMapLen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
key, err := d.decodeInterfaceCond()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value, err := d.decodeInterfaceCond()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyType := reflect.TypeOf(key)
|
||||
valueType := reflect.TypeOf(value)
|
||||
|
||||
if !keyType.Comparable() {
|
||||
return nil, fmt.Errorf("msgpack: unsupported map key: %s", keyType.String())
|
||||
}
|
||||
|
||||
mapType := reflect.MapOf(keyType, valueType)
|
||||
mapValue := reflect.MakeMap(mapType)
|
||||
mapValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value))
|
||||
|
||||
n--
|
||||
if err := d.decodeTypedMapValue(mapValue, n); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapValue.Interface(), nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeTypedMapValue(v reflect.Value, n int) error {
|
||||
typ := v.Type()
|
||||
keyType := typ.Key()
|
||||
valueType := typ.Elem()
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
mk := reflect.New(keyType).Elem()
|
||||
if err := d.DecodeValue(mk); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mv := reflect.New(valueType).Elem()
|
||||
if err := d.DecodeValue(mv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.SetMapIndex(mk, mv)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) skipMap(c byte) error {
|
||||
n, err := d.mapLen(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeStructValue(d *Decoder, v reflect.Value) error {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var isArray bool
|
||||
|
||||
n, err := d.mapLen(c)
|
||||
if err != nil {
|
||||
var err2 error
|
||||
n, err2 = d.arrayLen(c)
|
||||
if err2 != nil {
|
||||
return err
|
||||
}
|
||||
isArray = true
|
||||
}
|
||||
if n == -1 {
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := structs.Fields(v.Type(), d.structTag)
|
||||
if isArray {
|
||||
for i, f := range fields.List {
|
||||
if i >= n {
|
||||
break
|
||||
}
|
||||
if err := f.DecodeValue(d, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Skip extra values.
|
||||
for i := len(fields.List); i < n; i++ {
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
name, err := d.bytesTemp()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f := fields.Map[string(name)]; f != nil {
|
||||
if err := f.DecodeValue(d, v); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if d.flags&disallowUnknownFieldsFlag != 0 {
|
||||
return fmt.Errorf("msgpack: unknown field %q", name)
|
||||
} else if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
func (d *Decoder) skipN(n int) error {
|
||||
_, err := d.readN(n)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Decoder) uint8() (uint8, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) int8() (int8, error) {
|
||||
n, err := d.uint8()
|
||||
return int8(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) uint16() (uint16, error) {
|
||||
b, err := d.readN(2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return (uint16(b[0]) << 8) | uint16(b[1]), nil
|
||||
}
|
||||
|
||||
func (d *Decoder) int16() (int16, error) {
|
||||
n, err := d.uint16()
|
||||
return int16(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) uint32() (uint32, error) {
|
||||
b, err := d.readN(4)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n := (uint32(b[0]) << 24) |
|
||||
(uint32(b[1]) << 16) |
|
||||
(uint32(b[2]) << 8) |
|
||||
uint32(b[3])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) int32() (int32, error) {
|
||||
n, err := d.uint32()
|
||||
return int32(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) uint64() (uint64, error) {
|
||||
b, err := d.readN(8)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n := (uint64(b[0]) << 56) |
|
||||
(uint64(b[1]) << 48) |
|
||||
(uint64(b[2]) << 40) |
|
||||
(uint64(b[3]) << 32) |
|
||||
(uint64(b[4]) << 24) |
|
||||
(uint64(b[5]) << 16) |
|
||||
(uint64(b[6]) << 8) |
|
||||
uint64(b[7])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) int64() (int64, error) {
|
||||
n, err := d.uint64()
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// DecodeUint64 decodes msgpack int8/16/32/64 and uint8/16/32/64
|
||||
// into Go uint64.
|
||||
func (d *Decoder) DecodeUint64() (uint64, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.uint(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) uint(c byte) (uint64, error) {
|
||||
if c == msgpcode.Nil {
|
||||
return 0, nil
|
||||
}
|
||||
if msgpcode.IsFixedNum(c) {
|
||||
return uint64(int8(c)), nil
|
||||
}
|
||||
switch c {
|
||||
case msgpcode.Uint8:
|
||||
n, err := d.uint8()
|
||||
return uint64(n), err
|
||||
case msgpcode.Int8:
|
||||
n, err := d.int8()
|
||||
return uint64(n), err
|
||||
case msgpcode.Uint16:
|
||||
n, err := d.uint16()
|
||||
return uint64(n), err
|
||||
case msgpcode.Int16:
|
||||
n, err := d.int16()
|
||||
return uint64(n), err
|
||||
case msgpcode.Uint32:
|
||||
n, err := d.uint32()
|
||||
return uint64(n), err
|
||||
case msgpcode.Int32:
|
||||
n, err := d.int32()
|
||||
return uint64(n), err
|
||||
case msgpcode.Uint64, msgpcode.Int64:
|
||||
return d.uint64()
|
||||
}
|
||||
return 0, fmt.Errorf("msgpack: invalid code=%x decoding uint64", c)
|
||||
}
|
||||
|
||||
// DecodeInt64 decodes msgpack int8/16/32/64 and uint8/16/32/64
|
||||
// into Go int64.
|
||||
func (d *Decoder) DecodeInt64() (int64, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.int(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) int(c byte) (int64, error) {
|
||||
if c == msgpcode.Nil {
|
||||
return 0, nil
|
||||
}
|
||||
if msgpcode.IsFixedNum(c) {
|
||||
return int64(int8(c)), nil
|
||||
}
|
||||
switch c {
|
||||
case msgpcode.Uint8:
|
||||
n, err := d.uint8()
|
||||
return int64(n), err
|
||||
case msgpcode.Int8:
|
||||
n, err := d.uint8()
|
||||
return int64(int8(n)), err
|
||||
case msgpcode.Uint16:
|
||||
n, err := d.uint16()
|
||||
return int64(n), err
|
||||
case msgpcode.Int16:
|
||||
n, err := d.uint16()
|
||||
return int64(int16(n)), err
|
||||
case msgpcode.Uint32:
|
||||
n, err := d.uint32()
|
||||
return int64(n), err
|
||||
case msgpcode.Int32:
|
||||
n, err := d.uint32()
|
||||
return int64(int32(n)), err
|
||||
case msgpcode.Uint64, msgpcode.Int64:
|
||||
n, err := d.uint64()
|
||||
return int64(n), err
|
||||
}
|
||||
return 0, fmt.Errorf("msgpack: invalid code=%x decoding int64", c)
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeFloat32() (float32, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.float32(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) float32(c byte) (float32, error) {
|
||||
if c == msgpcode.Float {
|
||||
n, err := d.uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return math.Float32frombits(n), nil
|
||||
}
|
||||
|
||||
n, err := d.int(c)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("msgpack: invalid code=%x decoding float32", c)
|
||||
}
|
||||
return float32(n), nil
|
||||
}
|
||||
|
||||
// DecodeFloat64 decodes msgpack float32/64 into Go float64.
|
||||
func (d *Decoder) DecodeFloat64() (float64, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.float64(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) float64(c byte) (float64, error) {
|
||||
switch c {
|
||||
case msgpcode.Float:
|
||||
n, err := d.float32(c)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return float64(n), nil
|
||||
case msgpcode.Double:
|
||||
n, err := d.uint64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return math.Float64frombits(n), nil
|
||||
}
|
||||
|
||||
n, err := d.int(c)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("msgpack: invalid code=%x decoding float32", c)
|
||||
}
|
||||
return float64(n), nil
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeUint() (uint, error) {
|
||||
n, err := d.DecodeUint64()
|
||||
return uint(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeUint8() (uint8, error) {
|
||||
n, err := d.DecodeUint64()
|
||||
return uint8(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeUint16() (uint16, error) {
|
||||
n, err := d.DecodeUint64()
|
||||
return uint16(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeUint32() (uint32, error) {
|
||||
n, err := d.DecodeUint64()
|
||||
return uint32(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeInt() (int, error) {
|
||||
n, err := d.DecodeInt64()
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeInt8() (int8, error) {
|
||||
n, err := d.DecodeInt64()
|
||||
return int8(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeInt16() (int16, error) {
|
||||
n, err := d.DecodeInt64()
|
||||
return int16(n), err
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeInt32() (int32, error) {
|
||||
n, err := d.DecodeInt64()
|
||||
return int32(n), err
|
||||
}
|
||||
|
||||
func decodeFloat32Value(d *Decoder, v reflect.Value) error {
|
||||
f, err := d.DecodeFloat32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetFloat(float64(f))
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeFloat64Value(d *Decoder, v reflect.Value) error {
|
||||
f, err := d.DecodeFloat64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetFloat(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeInt64Value(d *Decoder, v reflect.Value) error {
|
||||
n, err := d.DecodeInt64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetInt(n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeUint64Value(d *Decoder, v reflect.Value) error {
|
||||
n, err := d.DecodeUint64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetUint(n)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
type queryResult struct {
|
||||
query string
|
||||
key string
|
||||
hasAsterisk bool
|
||||
|
||||
values []interface{}
|
||||
}
|
||||
|
||||
func (q *queryResult) nextKey() {
|
||||
ind := strings.IndexByte(q.query, '.')
|
||||
if ind == -1 {
|
||||
q.key = q.query
|
||||
q.query = ""
|
||||
return
|
||||
}
|
||||
q.key = q.query[:ind]
|
||||
q.query = q.query[ind+1:]
|
||||
}
|
||||
|
||||
// Query extracts data specified by the query from the msgpack stream skipping
|
||||
// any other data. Query consists of map keys and array indexes separated with dot,
|
||||
// e.g. key1.0.key2.
|
||||
func (d *Decoder) Query(query string) ([]interface{}, error) {
|
||||
res := queryResult{
|
||||
query: query,
|
||||
}
|
||||
if err := d.query(&res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.values, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) query(q *queryResult) error {
|
||||
q.nextKey()
|
||||
if q.key == "" {
|
||||
v, err := d.decodeInterfaceCond()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.values = append(q.values, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
code, err := d.PeekCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case code == msgpcode.Map16 || code == msgpcode.Map32 || msgpcode.IsFixedMap(code):
|
||||
err = d.queryMapKey(q)
|
||||
case code == msgpcode.Array16 || code == msgpcode.Array32 || msgpcode.IsFixedArray(code):
|
||||
err = d.queryArrayIndex(q)
|
||||
default:
|
||||
err = fmt.Errorf("msgpack: unsupported code=%x decoding key=%q", code, q.key)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Decoder) queryMapKey(q *queryResult) error {
|
||||
n, err := d.DecodeMapLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
key, err := d.bytesTemp()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if string(key) == q.key {
|
||||
if err := d.query(q); err != nil {
|
||||
return err
|
||||
}
|
||||
if q.hasAsterisk {
|
||||
return d.skipNext((n - i - 1) * 2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) queryArrayIndex(q *queryResult) error {
|
||||
n, err := d.DecodeArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if q.key == "*" {
|
||||
q.hasAsterisk = true
|
||||
|
||||
query := q.query
|
||||
for i := 0; i < n; i++ {
|
||||
q.query = query
|
||||
if err := d.query(q); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
q.hasAsterisk = false
|
||||
return nil
|
||||
}
|
||||
|
||||
ind, err := strconv.Atoi(q.key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
if i == ind {
|
||||
if err := d.query(q); err != nil {
|
||||
return err
|
||||
}
|
||||
if q.hasAsterisk {
|
||||
return d.skipNext(n - i - 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) skipNext(n int) error {
|
||||
for i := 0; i < n; i++ {
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
var sliceStringPtrType = reflect.TypeOf((*[]string)(nil))
|
||||
|
||||
// DecodeArrayLen decodes array length. Length is -1 when array is nil.
|
||||
func (d *Decoder) DecodeArrayLen() (int, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.arrayLen(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) arrayLen(c byte) (int, error) {
|
||||
if c == msgpcode.Nil {
|
||||
return -1, nil
|
||||
} else if c >= msgpcode.FixedArrayLow && c <= msgpcode.FixedArrayHigh {
|
||||
return int(c & msgpcode.FixedArrayMask), nil
|
||||
}
|
||||
switch c {
|
||||
case msgpcode.Array16:
|
||||
n, err := d.uint16()
|
||||
return int(n), err
|
||||
case msgpcode.Array32:
|
||||
n, err := d.uint32()
|
||||
return int(n), err
|
||||
}
|
||||
return 0, fmt.Errorf("msgpack: invalid code=%x decoding array length", c)
|
||||
}
|
||||
|
||||
func decodeStringSliceValue(d *Decoder, v reflect.Value) error {
|
||||
ptr := v.Addr().Convert(sliceStringPtrType).Interface().(*[]string)
|
||||
return d.decodeStringSlicePtr(ptr)
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeStringSlicePtr(ptr *[]string) error {
|
||||
n, err := d.DecodeArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ss := makeStrings(*ptr, n)
|
||||
for i := 0; i < n; i++ {
|
||||
s, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ss = append(ss, s)
|
||||
}
|
||||
*ptr = ss
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeStrings(s []string, n int) []string {
|
||||
if n > sliceAllocLimit {
|
||||
n = sliceAllocLimit
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
return make([]string, 0, n)
|
||||
}
|
||||
|
||||
if cap(s) >= n {
|
||||
return s[:0]
|
||||
}
|
||||
|
||||
s = s[:cap(s)]
|
||||
s = append(s, make([]string, n-len(s))...)
|
||||
return s[:0]
|
||||
}
|
||||
|
||||
func decodeSliceValue(d *Decoder, v reflect.Value) error {
|
||||
n, err := d.DecodeArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n == -1 {
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
return nil
|
||||
}
|
||||
if n == 0 && v.IsNil() {
|
||||
v.Set(reflect.MakeSlice(v.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Cap() >= n {
|
||||
v.Set(v.Slice(0, n))
|
||||
} else if v.Len() < v.Cap() {
|
||||
v.Set(v.Slice(0, v.Cap()))
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
if i >= v.Len() {
|
||||
v.Set(growSliceValue(v, n))
|
||||
}
|
||||
elem := v.Index(i)
|
||||
if err := d.DecodeValue(elem); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func growSliceValue(v reflect.Value, n int) reflect.Value {
|
||||
diff := n - v.Len()
|
||||
if diff > sliceAllocLimit {
|
||||
diff = sliceAllocLimit
|
||||
}
|
||||
v = reflect.AppendSlice(v, reflect.MakeSlice(v.Type(), diff, diff))
|
||||
return v
|
||||
}
|
||||
|
||||
func decodeArrayValue(d *Decoder, v reflect.Value) error {
|
||||
n, err := d.DecodeArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n == -1 {
|
||||
return nil
|
||||
}
|
||||
if n > v.Len() {
|
||||
return fmt.Errorf("%s len is %d, but msgpack has %d elements", v.Type(), v.Len(), n)
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
sv := v.Index(i)
|
||||
if err := d.DecodeValue(sv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeSlice() ([]interface{}, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.decodeSlice(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeSlice(c byte) ([]interface{}, error) {
|
||||
n, err := d.arrayLen(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
s := make([]interface{}, 0, min(n, sliceAllocLimit))
|
||||
for i := 0; i < n; i++ {
|
||||
v, err := d.decodeInterfaceCond()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s = append(s, v)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) skipSlice(c byte) error {
|
||||
n, err := d.arrayLen(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
func (d *Decoder) bytesLen(c byte) (int, error) {
|
||||
if c == msgpcode.Nil {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
if msgpcode.IsFixedString(c) {
|
||||
return int(c & msgpcode.FixedStrMask), nil
|
||||
}
|
||||
|
||||
switch c {
|
||||
case msgpcode.Str8, msgpcode.Bin8:
|
||||
n, err := d.uint8()
|
||||
return int(n), err
|
||||
case msgpcode.Str16, msgpcode.Bin16:
|
||||
n, err := d.uint16()
|
||||
return int(n), err
|
||||
case msgpcode.Str32, msgpcode.Bin32:
|
||||
n, err := d.uint32()
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("msgpack: invalid code=%x decoding string/bytes length", c)
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeString() (string, error) {
|
||||
if intern := d.flags&useInternedStringsFlag != 0; intern || len(d.dict) > 0 {
|
||||
return d.decodeInternedString(intern)
|
||||
}
|
||||
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return d.string(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) string(c byte) (string, error) {
|
||||
n, err := d.bytesLen(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return d.stringWithLen(n)
|
||||
}
|
||||
|
||||
func (d *Decoder) stringWithLen(n int) (string, error) {
|
||||
if n <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
b, err := d.readN(n)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
func decodeStringValue(d *Decoder, v reflect.Value) error {
|
||||
s, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetString(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeBytesLen() (int, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.bytesLen(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeBytes() ([]byte, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.bytes(c, nil)
|
||||
}
|
||||
|
||||
func (d *Decoder) bytes(c byte, b []byte) ([]byte, error) {
|
||||
n, err := d.bytesLen(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
return readN(d.r, b, n)
|
||||
}
|
||||
|
||||
func (d *Decoder) bytesTemp() ([]byte, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := d.bytesLen(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
return d.readN(n)
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeBytesPtr(ptr *[]byte) error {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.bytesPtr(c, ptr)
|
||||
}
|
||||
|
||||
func (d *Decoder) bytesPtr(c byte, ptr *[]byte) error {
|
||||
n, err := d.bytesLen(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == -1 {
|
||||
*ptr = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
*ptr, err = readN(d.r, *ptr, n)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Decoder) skipBytes(c byte) error {
|
||||
n, err := d.bytesLen(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
return d.skipN(n)
|
||||
}
|
||||
|
||||
func decodeBytesValue(d *Decoder, v reflect.Value) error {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := d.bytes(c, v.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.SetBytes(b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeByteArrayValue(d *Decoder, v reflect.Value) error {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := d.bytesLen(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == -1 {
|
||||
return nil
|
||||
}
|
||||
if n > v.Len() {
|
||||
return fmt.Errorf("%s len is %d, but msgpack has %d elements", v.Type(), v.Len(), n)
|
||||
}
|
||||
|
||||
b := v.Slice(0, n).Bytes()
|
||||
return d.readFull(b)
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
stringType = reflect.TypeOf((*string)(nil)).Elem()
|
||||
)
|
||||
|
||||
var valueDecoders []decoderFunc
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
valueDecoders = []decoderFunc{
|
||||
reflect.Bool: decodeBoolValue,
|
||||
reflect.Int: decodeInt64Value,
|
||||
reflect.Int8: decodeInt64Value,
|
||||
reflect.Int16: decodeInt64Value,
|
||||
reflect.Int32: decodeInt64Value,
|
||||
reflect.Int64: decodeInt64Value,
|
||||
reflect.Uint: decodeUint64Value,
|
||||
reflect.Uint8: decodeUint64Value,
|
||||
reflect.Uint16: decodeUint64Value,
|
||||
reflect.Uint32: decodeUint64Value,
|
||||
reflect.Uint64: decodeUint64Value,
|
||||
reflect.Float32: decodeFloat32Value,
|
||||
reflect.Float64: decodeFloat64Value,
|
||||
reflect.Complex64: decodeUnsupportedValue,
|
||||
reflect.Complex128: decodeUnsupportedValue,
|
||||
reflect.Array: decodeArrayValue,
|
||||
reflect.Chan: decodeUnsupportedValue,
|
||||
reflect.Func: decodeUnsupportedValue,
|
||||
reflect.Interface: decodeInterfaceValue,
|
||||
reflect.Map: decodeMapValue,
|
||||
reflect.Ptr: decodeUnsupportedValue,
|
||||
reflect.Slice: decodeSliceValue,
|
||||
reflect.String: decodeStringValue,
|
||||
reflect.Struct: decodeStructValue,
|
||||
reflect.UnsafePointer: decodeUnsupportedValue,
|
||||
}
|
||||
}
|
||||
|
||||
func getDecoder(typ reflect.Type) decoderFunc {
|
||||
if v, ok := typeDecMap.Load(typ); ok {
|
||||
return v.(decoderFunc)
|
||||
}
|
||||
fn := _getDecoder(typ)
|
||||
typeDecMap.Store(typ, fn)
|
||||
return fn
|
||||
}
|
||||
|
||||
func _getDecoder(typ reflect.Type) decoderFunc {
|
||||
kind := typ.Kind()
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
if _, ok := typeDecMap.Load(typ.Elem()); ok {
|
||||
return ptrDecoderFunc(typ)
|
||||
}
|
||||
}
|
||||
|
||||
if typ.Implements(customDecoderType) {
|
||||
return decodeCustomValue
|
||||
}
|
||||
if typ.Implements(unmarshalerType) {
|
||||
return unmarshalValue
|
||||
}
|
||||
if typ.Implements(binaryUnmarshalerType) {
|
||||
return unmarshalBinaryValue
|
||||
}
|
||||
if typ.Implements(textUnmarshalerType) {
|
||||
return unmarshalTextValue
|
||||
}
|
||||
|
||||
// Addressable struct field value.
|
||||
if kind != reflect.Ptr {
|
||||
ptr := reflect.PtrTo(typ)
|
||||
if ptr.Implements(customDecoderType) {
|
||||
return decodeCustomValueAddr
|
||||
}
|
||||
if ptr.Implements(unmarshalerType) {
|
||||
return unmarshalValueAddr
|
||||
}
|
||||
if ptr.Implements(binaryUnmarshalerType) {
|
||||
return unmarshalBinaryValueAddr
|
||||
}
|
||||
if ptr.Implements(textUnmarshalerType) {
|
||||
return unmarshalTextValueAddr
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr:
|
||||
return ptrDecoderFunc(typ)
|
||||
case reflect.Slice:
|
||||
elem := typ.Elem()
|
||||
if elem.Kind() == reflect.Uint8 {
|
||||
return decodeBytesValue
|
||||
}
|
||||
if elem == stringType {
|
||||
return decodeStringSliceValue
|
||||
}
|
||||
case reflect.Array:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
return decodeByteArrayValue
|
||||
}
|
||||
case reflect.Map:
|
||||
if typ.Key() == stringType {
|
||||
switch typ.Elem() {
|
||||
case stringType:
|
||||
return decodeMapStringStringValue
|
||||
case interfaceType:
|
||||
return decodeMapStringInterfaceValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valueDecoders[kind]
|
||||
}
|
||||
|
||||
func ptrDecoderFunc(typ reflect.Type) decoderFunc {
|
||||
decoder := getDecoder(typ.Elem())
|
||||
return func(d *Decoder, v reflect.Value) error {
|
||||
if d.hasNilCode() {
|
||||
if !v.IsNil() {
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
}
|
||||
return d.DecodeNil()
|
||||
}
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
return decoder(d, v.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
func decodeCustomValueAddr(d *Decoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface())
|
||||
}
|
||||
return decodeCustomValue(d, v.Addr())
|
||||
}
|
||||
|
||||
func decodeCustomValue(d *Decoder, v reflect.Value) error {
|
||||
if d.hasNilCode() {
|
||||
return d.decodeNilValue(v)
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
|
||||
decoder := v.Interface().(CustomDecoder)
|
||||
return decoder.DecodeMsgpack(d)
|
||||
}
|
||||
|
||||
func unmarshalValueAddr(d *Decoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface())
|
||||
}
|
||||
return unmarshalValue(d, v.Addr())
|
||||
}
|
||||
|
||||
func unmarshalValue(d *Decoder, v reflect.Value) error {
|
||||
if d.hasNilCode() {
|
||||
return d.decodeNilValue(v)
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
|
||||
var b []byte
|
||||
|
||||
d.rec = make([]byte, 0, 64)
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
b = d.rec
|
||||
d.rec = nil
|
||||
|
||||
unmarshaler := v.Interface().(Unmarshaler)
|
||||
return unmarshaler.UnmarshalMsgpack(b)
|
||||
}
|
||||
|
||||
func decodeBoolValue(d *Decoder, v reflect.Value) error {
|
||||
flag, err := d.DecodeBool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetBool(flag)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeInterfaceValue(d *Decoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return d.interfaceValue(v)
|
||||
}
|
||||
return d.DecodeValue(v.Elem())
|
||||
}
|
||||
|
||||
func (d *Decoder) interfaceValue(v reflect.Value) error {
|
||||
vv, err := d.decodeInterfaceCond()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if vv != nil {
|
||||
if v.Type() == errorType {
|
||||
if vv, ok := vv.(string); ok {
|
||||
v.Set(reflect.ValueOf(errors.New(vv)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
v.Set(reflect.ValueOf(vv))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeUnsupportedValue(d *Decoder, v reflect.Value) error {
|
||||
return fmt.Errorf("msgpack: Decode(unsupported %s)", v.Type())
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func unmarshalBinaryValueAddr(d *Decoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface())
|
||||
}
|
||||
return unmarshalBinaryValue(d, v.Addr())
|
||||
}
|
||||
|
||||
func unmarshalBinaryValue(d *Decoder, v reflect.Value) error {
|
||||
if d.hasNilCode() {
|
||||
return d.decodeNilValue(v)
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
|
||||
data, err := d.DecodeBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unmarshaler := v.Interface().(encoding.BinaryUnmarshaler)
|
||||
return unmarshaler.UnmarshalBinary(data)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func unmarshalTextValueAddr(d *Decoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface())
|
||||
}
|
||||
return unmarshalTextValue(d, v.Addr())
|
||||
}
|
||||
|
||||
func unmarshalTextValue(d *Decoder, v reflect.Value) error {
|
||||
if d.hasNilCode() {
|
||||
return d.decodeNilValue(v)
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
|
||||
data, err := d.DecodeBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unmarshaler := v.Interface().(encoding.TextUnmarshaler)
|
||||
return unmarshaler.UnmarshalText(data)
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
const (
|
||||
sortMapKeysFlag uint32 = 1 << iota
|
||||
arrayEncodedStructsFlag
|
||||
useCompactIntsFlag
|
||||
useCompactFloatsFlag
|
||||
useInternedStringsFlag
|
||||
)
|
||||
|
||||
type writer interface {
|
||||
io.Writer
|
||||
WriteByte(byte) error
|
||||
}
|
||||
|
||||
type byteWriter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func newByteWriter(w io.Writer) byteWriter {
|
||||
return byteWriter{
|
||||
Writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (bw byteWriter) WriteByte(c byte) error {
|
||||
_, err := bw.Write([]byte{c})
|
||||
return err
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var encPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return NewEncoder(nil)
|
||||
},
|
||||
}
|
||||
|
||||
func GetEncoder() *Encoder {
|
||||
return encPool.Get().(*Encoder)
|
||||
}
|
||||
|
||||
func PutEncoder(enc *Encoder) {
|
||||
enc.w = nil
|
||||
encPool.Put(enc)
|
||||
}
|
||||
|
||||
// Marshal returns the MessagePack encoding of v.
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
enc := GetEncoder()
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc.Reset(&buf)
|
||||
|
||||
err := enc.Encode(v)
|
||||
b := buf.Bytes()
|
||||
|
||||
PutEncoder(enc)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
|
||||
type Encoder struct {
|
||||
w writer
|
||||
|
||||
buf []byte
|
||||
timeBuf []byte
|
||||
|
||||
dict map[string]int
|
||||
|
||||
flags uint32
|
||||
structTag string
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
e := &Encoder{
|
||||
buf: make([]byte, 9),
|
||||
}
|
||||
e.Reset(w)
|
||||
return e
|
||||
}
|
||||
|
||||
// Writer returns the Encoder's writer.
|
||||
func (e *Encoder) Writer() io.Writer {
|
||||
return e.w
|
||||
}
|
||||
|
||||
// Reset discards any buffered data, resets all state, and switches the writer to write to w.
|
||||
func (e *Encoder) Reset(w io.Writer) {
|
||||
e.ResetDict(w, nil)
|
||||
}
|
||||
|
||||
// ResetDict is like Reset, but also resets the dict.
|
||||
func (e *Encoder) ResetDict(w io.Writer, dict map[string]int) {
|
||||
e.resetWriter(w)
|
||||
e.flags = 0
|
||||
e.structTag = ""
|
||||
|
||||
if len(dict) > 0 {
|
||||
e.dict = dict
|
||||
} else {
|
||||
for k := range e.dict {
|
||||
delete(e.dict, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Encoder) resetWriter(w io.Writer) {
|
||||
if bw, ok := w.(writer); ok {
|
||||
e.w = bw
|
||||
} else {
|
||||
e.w = newByteWriter(w)
|
||||
}
|
||||
}
|
||||
|
||||
// SetSortMapKeys causes the Encoder to encode map keys in increasing order.
|
||||
// Supported map types are:
|
||||
// - map[string]string
|
||||
// - map[string]interface{}
|
||||
func (e *Encoder) SetSortMapKeys(on bool) *Encoder {
|
||||
if on {
|
||||
e.flags |= sortMapKeysFlag
|
||||
} else {
|
||||
e.flags &= ^sortMapKeysFlag
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// SetCustomStructTag causes the Encoder to use a custom struct tag as
|
||||
// fallback option if there is no msgpack tag.
|
||||
func (e *Encoder) SetCustomStructTag(tag string) {
|
||||
e.structTag = tag
|
||||
}
|
||||
|
||||
// UseArrayEncodedStructs causes the Encoder to encode Go structs as msgpack arrays.
|
||||
func (e *Encoder) UseArrayEncodedStructs(on bool) {
|
||||
if on {
|
||||
e.flags |= arrayEncodedStructsFlag
|
||||
} else {
|
||||
e.flags &= ^arrayEncodedStructsFlag
|
||||
}
|
||||
}
|
||||
|
||||
// UseCompactEncoding causes the Encoder to chose the most compact encoding.
|
||||
// For example, it allows to encode small Go int64 as msgpack int8 saving 7 bytes.
|
||||
func (e *Encoder) UseCompactInts(on bool) {
|
||||
if on {
|
||||
e.flags |= useCompactIntsFlag
|
||||
} else {
|
||||
e.flags &= ^useCompactIntsFlag
|
||||
}
|
||||
}
|
||||
|
||||
// UseCompactFloats causes the Encoder to chose a compact integer encoding
|
||||
// for floats that can be represented as integers.
|
||||
func (e *Encoder) UseCompactFloats(on bool) {
|
||||
if on {
|
||||
e.flags |= useCompactFloatsFlag
|
||||
} else {
|
||||
e.flags &= ^useCompactFloatsFlag
|
||||
}
|
||||
}
|
||||
|
||||
// UseInternedStrings causes the Encoder to intern strings.
|
||||
func (e *Encoder) UseInternedStrings(on bool) {
|
||||
if on {
|
||||
e.flags |= useInternedStringsFlag
|
||||
} else {
|
||||
e.flags &= ^useInternedStringsFlag
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Encoder) Encode(v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return e.EncodeNil()
|
||||
case string:
|
||||
return e.EncodeString(v)
|
||||
case []byte:
|
||||
return e.EncodeBytes(v)
|
||||
case int:
|
||||
return e.encodeInt64Cond(int64(v))
|
||||
case int64:
|
||||
return e.encodeInt64Cond(v)
|
||||
case uint:
|
||||
return e.encodeUint64Cond(uint64(v))
|
||||
case uint64:
|
||||
return e.encodeUint64Cond(v)
|
||||
case bool:
|
||||
return e.EncodeBool(v)
|
||||
case float32:
|
||||
return e.EncodeFloat32(v)
|
||||
case float64:
|
||||
return e.EncodeFloat64(v)
|
||||
case time.Duration:
|
||||
return e.encodeInt64Cond(int64(v))
|
||||
case time.Time:
|
||||
return e.EncodeTime(v)
|
||||
}
|
||||
return e.EncodeValue(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeMulti(v ...interface{}) error {
|
||||
for _, vv := range v {
|
||||
if err := e.Encode(vv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeValue(v reflect.Value) error {
|
||||
fn := getEncoder(v.Type())
|
||||
return fn(e, v)
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeNil() error {
|
||||
return e.writeCode(msgpcode.Nil)
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeBool(value bool) error {
|
||||
if value {
|
||||
return e.writeCode(msgpcode.True)
|
||||
}
|
||||
return e.writeCode(msgpcode.False)
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeDuration(d time.Duration) error {
|
||||
return e.EncodeInt(int64(d))
|
||||
}
|
||||
|
||||
func (e *Encoder) writeCode(c byte) error {
|
||||
return e.w.WriteByte(c)
|
||||
}
|
||||
|
||||
func (e *Encoder) write(b []byte) error {
|
||||
_, err := e.w.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Encoder) writeString(s string) error {
|
||||
_, err := e.w.Write(stringToBytes(s))
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
func encodeMapValue(e *Encoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
|
||||
if err := e.EncodeMapLen(v.Len()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, key := range v.MapKeys() {
|
||||
if err := e.EncodeValue(key); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.EncodeValue(v.MapIndex(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeMapStringStringValue(e *Encoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
|
||||
if err := e.EncodeMapLen(v.Len()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := v.Convert(mapStringStringType).Interface().(map[string]string)
|
||||
if e.flags&sortMapKeysFlag != 0 {
|
||||
return e.encodeSortedMapStringString(m)
|
||||
}
|
||||
|
||||
for mk, mv := range m {
|
||||
if err := e.EncodeString(mk); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.EncodeString(mv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeMapStringInterfaceValue(e *Encoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
m := v.Convert(mapStringInterfaceType).Interface().(map[string]interface{})
|
||||
if e.flags&sortMapKeysFlag != 0 {
|
||||
return e.EncodeMapSorted(m)
|
||||
}
|
||||
return e.EncodeMap(m)
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeMap(m map[string]interface{}) error {
|
||||
if m == nil {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
if err := e.EncodeMapLen(len(m)); err != nil {
|
||||
return err
|
||||
}
|
||||
for mk, mv := range m {
|
||||
if err := e.EncodeString(mk); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.Encode(mv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeMapSorted(m map[string]interface{}) error {
|
||||
if m == nil {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
if err := e.EncodeMapLen(len(m)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(m))
|
||||
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
if err := e.EncodeString(k); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.Encode(m[k]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeSortedMapStringString(m map[string]string) error {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
err := e.EncodeString(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = e.EncodeString(m[k]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeMapLen(l int) error {
|
||||
if l < 16 {
|
||||
return e.writeCode(msgpcode.FixedMapLow | byte(l))
|
||||
}
|
||||
if l <= math.MaxUint16 {
|
||||
return e.write2(msgpcode.Map16, uint16(l))
|
||||
}
|
||||
return e.write4(msgpcode.Map32, uint32(l))
|
||||
}
|
||||
|
||||
func encodeStructValue(e *Encoder, strct reflect.Value) error {
|
||||
structFields := structs.Fields(strct.Type(), e.structTag)
|
||||
if e.flags&arrayEncodedStructsFlag != 0 || structFields.AsArray {
|
||||
return encodeStructValueAsArray(e, strct, structFields.List)
|
||||
}
|
||||
fields := structFields.OmitEmpty(strct)
|
||||
|
||||
if err := e.EncodeMapLen(len(fields)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
if err := e.EncodeString(f.name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.EncodeValue(e, strct); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeStructValueAsArray(e *Encoder, strct reflect.Value, fields []*field) error {
|
||||
if err := e.EncodeArrayLen(len(fields)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range fields {
|
||||
if err := f.EncodeValue(e, strct); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
// EncodeUint8 encodes an uint8 in 2 bytes preserving type of the number.
|
||||
func (e *Encoder) EncodeUint8(n uint8) error {
|
||||
return e.write1(msgpcode.Uint8, n)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeUint8Cond(n uint8) error {
|
||||
if e.flags&useCompactIntsFlag != 0 {
|
||||
return e.EncodeUint(uint64(n))
|
||||
}
|
||||
return e.EncodeUint8(n)
|
||||
}
|
||||
|
||||
// EncodeUint16 encodes an uint16 in 3 bytes preserving type of the number.
|
||||
func (e *Encoder) EncodeUint16(n uint16) error {
|
||||
return e.write2(msgpcode.Uint16, n)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeUint16Cond(n uint16) error {
|
||||
if e.flags&useCompactIntsFlag != 0 {
|
||||
return e.EncodeUint(uint64(n))
|
||||
}
|
||||
return e.EncodeUint16(n)
|
||||
}
|
||||
|
||||
// EncodeUint32 encodes an uint16 in 5 bytes preserving type of the number.
|
||||
func (e *Encoder) EncodeUint32(n uint32) error {
|
||||
return e.write4(msgpcode.Uint32, n)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeUint32Cond(n uint32) error {
|
||||
if e.flags&useCompactIntsFlag != 0 {
|
||||
return e.EncodeUint(uint64(n))
|
||||
}
|
||||
return e.EncodeUint32(n)
|
||||
}
|
||||
|
||||
// EncodeUint64 encodes an uint16 in 9 bytes preserving type of the number.
|
||||
func (e *Encoder) EncodeUint64(n uint64) error {
|
||||
return e.write8(msgpcode.Uint64, n)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeUint64Cond(n uint64) error {
|
||||
if e.flags&useCompactIntsFlag != 0 {
|
||||
return e.EncodeUint(n)
|
||||
}
|
||||
return e.EncodeUint64(n)
|
||||
}
|
||||
|
||||
// EncodeInt8 encodes an int8 in 2 bytes preserving type of the number.
|
||||
func (e *Encoder) EncodeInt8(n int8) error {
|
||||
return e.write1(msgpcode.Int8, uint8(n))
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeInt8Cond(n int8) error {
|
||||
if e.flags&useCompactIntsFlag != 0 {
|
||||
return e.EncodeInt(int64(n))
|
||||
}
|
||||
return e.EncodeInt8(n)
|
||||
}
|
||||
|
||||
// EncodeInt16 encodes an int16 in 3 bytes preserving type of the number.
|
||||
func (e *Encoder) EncodeInt16(n int16) error {
|
||||
return e.write2(msgpcode.Int16, uint16(n))
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeInt16Cond(n int16) error {
|
||||
if e.flags&useCompactIntsFlag != 0 {
|
||||
return e.EncodeInt(int64(n))
|
||||
}
|
||||
return e.EncodeInt16(n)
|
||||
}
|
||||
|
||||
// EncodeInt32 encodes an int32 in 5 bytes preserving type of the number.
|
||||
func (e *Encoder) EncodeInt32(n int32) error {
|
||||
return e.write4(msgpcode.Int32, uint32(n))
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeInt32Cond(n int32) error {
|
||||
if e.flags&useCompactIntsFlag != 0 {
|
||||
return e.EncodeInt(int64(n))
|
||||
}
|
||||
return e.EncodeInt32(n)
|
||||
}
|
||||
|
||||
// EncodeInt64 encodes an int64 in 9 bytes preserving type of the number.
|
||||
func (e *Encoder) EncodeInt64(n int64) error {
|
||||
return e.write8(msgpcode.Int64, uint64(n))
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeInt64Cond(n int64) error {
|
||||
if e.flags&useCompactIntsFlag != 0 {
|
||||
return e.EncodeInt(n)
|
||||
}
|
||||
return e.EncodeInt64(n)
|
||||
}
|
||||
|
||||
// EncodeUnsignedNumber encodes an uint64 in 1, 2, 3, 5, or 9 bytes.
|
||||
// Type of the number is lost during encoding.
|
||||
func (e *Encoder) EncodeUint(n uint64) error {
|
||||
if n <= math.MaxInt8 {
|
||||
return e.w.WriteByte(byte(n))
|
||||
}
|
||||
if n <= math.MaxUint8 {
|
||||
return e.EncodeUint8(uint8(n))
|
||||
}
|
||||
if n <= math.MaxUint16 {
|
||||
return e.EncodeUint16(uint16(n))
|
||||
}
|
||||
if n <= math.MaxUint32 {
|
||||
return e.EncodeUint32(uint32(n))
|
||||
}
|
||||
return e.EncodeUint64(n)
|
||||
}
|
||||
|
||||
// EncodeNumber encodes an int64 in 1, 2, 3, 5, or 9 bytes.
|
||||
// Type of the number is lost during encoding.
|
||||
func (e *Encoder) EncodeInt(n int64) error {
|
||||
if n >= 0 {
|
||||
return e.EncodeUint(uint64(n))
|
||||
}
|
||||
if n >= int64(int8(msgpcode.NegFixedNumLow)) {
|
||||
return e.w.WriteByte(byte(n))
|
||||
}
|
||||
if n >= math.MinInt8 {
|
||||
return e.EncodeInt8(int8(n))
|
||||
}
|
||||
if n >= math.MinInt16 {
|
||||
return e.EncodeInt16(int16(n))
|
||||
}
|
||||
if n >= math.MinInt32 {
|
||||
return e.EncodeInt32(int32(n))
|
||||
}
|
||||
return e.EncodeInt64(n)
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeFloat32(n float32) error {
|
||||
if e.flags&useCompactFloatsFlag != 0 {
|
||||
if float32(int64(n)) == n {
|
||||
return e.EncodeInt(int64(n))
|
||||
}
|
||||
}
|
||||
return e.write4(msgpcode.Float, math.Float32bits(n))
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeFloat64(n float64) error {
|
||||
if e.flags&useCompactFloatsFlag != 0 {
|
||||
// Both NaN and Inf convert to int64(-0x8000000000000000)
|
||||
// If n is NaN then it never compares true with any other value
|
||||
// If n is Inf then it doesn't convert from int64 back to +/-Inf
|
||||
// In both cases the comparison works.
|
||||
if float64(int64(n)) == n {
|
||||
return e.EncodeInt(int64(n))
|
||||
}
|
||||
}
|
||||
return e.write8(msgpcode.Double, math.Float64bits(n))
|
||||
}
|
||||
|
||||
func (e *Encoder) write1(code byte, n uint8) error {
|
||||
e.buf = e.buf[:2]
|
||||
e.buf[0] = code
|
||||
e.buf[1] = n
|
||||
return e.write(e.buf)
|
||||
}
|
||||
|
||||
func (e *Encoder) write2(code byte, n uint16) error {
|
||||
e.buf = e.buf[:3]
|
||||
e.buf[0] = code
|
||||
e.buf[1] = byte(n >> 8)
|
||||
e.buf[2] = byte(n)
|
||||
return e.write(e.buf)
|
||||
}
|
||||
|
||||
func (e *Encoder) write4(code byte, n uint32) error {
|
||||
e.buf = e.buf[:5]
|
||||
e.buf[0] = code
|
||||
e.buf[1] = byte(n >> 24)
|
||||
e.buf[2] = byte(n >> 16)
|
||||
e.buf[3] = byte(n >> 8)
|
||||
e.buf[4] = byte(n)
|
||||
return e.write(e.buf)
|
||||
}
|
||||
|
||||
func (e *Encoder) write8(code byte, n uint64) error {
|
||||
e.buf = e.buf[:9]
|
||||
e.buf[0] = code
|
||||
e.buf[1] = byte(n >> 56)
|
||||
e.buf[2] = byte(n >> 48)
|
||||
e.buf[3] = byte(n >> 40)
|
||||
e.buf[4] = byte(n >> 32)
|
||||
e.buf[5] = byte(n >> 24)
|
||||
e.buf[6] = byte(n >> 16)
|
||||
e.buf[7] = byte(n >> 8)
|
||||
e.buf[8] = byte(n)
|
||||
return e.write(e.buf)
|
||||
}
|
||||
|
||||
func encodeUint8CondValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeUint8Cond(uint8(v.Uint()))
|
||||
}
|
||||
|
||||
func encodeUint16CondValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeUint16Cond(uint16(v.Uint()))
|
||||
}
|
||||
|
||||
func encodeUint32CondValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeUint32Cond(uint32(v.Uint()))
|
||||
}
|
||||
|
||||
func encodeUint64CondValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeUint64Cond(v.Uint())
|
||||
}
|
||||
|
||||
func encodeInt8CondValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeInt8Cond(int8(v.Int()))
|
||||
}
|
||||
|
||||
func encodeInt16CondValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeInt16Cond(int16(v.Int()))
|
||||
}
|
||||
|
||||
func encodeInt32CondValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeInt32Cond(int32(v.Int()))
|
||||
}
|
||||
|
||||
func encodeInt64CondValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeInt64Cond(v.Int())
|
||||
}
|
||||
|
||||
func encodeFloat32Value(e *Encoder, v reflect.Value) error {
|
||||
return e.EncodeFloat32(float32(v.Float()))
|
||||
}
|
||||
|
||||
func encodeFloat64Value(e *Encoder, v reflect.Value) error {
|
||||
return e.EncodeFloat64(v.Float())
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
var stringSliceType = reflect.TypeOf(([]string)(nil))
|
||||
|
||||
func encodeStringValue(e *Encoder, v reflect.Value) error {
|
||||
return e.EncodeString(v.String())
|
||||
}
|
||||
|
||||
func encodeByteSliceValue(e *Encoder, v reflect.Value) error {
|
||||
return e.EncodeBytes(v.Bytes())
|
||||
}
|
||||
|
||||
func encodeByteArrayValue(e *Encoder, v reflect.Value) error {
|
||||
if err := e.EncodeBytesLen(v.Len()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v.CanAddr() {
|
||||
b := v.Slice(0, v.Len()).Bytes()
|
||||
return e.write(b)
|
||||
}
|
||||
|
||||
e.buf = grow(e.buf, v.Len())
|
||||
reflect.Copy(reflect.ValueOf(e.buf), v)
|
||||
return e.write(e.buf)
|
||||
}
|
||||
|
||||
func grow(b []byte, n int) []byte {
|
||||
if cap(b) >= n {
|
||||
return b[:n]
|
||||
}
|
||||
b = b[:cap(b)]
|
||||
b = append(b, make([]byte, n-len(b))...)
|
||||
return b
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeBytesLen(l int) error {
|
||||
if l < 256 {
|
||||
return e.write1(msgpcode.Bin8, uint8(l))
|
||||
}
|
||||
if l <= math.MaxUint16 {
|
||||
return e.write2(msgpcode.Bin16, uint16(l))
|
||||
}
|
||||
return e.write4(msgpcode.Bin32, uint32(l))
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeStringLen(l int) error {
|
||||
if l < 32 {
|
||||
return e.writeCode(msgpcode.FixedStrLow | byte(l))
|
||||
}
|
||||
if l < 256 {
|
||||
return e.write1(msgpcode.Str8, uint8(l))
|
||||
}
|
||||
if l <= math.MaxUint16 {
|
||||
return e.write2(msgpcode.Str16, uint16(l))
|
||||
}
|
||||
return e.write4(msgpcode.Str32, uint32(l))
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeString(v string) error {
|
||||
if intern := e.flags&useInternedStringsFlag != 0; intern || len(e.dict) > 0 {
|
||||
return e.encodeInternedString(v, intern)
|
||||
}
|
||||
return e.encodeNormalString(v)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeNormalString(v string) error {
|
||||
if err := e.encodeStringLen(len(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.writeString(v)
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeBytes(v []byte) error {
|
||||
if v == nil {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
if err := e.EncodeBytesLen(len(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.write(v)
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeArrayLen(l int) error {
|
||||
if l < 16 {
|
||||
return e.writeCode(msgpcode.FixedArrayLow | byte(l))
|
||||
}
|
||||
if l <= math.MaxUint16 {
|
||||
return e.write2(msgpcode.Array16, uint16(l))
|
||||
}
|
||||
return e.write4(msgpcode.Array32, uint32(l))
|
||||
}
|
||||
|
||||
func encodeStringSliceValue(e *Encoder, v reflect.Value) error {
|
||||
ss := v.Convert(stringSliceType).Interface().([]string)
|
||||
return e.encodeStringSlice(ss)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeStringSlice(s []string) error {
|
||||
if s == nil {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
if err := e.EncodeArrayLen(len(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range s {
|
||||
if err := e.EncodeString(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeSliceValue(e *Encoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
return encodeArrayValue(e, v)
|
||||
}
|
||||
|
||||
func encodeArrayValue(e *Encoder, v reflect.Value) error {
|
||||
l := v.Len()
|
||||
if err := e.EncodeArrayLen(l); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
if err := e.EncodeValue(v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var valueEncoders []encoderFunc
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
valueEncoders = []encoderFunc{
|
||||
reflect.Bool: encodeBoolValue,
|
||||
reflect.Int: encodeInt64CondValue,
|
||||
reflect.Int8: encodeInt8CondValue,
|
||||
reflect.Int16: encodeInt16CondValue,
|
||||
reflect.Int32: encodeInt32CondValue,
|
||||
reflect.Int64: encodeInt64CondValue,
|
||||
reflect.Uint: encodeUint64CondValue,
|
||||
reflect.Uint8: encodeUint8CondValue,
|
||||
reflect.Uint16: encodeUint16CondValue,
|
||||
reflect.Uint32: encodeUint32CondValue,
|
||||
reflect.Uint64: encodeUint64CondValue,
|
||||
reflect.Float32: encodeFloat32Value,
|
||||
reflect.Float64: encodeFloat64Value,
|
||||
reflect.Complex64: encodeUnsupportedValue,
|
||||
reflect.Complex128: encodeUnsupportedValue,
|
||||
reflect.Array: encodeArrayValue,
|
||||
reflect.Chan: encodeUnsupportedValue,
|
||||
reflect.Func: encodeUnsupportedValue,
|
||||
reflect.Interface: encodeInterfaceValue,
|
||||
reflect.Map: encodeMapValue,
|
||||
reflect.Ptr: encodeUnsupportedValue,
|
||||
reflect.Slice: encodeSliceValue,
|
||||
reflect.String: encodeStringValue,
|
||||
reflect.Struct: encodeStructValue,
|
||||
reflect.UnsafePointer: encodeUnsupportedValue,
|
||||
}
|
||||
}
|
||||
|
||||
func getEncoder(typ reflect.Type) encoderFunc {
|
||||
if v, ok := typeEncMap.Load(typ); ok {
|
||||
return v.(encoderFunc)
|
||||
}
|
||||
fn := _getEncoder(typ)
|
||||
typeEncMap.Store(typ, fn)
|
||||
return fn
|
||||
}
|
||||
|
||||
func _getEncoder(typ reflect.Type) encoderFunc {
|
||||
kind := typ.Kind()
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
if _, ok := typeEncMap.Load(typ.Elem()); ok {
|
||||
return ptrEncoderFunc(typ)
|
||||
}
|
||||
}
|
||||
|
||||
if typ.Implements(customEncoderType) {
|
||||
return encodeCustomValue
|
||||
}
|
||||
if typ.Implements(marshalerType) {
|
||||
return marshalValue
|
||||
}
|
||||
if typ.Implements(binaryMarshalerType) {
|
||||
return marshalBinaryValue
|
||||
}
|
||||
if typ.Implements(textMarshalerType) {
|
||||
return marshalTextValue
|
||||
}
|
||||
|
||||
// Addressable struct field value.
|
||||
if kind != reflect.Ptr {
|
||||
ptr := reflect.PtrTo(typ)
|
||||
if ptr.Implements(customEncoderType) {
|
||||
return encodeCustomValuePtr
|
||||
}
|
||||
if ptr.Implements(marshalerType) {
|
||||
return marshalValuePtr
|
||||
}
|
||||
if ptr.Implements(binaryMarshalerType) {
|
||||
return marshalBinaryValueAddr
|
||||
}
|
||||
if ptr.Implements(textMarshalerType) {
|
||||
return marshalTextValueAddr
|
||||
}
|
||||
}
|
||||
|
||||
if typ == errorType {
|
||||
return encodeErrorValue
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr:
|
||||
return ptrEncoderFunc(typ)
|
||||
case reflect.Slice:
|
||||
elem := typ.Elem()
|
||||
if elem.Kind() == reflect.Uint8 {
|
||||
return encodeByteSliceValue
|
||||
}
|
||||
if elem == stringType {
|
||||
return encodeStringSliceValue
|
||||
}
|
||||
case reflect.Array:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
return encodeByteArrayValue
|
||||
}
|
||||
case reflect.Map:
|
||||
if typ.Key() == stringType {
|
||||
switch typ.Elem() {
|
||||
case stringType:
|
||||
return encodeMapStringStringValue
|
||||
case interfaceType:
|
||||
return encodeMapStringInterfaceValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valueEncoders[kind]
|
||||
}
|
||||
|
||||
func ptrEncoderFunc(typ reflect.Type) encoderFunc {
|
||||
encoder := getEncoder(typ.Elem())
|
||||
return func(e *Encoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
return encoder(e, v.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
func encodeCustomValuePtr(e *Encoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface())
|
||||
}
|
||||
encoder := v.Addr().Interface().(CustomEncoder)
|
||||
return encoder.EncodeMsgpack(e)
|
||||
}
|
||||
|
||||
func encodeCustomValue(e *Encoder, v reflect.Value) error {
|
||||
if nilable(v) && v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
|
||||
encoder := v.Interface().(CustomEncoder)
|
||||
return encoder.EncodeMsgpack(e)
|
||||
}
|
||||
|
||||
func marshalValuePtr(e *Encoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface())
|
||||
}
|
||||
return marshalValue(e, v.Addr())
|
||||
}
|
||||
|
||||
func marshalValue(e *Encoder, v reflect.Value) error {
|
||||
if nilable(v) && v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
|
||||
marshaler := v.Interface().(Marshaler)
|
||||
b, err := marshaler.MarshalMsgpack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = e.w.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func encodeBoolValue(e *Encoder, v reflect.Value) error {
|
||||
return e.EncodeBool(v.Bool())
|
||||
}
|
||||
|
||||
func encodeInterfaceValue(e *Encoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
return e.EncodeValue(v.Elem())
|
||||
}
|
||||
|
||||
func encodeErrorValue(e *Encoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
return e.EncodeString(v.Interface().(error).Error())
|
||||
}
|
||||
|
||||
func encodeUnsupportedValue(e *Encoder, v reflect.Value) error {
|
||||
return fmt.Errorf("msgpack: Encode(unsupported %s)", v.Type())
|
||||
}
|
||||
|
||||
func nilable(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func marshalBinaryValueAddr(e *Encoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface())
|
||||
}
|
||||
return marshalBinaryValue(e, v.Addr())
|
||||
}
|
||||
|
||||
func marshalBinaryValue(e *Encoder, v reflect.Value) error {
|
||||
if nilable(v) && v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
|
||||
marshaler := v.Interface().(encoding.BinaryMarshaler)
|
||||
data, err := marshaler.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.EncodeBytes(data)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func marshalTextValueAddr(e *Encoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface())
|
||||
}
|
||||
return marshalTextValue(e, v.Addr())
|
||||
}
|
||||
|
||||
func marshalTextValue(e *Encoder, v reflect.Value) error {
|
||||
if nilable(v) && v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
|
||||
marshaler := v.Interface().(encoding.TextMarshaler)
|
||||
data, err := marshaler.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.EncodeBytes(data)
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
type extInfo struct {
|
||||
Type reflect.Type
|
||||
Decoder func(d *Decoder, v reflect.Value, extLen int) error
|
||||
}
|
||||
|
||||
var extTypes = make(map[int8]*extInfo)
|
||||
|
||||
type MarshalerUnmarshaler interface {
|
||||
Marshaler
|
||||
Unmarshaler
|
||||
}
|
||||
|
||||
func RegisterExt(extID int8, value MarshalerUnmarshaler) {
|
||||
RegisterExtEncoder(extID, value, func(e *Encoder, v reflect.Value) ([]byte, error) {
|
||||
marshaler := v.Interface().(Marshaler)
|
||||
return marshaler.MarshalMsgpack()
|
||||
})
|
||||
RegisterExtDecoder(extID, value, func(d *Decoder, v reflect.Value, extLen int) error {
|
||||
b, err := d.readN(extLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.Interface().(Unmarshaler).UnmarshalMsgpack(b)
|
||||
})
|
||||
}
|
||||
|
||||
func RegisterExtEncoder(
|
||||
extID int8,
|
||||
value interface{},
|
||||
encoder func(enc *Encoder, v reflect.Value) ([]byte, error),
|
||||
) {
|
||||
typ := reflect.TypeOf(value)
|
||||
extEncoder := makeExtEncoder(extID, typ, encoder)
|
||||
|
||||
typeEncMap.Store(typ, extEncoder)
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typeEncMap.Store(typ.Elem(), makeExtEncoderAddr(extEncoder))
|
||||
}
|
||||
}
|
||||
|
||||
func makeExtEncoder(
|
||||
extID int8,
|
||||
typ reflect.Type,
|
||||
encoder func(enc *Encoder, v reflect.Value) ([]byte, error),
|
||||
) encoderFunc {
|
||||
nilable := typ.Kind() == reflect.Ptr
|
||||
|
||||
return func(e *Encoder, v reflect.Value) error {
|
||||
if nilable && v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
|
||||
b, err := encoder(e, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.EncodeExtHeader(extID, len(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.write(b)
|
||||
}
|
||||
}
|
||||
|
||||
func makeExtEncoderAddr(extEncoder encoderFunc) encoderFunc {
|
||||
return func(e *Encoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface())
|
||||
}
|
||||
return extEncoder(e, v.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterExtDecoder(
|
||||
extID int8,
|
||||
value interface{},
|
||||
decoder func(dec *Decoder, v reflect.Value, extLen int) error,
|
||||
) {
|
||||
typ := reflect.TypeOf(value)
|
||||
extDecoder := makeExtDecoder(extID, typ, decoder)
|
||||
extTypes[extID] = &extInfo{
|
||||
Type: typ,
|
||||
Decoder: decoder,
|
||||
}
|
||||
|
||||
typeDecMap.Store(typ, extDecoder)
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typeDecMap.Store(typ.Elem(), makeExtDecoderAddr(extDecoder))
|
||||
}
|
||||
}
|
||||
|
||||
func makeExtDecoder(
|
||||
wantedExtID int8,
|
||||
typ reflect.Type,
|
||||
decoder func(d *Decoder, v reflect.Value, extLen int) error,
|
||||
) decoderFunc {
|
||||
nilable := typ.Kind() == reflect.Ptr
|
||||
|
||||
return func(d *Decoder, v reflect.Value) error {
|
||||
if d.hasNilCode() {
|
||||
v.Set(reflect.Zero(typ))
|
||||
return d.DecodeNil()
|
||||
}
|
||||
|
||||
if nilable && v.IsNil() {
|
||||
v.Set(reflect.New(typ.Elem()))
|
||||
}
|
||||
|
||||
extID, extLen, err := d.DecodeExtHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if extID != wantedExtID {
|
||||
return fmt.Errorf("msgpack: got ext type=%d, wanted %d", extID, wantedExtID)
|
||||
}
|
||||
return decoder(d, v, extLen)
|
||||
}
|
||||
}
|
||||
|
||||
func makeExtDecoderAddr(extDecoder decoderFunc) decoderFunc {
|
||||
return func(d *Decoder, v reflect.Value) error {
|
||||
if !v.CanAddr() {
|
||||
return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface())
|
||||
}
|
||||
return extDecoder(d, v.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeExtHeader(extID int8, extLen int) error {
|
||||
if err := e.encodeExtLen(extLen); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.w.WriteByte(byte(extID)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeExtLen(l int) error {
|
||||
switch l {
|
||||
case 1:
|
||||
return e.writeCode(msgpcode.FixExt1)
|
||||
case 2:
|
||||
return e.writeCode(msgpcode.FixExt2)
|
||||
case 4:
|
||||
return e.writeCode(msgpcode.FixExt4)
|
||||
case 8:
|
||||
return e.writeCode(msgpcode.FixExt8)
|
||||
case 16:
|
||||
return e.writeCode(msgpcode.FixExt16)
|
||||
}
|
||||
if l <= math.MaxUint8 {
|
||||
return e.write1(msgpcode.Ext8, uint8(l))
|
||||
}
|
||||
if l <= math.MaxUint16 {
|
||||
return e.write2(msgpcode.Ext16, uint16(l))
|
||||
}
|
||||
return e.write4(msgpcode.Ext32, uint32(l))
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeExtHeader() (extID int8, extLen int, err error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return d.extHeader(c)
|
||||
}
|
||||
|
||||
func (d *Decoder) extHeader(c byte) (int8, int, error) {
|
||||
extLen, err := d.parseExtLen(c)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
extID, err := d.readCode()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return int8(extID), extLen, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) parseExtLen(c byte) (int, error) {
|
||||
switch c {
|
||||
case msgpcode.FixExt1:
|
||||
return 1, nil
|
||||
case msgpcode.FixExt2:
|
||||
return 2, nil
|
||||
case msgpcode.FixExt4:
|
||||
return 4, nil
|
||||
case msgpcode.FixExt8:
|
||||
return 8, nil
|
||||
case msgpcode.FixExt16:
|
||||
return 16, nil
|
||||
case msgpcode.Ext8:
|
||||
n, err := d.uint8()
|
||||
return int(n), err
|
||||
case msgpcode.Ext16:
|
||||
n, err := d.uint16()
|
||||
return int(n), err
|
||||
case msgpcode.Ext32:
|
||||
n, err := d.uint32()
|
||||
return int(n), err
|
||||
default:
|
||||
return 0, fmt.Errorf("msgpack: invalid code=%x decoding ext len", c)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeInterfaceExt(c byte) (interface{}, error) {
|
||||
extID, extLen, err := d.extHeader(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, ok := extTypes[extID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("msgpack: unknown ext id=%d", extID)
|
||||
}
|
||||
|
||||
v := reflect.New(info.Type).Elem()
|
||||
if nilable(v) && v.IsNil() {
|
||||
v.Set(reflect.New(info.Type.Elem()))
|
||||
}
|
||||
|
||||
if err := info.Decoder(d, v, extLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v.Interface(), nil
|
||||
}
|
||||
|
||||
func (d *Decoder) skipExt(c byte) error {
|
||||
n, err := d.parseExtLen(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.skipN(n + 1)
|
||||
}
|
||||
|
||||
func (d *Decoder) skipExtHeader(c byte) error {
|
||||
// Read ext type.
|
||||
_, err := d.readCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Read ext body len.
|
||||
for i := 0; i < extHeaderLen(c); i++ {
|
||||
_, err := d.readCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extHeaderLen(c byte) int {
|
||||
switch c {
|
||||
case msgpcode.Ext8:
|
||||
return 1
|
||||
case msgpcode.Ext16:
|
||||
return 2
|
||||
case msgpcode.Ext32:
|
||||
return 4
|
||||
}
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
module github.com/vmihailenco/msgpack/v5
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/vmihailenco/tagparser v0.1.2
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
|
||||
)
|
||||
|
||||
go 1.11
|
|
@ -0,0 +1,20 @@
|
|||
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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
||||
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,238 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
const (
|
||||
minInternedStringLen = 3
|
||||
maxDictLen = math.MaxUint16
|
||||
)
|
||||
|
||||
var internedStringExtID = int8(math.MinInt8)
|
||||
|
||||
func init() {
|
||||
extTypes[internedStringExtID] = &extInfo{
|
||||
Type: stringType,
|
||||
Decoder: decodeInternedStringExt,
|
||||
}
|
||||
}
|
||||
|
||||
func decodeInternedStringExt(d *Decoder, v reflect.Value, extLen int) error {
|
||||
idx, err := d.decodeInternedStringIndex(extLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := d.internedStringAtIndex(idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.SetString(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func encodeInternedInterfaceValue(e *Encoder, v reflect.Value) error {
|
||||
if v.IsNil() {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
|
||||
v = v.Elem()
|
||||
if v.Kind() == reflect.String {
|
||||
return e.encodeInternedString(v.String(), true)
|
||||
}
|
||||
return e.EncodeValue(v)
|
||||
}
|
||||
|
||||
func encodeInternedStringValue(e *Encoder, v reflect.Value) error {
|
||||
return e.encodeInternedString(v.String(), true)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeInternedString(s string, intern bool) error {
|
||||
// Interned string takes at least 3 bytes. Plain string 1 byte + string len.
|
||||
if len(s) >= minInternedStringLen {
|
||||
if idx, ok := e.dict[s]; ok {
|
||||
return e.encodeInternedStringIndex(idx)
|
||||
}
|
||||
|
||||
if intern && len(e.dict) < maxDictLen {
|
||||
if e.dict == nil {
|
||||
e.dict = make(map[string]int)
|
||||
}
|
||||
idx := len(e.dict)
|
||||
e.dict[s] = idx
|
||||
}
|
||||
}
|
||||
|
||||
return e.encodeNormalString(s)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeInternedStringIndex(idx int) error {
|
||||
if idx <= math.MaxUint8 {
|
||||
if err := e.writeCode(msgpcode.FixExt1); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.write1(byte(internedStringExtID), uint8(idx))
|
||||
}
|
||||
|
||||
if idx <= math.MaxUint16 {
|
||||
if err := e.writeCode(msgpcode.FixExt2); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.write2(byte(internedStringExtID), uint16(idx))
|
||||
}
|
||||
|
||||
if uint64(idx) <= math.MaxUint32 {
|
||||
if err := e.writeCode(msgpcode.FixExt4); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.write4(byte(internedStringExtID), uint32(idx))
|
||||
}
|
||||
|
||||
return fmt.Errorf("msgpack: interned string index=%d is too large", idx)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func decodeInternedInterfaceValue(d *Decoder, v reflect.Value) error {
|
||||
s, err := d.decodeInternedString(true)
|
||||
if err == nil {
|
||||
v.Set(reflect.ValueOf(s))
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
if _, ok := err.(unexpectedCodeError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.s.UnreadByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
return decodeInterfaceValue(d, v)
|
||||
}
|
||||
|
||||
func decodeInternedStringValue(d *Decoder, v reflect.Value) error {
|
||||
s, err := d.decodeInternedString(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.SetString(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeInternedString(intern bool) (string, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if msgpcode.IsFixedString(c) {
|
||||
n := int(c & msgpcode.FixedStrMask)
|
||||
return d.decodeInternedStringWithLen(n, intern)
|
||||
}
|
||||
|
||||
switch c {
|
||||
case msgpcode.Nil:
|
||||
return "", nil
|
||||
case msgpcode.FixExt1, msgpcode.FixExt2, msgpcode.FixExt4:
|
||||
typeID, extLen, err := d.extHeader(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if typeID != internedStringExtID {
|
||||
err := fmt.Errorf("msgpack: got ext type=%d, wanted %d",
|
||||
typeID, internedStringExtID)
|
||||
return "", err
|
||||
}
|
||||
|
||||
idx, err := d.decodeInternedStringIndex(extLen)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return d.internedStringAtIndex(idx)
|
||||
case msgpcode.Str8, msgpcode.Bin8:
|
||||
n, err := d.uint8()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return d.decodeInternedStringWithLen(int(n), intern)
|
||||
case msgpcode.Str16, msgpcode.Bin16:
|
||||
n, err := d.uint16()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return d.decodeInternedStringWithLen(int(n), intern)
|
||||
case msgpcode.Str32, msgpcode.Bin32:
|
||||
n, err := d.uint32()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return d.decodeInternedStringWithLen(int(n), intern)
|
||||
}
|
||||
|
||||
return "", unexpectedCodeError{
|
||||
code: c,
|
||||
hint: "interned string",
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeInternedStringIndex(extLen int) (int, error) {
|
||||
switch extLen {
|
||||
case 1:
|
||||
n, err := d.uint8()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(n), nil
|
||||
case 2:
|
||||
n, err := d.uint16()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(n), nil
|
||||
case 4:
|
||||
n, err := d.uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
err := fmt.Errorf("msgpack: unsupported ext len=%d decoding interned string", extLen)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (d *Decoder) internedStringAtIndex(idx int) (string, error) {
|
||||
if idx >= len(d.dict) {
|
||||
err := fmt.Errorf("msgpack: interned string at index=%d does not exist", idx)
|
||||
return "", err
|
||||
}
|
||||
return d.dict[idx], nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeInternedStringWithLen(n int, intern bool) (string, error) {
|
||||
if n <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
s, err := d.stringWithLen(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if intern && len(s) >= minInternedStringLen && len(d.dict) < maxDictLen {
|
||||
d.dict = append(d.dict, s)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package msgpack
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Marshaler interface {
|
||||
MarshalMsgpack() ([]byte, error)
|
||||
}
|
||||
|
||||
type Unmarshaler interface {
|
||||
UnmarshalMsgpack([]byte) error
|
||||
}
|
||||
|
||||
type CustomEncoder interface {
|
||||
EncodeMsgpack(*Encoder) error
|
||||
}
|
||||
|
||||
type CustomDecoder interface {
|
||||
DecodeMsgpack(*Decoder) error
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type RawMessage []byte
|
||||
|
||||
var (
|
||||
_ CustomEncoder = (RawMessage)(nil)
|
||||
_ CustomDecoder = (*RawMessage)(nil)
|
||||
)
|
||||
|
||||
func (m RawMessage) EncodeMsgpack(enc *Encoder) error {
|
||||
return enc.write(m)
|
||||
}
|
||||
|
||||
func (m *RawMessage) DecodeMsgpack(dec *Decoder) error {
|
||||
msg, err := dec.DecodeRaw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*m = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type unexpectedCodeError struct {
|
||||
code byte
|
||||
hint string
|
||||
}
|
||||
|
||||
func (err unexpectedCodeError) Error() string {
|
||||
return fmt.Sprintf("msgpack: unexpected code=%x decoding %s", err.code, err.hint)
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package msgpcode
|
||||
|
||||
var (
|
||||
PosFixedNumHigh byte = 0x7f
|
||||
NegFixedNumLow byte = 0xe0
|
||||
|
||||
Nil byte = 0xc0
|
||||
|
||||
False byte = 0xc2
|
||||
True byte = 0xc3
|
||||
|
||||
Float byte = 0xca
|
||||
Double byte = 0xcb
|
||||
|
||||
Uint8 byte = 0xcc
|
||||
Uint16 byte = 0xcd
|
||||
Uint32 byte = 0xce
|
||||
Uint64 byte = 0xcf
|
||||
|
||||
Int8 byte = 0xd0
|
||||
Int16 byte = 0xd1
|
||||
Int32 byte = 0xd2
|
||||
Int64 byte = 0xd3
|
||||
|
||||
FixedStrLow byte = 0xa0
|
||||
FixedStrHigh byte = 0xbf
|
||||
FixedStrMask byte = 0x1f
|
||||
Str8 byte = 0xd9
|
||||
Str16 byte = 0xda
|
||||
Str32 byte = 0xdb
|
||||
|
||||
Bin8 byte = 0xc4
|
||||
Bin16 byte = 0xc5
|
||||
Bin32 byte = 0xc6
|
||||
|
||||
FixedArrayLow byte = 0x90
|
||||
FixedArrayHigh byte = 0x9f
|
||||
FixedArrayMask byte = 0xf
|
||||
Array16 byte = 0xdc
|
||||
Array32 byte = 0xdd
|
||||
|
||||
FixedMapLow byte = 0x80
|
||||
FixedMapHigh byte = 0x8f
|
||||
FixedMapMask byte = 0xf
|
||||
Map16 byte = 0xde
|
||||
Map32 byte = 0xdf
|
||||
|
||||
FixExt1 byte = 0xd4
|
||||
FixExt2 byte = 0xd5
|
||||
FixExt4 byte = 0xd6
|
||||
FixExt8 byte = 0xd7
|
||||
FixExt16 byte = 0xd8
|
||||
Ext8 byte = 0xc7
|
||||
Ext16 byte = 0xc8
|
||||
Ext32 byte = 0xc9
|
||||
)
|
||||
|
||||
func IsFixedNum(c byte) bool {
|
||||
return c <= PosFixedNumHigh || c >= NegFixedNumLow
|
||||
}
|
||||
|
||||
func IsFixedMap(c byte) bool {
|
||||
return c >= FixedMapLow && c <= FixedMapHigh
|
||||
}
|
||||
|
||||
func IsFixedArray(c byte) bool {
|
||||
return c >= FixedArrayLow && c <= FixedArrayHigh
|
||||
}
|
||||
|
||||
func IsFixedString(c byte) bool {
|
||||
return c >= FixedStrLow && c <= FixedStrHigh
|
||||
}
|
||||
|
||||
func IsString(c byte) bool {
|
||||
return IsFixedString(c) || c == Str8 || c == Str16 || c == Str32
|
||||
}
|
||||
|
||||
func IsBin(c byte) bool {
|
||||
return c == Bin8 || c == Bin16 || c == Bin32
|
||||
}
|
||||
|
||||
func IsFixedExt(c byte) bool {
|
||||
return c >= FixExt1 && c <= FixExt16
|
||||
}
|
||||
|
||||
func IsExt(c byte) bool {
|
||||
return IsFixedExt(c) || c == Ext8 || c == Ext16 || c == Ext32
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// +build appengine
|
||||
|
||||
package msgpack
|
||||
|
||||
// bytesToString converts byte slice to string.
|
||||
func bytesToString(b []byte) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// stringToBytes converts string to byte slice.
|
||||
func stringToBytes(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5/msgpcode"
|
||||
)
|
||||
|
||||
var timeExtID int8 = -1
|
||||
|
||||
func init() {
|
||||
RegisterExtEncoder(timeExtID, time.Time{}, timeEncoder)
|
||||
RegisterExtDecoder(timeExtID, time.Time{}, timeDecoder)
|
||||
}
|
||||
|
||||
func timeEncoder(e *Encoder, v reflect.Value) ([]byte, error) {
|
||||
return e.encodeTime(v.Interface().(time.Time)), nil
|
||||
}
|
||||
|
||||
func timeDecoder(d *Decoder, v reflect.Value, extLen int) error {
|
||||
tm, err := d.decodeTime(extLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ptr := v.Addr().Interface().(*time.Time)
|
||||
*ptr = tm
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeTime(tm time.Time) error {
|
||||
b := e.encodeTime(tm)
|
||||
if err := e.encodeExtLen(len(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.w.WriteByte(byte(timeExtID)); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.write(b)
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeTime(tm time.Time) []byte {
|
||||
if e.timeBuf == nil {
|
||||
e.timeBuf = make([]byte, 12)
|
||||
}
|
||||
|
||||
secs := uint64(tm.Unix())
|
||||
if secs>>34 == 0 {
|
||||
data := uint64(tm.Nanosecond())<<34 | secs
|
||||
|
||||
if data&0xffffffff00000000 == 0 {
|
||||
b := e.timeBuf[:4]
|
||||
binary.BigEndian.PutUint32(b, uint32(data))
|
||||
return b
|
||||
}
|
||||
|
||||
b := e.timeBuf[:8]
|
||||
binary.BigEndian.PutUint64(b, data)
|
||||
return b
|
||||
}
|
||||
|
||||
b := e.timeBuf[:12]
|
||||
binary.BigEndian.PutUint32(b, uint32(tm.Nanosecond()))
|
||||
binary.BigEndian.PutUint64(b[4:], secs)
|
||||
return b
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeTime() (time.Time, error) {
|
||||
c, err := d.readCode()
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
// Legacy format.
|
||||
if c == msgpcode.FixedArrayLow|2 {
|
||||
sec, err := d.DecodeInt64()
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
nsec, err := d.DecodeInt64()
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return time.Unix(sec, nsec), nil
|
||||
}
|
||||
|
||||
if msgpcode.IsString(c) {
|
||||
s, err := d.string(c)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Parse(time.RFC3339Nano, s)
|
||||
}
|
||||
|
||||
extID, extLen, err := d.extHeader(c)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
if extID != timeExtID {
|
||||
return time.Time{}, fmt.Errorf("msgpack: invalid time ext id=%d", extID)
|
||||
}
|
||||
|
||||
tm, err := d.decodeTime(extLen)
|
||||
if err != nil {
|
||||
return tm, err
|
||||
}
|
||||
|
||||
if tm.IsZero() {
|
||||
// Zero time does not have timezone information.
|
||||
return tm.UTC(), nil
|
||||
}
|
||||
return tm, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeTime(extLen int) (time.Time, error) {
|
||||
b, err := d.readN(extLen)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
switch len(b) {
|
||||
case 4:
|
||||
sec := binary.BigEndian.Uint32(b)
|
||||
return time.Unix(int64(sec), 0), nil
|
||||
case 8:
|
||||
sec := binary.BigEndian.Uint64(b)
|
||||
nsec := int64(sec >> 34)
|
||||
sec &= 0x00000003ffffffff
|
||||
return time.Unix(int64(sec), nsec), nil
|
||||
case 12:
|
||||
nsec := binary.BigEndian.Uint32(b)
|
||||
sec := binary.BigEndian.Uint64(b[4:])
|
||||
return time.Unix(int64(sec), int64(nsec)), nil
|
||||
default:
|
||||
err = fmt.Errorf("msgpack: invalid ext len=%d decoding time", extLen)
|
||||
return time.Time{}, err
|
||||
}
|
||||
}
|
|
@ -0,0 +1,388 @@
|
|||
package msgpack
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/vmihailenco/tagparser"
|
||||
)
|
||||
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
var (
|
||||
customEncoderType = reflect.TypeOf((*CustomEncoder)(nil)).Elem()
|
||||
customDecoderType = reflect.TypeOf((*CustomDecoder)(nil)).Elem()
|
||||
)
|
||||
|
||||
var (
|
||||
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
var (
|
||||
binaryMarshalerType = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem()
|
||||
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
var (
|
||||
textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
type (
|
||||
encoderFunc func(*Encoder, reflect.Value) error
|
||||
decoderFunc func(*Decoder, reflect.Value) error
|
||||
)
|
||||
|
||||
var (
|
||||
typeEncMap sync.Map
|
||||
typeDecMap sync.Map
|
||||
)
|
||||
|
||||
// Register registers encoder and decoder functions for a value.
|
||||
// This is low level API and in most cases you should prefer implementing
|
||||
// CustomEncoder/CustomDecoder or Marshaler/Unmarshaler interfaces.
|
||||
func Register(value interface{}, enc encoderFunc, dec decoderFunc) {
|
||||
typ := reflect.TypeOf(value)
|
||||
if enc != nil {
|
||||
typeEncMap.Store(typ, enc)
|
||||
}
|
||||
if dec != nil {
|
||||
typeDecMap.Store(typ, dec)
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const defaultStructTag = "msgpack"
|
||||
|
||||
var structs = newStructCache()
|
||||
|
||||
type structCache struct {
|
||||
m sync.Map
|
||||
}
|
||||
|
||||
type structCacheKey struct {
|
||||
tag string
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
func newStructCache() *structCache {
|
||||
return new(structCache)
|
||||
}
|
||||
|
||||
func (m *structCache) Fields(typ reflect.Type, tag string) *fields {
|
||||
key := structCacheKey{tag: tag, typ: typ}
|
||||
|
||||
if v, ok := m.m.Load(key); ok {
|
||||
return v.(*fields)
|
||||
}
|
||||
|
||||
fs := getFields(typ, tag)
|
||||
m.m.Store(key, fs)
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type field struct {
|
||||
name string
|
||||
index []int
|
||||
omitEmpty bool
|
||||
encoder encoderFunc
|
||||
decoder decoderFunc
|
||||
}
|
||||
|
||||
func (f *field) Omit(strct reflect.Value) bool {
|
||||
v, ok := fieldByIndex(strct, f.index)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
return f.omitEmpty && isEmptyValue(v)
|
||||
}
|
||||
|
||||
func (f *field) EncodeValue(e *Encoder, strct reflect.Value) error {
|
||||
v, ok := fieldByIndex(strct, f.index)
|
||||
if !ok {
|
||||
return e.EncodeNil()
|
||||
}
|
||||
return f.encoder(e, v)
|
||||
}
|
||||
|
||||
func (f *field) DecodeValue(d *Decoder, strct reflect.Value) error {
|
||||
v := fieldByIndexAlloc(strct, f.index)
|
||||
return f.decoder(d, v)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type fields struct {
|
||||
Type reflect.Type
|
||||
Map map[string]*field
|
||||
List []*field
|
||||
AsArray bool
|
||||
|
||||
hasOmitEmpty bool
|
||||
}
|
||||
|
||||
func newFields(typ reflect.Type) *fields {
|
||||
return &fields{
|
||||
Type: typ,
|
||||
Map: make(map[string]*field, typ.NumField()),
|
||||
List: make([]*field, 0, typ.NumField()),
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *fields) Add(field *field) {
|
||||
fs.warnIfFieldExists(field.name)
|
||||
fs.Map[field.name] = field
|
||||
fs.List = append(fs.List, field)
|
||||
if field.omitEmpty {
|
||||
fs.hasOmitEmpty = true
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *fields) warnIfFieldExists(name string) {
|
||||
if _, ok := fs.Map[name]; ok {
|
||||
log.Printf("msgpack: %s already has field=%s", fs.Type, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *fields) OmitEmpty(strct reflect.Value) []*field {
|
||||
if !fs.hasOmitEmpty {
|
||||
return fs.List
|
||||
}
|
||||
|
||||
fields := make([]*field, 0, len(fs.List))
|
||||
|
||||
for _, f := range fs.List {
|
||||
if !f.Omit(strct) {
|
||||
fields = append(fields, f)
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func getFields(typ reflect.Type, fallbackTag string) *fields {
|
||||
fs := newFields(typ)
|
||||
|
||||
var omitEmpty bool
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
f := typ.Field(i)
|
||||
|
||||
tagStr := f.Tag.Get(defaultStructTag)
|
||||
if tagStr == "" && fallbackTag != "" {
|
||||
tagStr = f.Tag.Get(fallbackTag)
|
||||
}
|
||||
|
||||
tag := tagparser.Parse(tagStr)
|
||||
if tag.Name == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if f.Name == "_msgpack" {
|
||||
fs.AsArray = tag.HasOption("as_array") || tag.HasOption("asArray")
|
||||
if tag.HasOption("omitempty") {
|
||||
omitEmpty = true
|
||||
}
|
||||
}
|
||||
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
field := &field{
|
||||
name: tag.Name,
|
||||
index: f.Index,
|
||||
omitEmpty: omitEmpty || tag.HasOption("omitempty"),
|
||||
}
|
||||
|
||||
if tag.HasOption("intern") {
|
||||
switch f.Type.Kind() {
|
||||
case reflect.Interface:
|
||||
field.encoder = encodeInternedInterfaceValue
|
||||
field.decoder = decodeInternedInterfaceValue
|
||||
case reflect.String:
|
||||
field.encoder = encodeInternedStringValue
|
||||
field.decoder = decodeInternedStringValue
|
||||
default:
|
||||
err := fmt.Errorf("msgpack: intern strings are not supported on %s", f.Type)
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
field.encoder = getEncoder(f.Type)
|
||||
field.decoder = getDecoder(f.Type)
|
||||
}
|
||||
|
||||
if field.name == "" {
|
||||
field.name = f.Name
|
||||
}
|
||||
|
||||
if f.Anonymous && !tag.HasOption("noinline") {
|
||||
inline := tag.HasOption("inline")
|
||||
if inline {
|
||||
inlineFields(fs, f.Type, field, fallbackTag)
|
||||
} else {
|
||||
inline = shouldInline(fs, f.Type, field, fallbackTag)
|
||||
}
|
||||
|
||||
if inline {
|
||||
if _, ok := fs.Map[field.name]; ok {
|
||||
log.Printf("msgpack: %s already has field=%s", fs.Type, field.name)
|
||||
}
|
||||
fs.Map[field.name] = field
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fs.Add(field)
|
||||
|
||||
if alias, ok := tag.Options["alias"]; ok {
|
||||
fs.warnIfFieldExists(alias)
|
||||
fs.Map[alias] = field
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
var (
|
||||
encodeStructValuePtr uintptr
|
||||
decodeStructValuePtr uintptr
|
||||
)
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
encodeStructValuePtr = reflect.ValueOf(encodeStructValue).Pointer()
|
||||
decodeStructValuePtr = reflect.ValueOf(decodeStructValue).Pointer()
|
||||
}
|
||||
|
||||
func inlineFields(fs *fields, typ reflect.Type, f *field, tag string) {
|
||||
inlinedFields := getFields(typ, tag).List
|
||||
for _, field := range inlinedFields {
|
||||
if _, ok := fs.Map[field.name]; ok {
|
||||
// Don't inline shadowed fields.
|
||||
continue
|
||||
}
|
||||
field.index = append(f.index, field.index...)
|
||||
fs.Add(field)
|
||||
}
|
||||
}
|
||||
|
||||
func shouldInline(fs *fields, typ reflect.Type, f *field, tag string) bool {
|
||||
var encoder encoderFunc
|
||||
var decoder decoderFunc
|
||||
|
||||
if typ.Kind() == reflect.Struct {
|
||||
encoder = f.encoder
|
||||
decoder = f.decoder
|
||||
} else {
|
||||
for typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
encoder = getEncoder(typ)
|
||||
decoder = getDecoder(typ)
|
||||
}
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.ValueOf(encoder).Pointer() != encodeStructValuePtr {
|
||||
return false
|
||||
}
|
||||
if reflect.ValueOf(decoder).Pointer() != decodeStructValuePtr {
|
||||
return false
|
||||
}
|
||||
|
||||
inlinedFields := getFields(typ, tag).List
|
||||
for _, field := range inlinedFields {
|
||||
if _, ok := fs.Map[field.name]; ok {
|
||||
// Don't auto inline if there are shadowed fields.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, field := range inlinedFields {
|
||||
field.index = append(f.index, field.index...)
|
||||
fs.Add(field)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fieldByIndex(v reflect.Value, index []int) (_ reflect.Value, ok bool) {
|
||||
if len(index) == 1 {
|
||||
return v.Field(index[0]), true
|
||||
}
|
||||
|
||||
for i, idx := range index {
|
||||
if i > 0 {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return v, false
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
v = v.Field(idx)
|
||||
}
|
||||
|
||||
return v, true
|
||||
}
|
||||
|
||||
func fieldByIndexAlloc(v reflect.Value, index []int) reflect.Value {
|
||||
if len(index) == 1 {
|
||||
return v.Field(index[0])
|
||||
}
|
||||
|
||||
for i, idx := range index {
|
||||
if i > 0 {
|
||||
var ok bool
|
||||
v, ok = indirectNil(v)
|
||||
if !ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
v = v.Field(idx)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func indirectNil(v reflect.Value) (reflect.Value, bool) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
if !v.CanSet() {
|
||||
return v, false
|
||||
}
|
||||
elemType := v.Type().Elem()
|
||||
if elemType.Kind() != reflect.Struct {
|
||||
return v, false
|
||||
}
|
||||
v.Set(reflect.New(elemType))
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
return v, true
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// +build !appengine
|
||||
|
||||
package msgpack
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// bytesToString converts byte slice to string.
|
||||
func bytesToString(b []byte) string { //nolint:deadcode,unused
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// stringToBytes converts string to byte slice.
|
||||
func stringToBytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(
|
||||
&struct {
|
||||
string
|
||||
Cap int
|
||||
}{s, len(s)},
|
||||
))
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
dist: xenial
|
||||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
go_import_path: github.com/vmihailenco/tagparser
|
||||
|
||||
before_install:
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1
|
||||
|
||||
script:
|
||||
- make
|
||||
- golangci-lint run
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2019 The github.com/vmihailenco/tagparser Authors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,8 @@
|
|||
all:
|
||||
go test ./...
|
||||
go test ./... -short -race
|
||||
go test ./... -run=NONE -bench=. -benchmem
|
||||
env GOOS=linux GOARCH=386 go test ./...
|
||||
go vet ./...
|
||||
go get github.com/gordonklaus/ineffassign
|
||||
ineffassign .
|
|
@ -0,0 +1,24 @@
|
|||
# Opinionated Golang tag parser
|
||||
|
||||
[![Build Status](https://travis-ci.org/vmihailenco/tagparser.png?branch=master)](https://travis-ci.org/vmihailenco/tagparser)
|
||||
[![GoDoc](https://godoc.org/github.com/vmihailenco/tagparser?status.svg)](https://godoc.org/github.com/vmihailenco/tagparser)
|
||||
|
||||
## Installation
|
||||
|
||||
Install:
|
||||
|
||||
```shell
|
||||
go get -u github.com/vmihailenco/tagparser
|
||||
```
|
||||
|
||||
## Quickstart
|
||||
|
||||
```go
|
||||
func ExampleParse() {
|
||||
tag := tagparser.Parse("some_name,key:value,key2:'complex value'")
|
||||
fmt.Println(tag.Name)
|
||||
fmt.Println(tag.Options)
|
||||
// Output: some_name
|
||||
// map[key:value key2:'complex value']
|
||||
}
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/vmihailenco/tagparser
|
||||
|
||||
go 1.13
|
82
src/vendor/github.com/vmihailenco/tagparser/internal/parser/parser.go
generated
vendored
Normal file
82
src/vendor/github.com/vmihailenco/tagparser/internal/parser/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/vmihailenco/tagparser/internal"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
b []byte
|
||||
i int
|
||||
}
|
||||
|
||||
func New(b []byte) *Parser {
|
||||
return &Parser{
|
||||
b: b,
|
||||
}
|
||||
}
|
||||
|
||||
func NewString(s string) *Parser {
|
||||
return New(internal.StringToBytes(s))
|
||||
}
|
||||
|
||||
func (p *Parser) Bytes() []byte {
|
||||
return p.b[p.i:]
|
||||
}
|
||||
|
||||
func (p *Parser) Valid() bool {
|
||||
return p.i < len(p.b)
|
||||
}
|
||||
|
||||
func (p *Parser) Read() byte {
|
||||
if p.Valid() {
|
||||
c := p.b[p.i]
|
||||
p.Advance()
|
||||
return c
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *Parser) Peek() byte {
|
||||
if p.Valid() {
|
||||
return p.b[p.i]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *Parser) Advance() {
|
||||
p.i++
|
||||
}
|
||||
|
||||
func (p *Parser) Skip(skip byte) bool {
|
||||
if p.Peek() == skip {
|
||||
p.Advance()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Parser) SkipBytes(skip []byte) bool {
|
||||
if len(skip) > len(p.b[p.i:]) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(p.b[p.i:p.i+len(skip)], skip) {
|
||||
return false
|
||||
}
|
||||
p.i += len(skip)
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Parser) ReadSep(sep byte) ([]byte, bool) {
|
||||
ind := bytes.IndexByte(p.b[p.i:], sep)
|
||||
if ind == -1 {
|
||||
b := p.b[p.i:]
|
||||
p.i = len(p.b)
|
||||
return b, false
|
||||
}
|
||||
|
||||
b := p.b[p.i : p.i+ind]
|
||||
p.i += ind + 1
|
||||
return b, true
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// +build appengine
|
||||
|
||||
package internal
|
||||
|
||||
func BytesToString(b []byte) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func StringToBytes(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// +build !appengine
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// BytesToString converts byte slice to string.
|
||||
func BytesToString(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// StringToBytes converts string to byte slice.
|
||||
func StringToBytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(
|
||||
&struct {
|
||||
string
|
||||
Cap int
|
||||
}{s, len(s)},
|
||||
))
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
package tagparser
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/vmihailenco/tagparser/internal/parser"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Name string
|
||||
Options map[string]string
|
||||
}
|
||||
|
||||
func (t *Tag) HasOption(name string) bool {
|
||||
_, ok := t.Options[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func Parse(s string) *Tag {
|
||||
p := &tagParser{
|
||||
Parser: parser.NewString(s),
|
||||
}
|
||||
p.parseKey()
|
||||
return &p.Tag
|
||||
}
|
||||
|
||||
type tagParser struct {
|
||||
*parser.Parser
|
||||
|
||||
Tag Tag
|
||||
hasName bool
|
||||
key string
|
||||
}
|
||||
|
||||
func (p *tagParser) setTagOption(key, value string) {
|
||||
key = strings.TrimSpace(key)
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
if !p.hasName {
|
||||
p.hasName = true
|
||||
if key == "" {
|
||||
p.Tag.Name = value
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.Tag.Options == nil {
|
||||
p.Tag.Options = make(map[string]string)
|
||||
}
|
||||
if key == "" {
|
||||
p.Tag.Options[value] = ""
|
||||
} else {
|
||||
p.Tag.Options[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func (p *tagParser) parseKey() {
|
||||
p.key = ""
|
||||
|
||||
var b []byte
|
||||
for p.Valid() {
|
||||
c := p.Read()
|
||||
switch c {
|
||||
case ',':
|
||||
p.Skip(' ')
|
||||
p.setTagOption("", string(b))
|
||||
p.parseKey()
|
||||
return
|
||||
case ':':
|
||||
p.key = string(b)
|
||||
p.parseValue()
|
||||
return
|
||||
case '\'':
|
||||
p.parseQuotedValue()
|
||||
return
|
||||
default:
|
||||
b = append(b, c)
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) > 0 {
|
||||
p.setTagOption("", string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *tagParser) parseValue() {
|
||||
const quote = '\''
|
||||
|
||||
c := p.Peek()
|
||||
if c == quote {
|
||||
p.Skip(quote)
|
||||
p.parseQuotedValue()
|
||||
return
|
||||
}
|
||||
|
||||
var b []byte
|
||||
for p.Valid() {
|
||||
c = p.Read()
|
||||
switch c {
|
||||
case '\\':
|
||||
b = append(b, p.Read())
|
||||
case '(':
|
||||
b = append(b, c)
|
||||
b = p.readBrackets(b)
|
||||
case ',':
|
||||
p.Skip(' ')
|
||||
p.setTagOption(p.key, string(b))
|
||||
p.parseKey()
|
||||
return
|
||||
default:
|
||||
b = append(b, c)
|
||||
}
|
||||
}
|
||||
p.setTagOption(p.key, string(b))
|
||||
}
|
||||
|
||||
func (p *tagParser) readBrackets(b []byte) []byte {
|
||||
var lvl int
|
||||
loop:
|
||||
for p.Valid() {
|
||||
c := p.Read()
|
||||
switch c {
|
||||
case '\\':
|
||||
b = append(b, p.Read())
|
||||
case '(':
|
||||
b = append(b, c)
|
||||
lvl++
|
||||
case ')':
|
||||
b = append(b, c)
|
||||
lvl--
|
||||
if lvl < 0 {
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
b = append(b, c)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *tagParser) parseQuotedValue() {
|
||||
const quote = '\''
|
||||
|
||||
var b []byte
|
||||
b = append(b, quote)
|
||||
|
||||
for p.Valid() {
|
||||
bb, ok := p.ReadSep(quote)
|
||||
if !ok {
|
||||
b = append(b, bb...)
|
||||
break
|
||||
}
|
||||
|
||||
if len(bb) > 0 && bb[len(bb)-1] == '\\' {
|
||||
b = append(b, bb[:len(bb)-1]...)
|
||||
b = append(b, quote)
|
||||
continue
|
||||
}
|
||||
|
||||
b = append(b, bb...)
|
||||
b = append(b, quote)
|
||||
break
|
||||
}
|
||||
|
||||
p.setTagOption(p.key, string(b))
|
||||
if p.Skip(',') {
|
||||
p.Skip(' ')
|
||||
}
|
||||
p.parseKey()
|
||||
}
|
||||
|
||||
func Unquote(s string) (string, bool) {
|
||||
const quote = '\''
|
||||
|
||||
if len(s) < 2 {
|
||||
return s, false
|
||||
}
|
||||
if s[0] == quote && s[len(s)-1] == quote {
|
||||
return s[1 : len(s)-1], true
|
||||
}
|
||||
return s, false
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- "1.4.x"
|
||||
- "1.5.x"
|
||||
- "1.6.x"
|
||||
- "1.7.x"
|
||||
- "1.8.x"
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- "tip"
|
||||
|
||||
go_import_path: gopkg.in/yaml.v3
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
This project is covered by two different licenses: MIT and Apache.
|
||||
|
||||
#### MIT License ####
|
||||
|
||||
The following files were ported to Go from C files of libyaml, and thus
|
||||
are still covered by their original MIT license, with the additional
|
||||
copyright staring in 2011 when the project was ported over:
|
||||
|
||||
apic.go emitterc.go parserc.go readerc.go scannerc.go
|
||||
writerc.go yamlh.go yamlprivateh.go
|
||||
|
||||
Copyright (c) 2006-2010 Kirill Simonov
|
||||
Copyright (c) 2006-2011 Kirill Simonov
|
||||
|
||||
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.
|
||||
|
||||
### Apache License ###
|
||||
|
||||
All the remaining project files are covered by the Apache license:
|
||||
|
||||
Copyright (c) 2011-2019 Canonical Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,13 @@
|
|||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,150 @@
|
|||
# YAML support for the Go language
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The yaml package enables Go programs to comfortably encode and decode YAML
|
||||
values. It was developed within [Canonical](https://www.canonical.com) as
|
||||
part of the [juju](https://juju.ubuntu.com) project, and is based on a
|
||||
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
|
||||
C library to parse and generate YAML data quickly and reliably.
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
The yaml package supports most of YAML 1.2, but preserves some behavior
|
||||
from 1.1 for backwards compatibility.
|
||||
|
||||
Specifically, as of v3 of the yaml package:
|
||||
|
||||
- YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being
|
||||
decoded into a typed bool value. Otherwise they behave as a string. Booleans
|
||||
in YAML 1.2 are _true/false_ only.
|
||||
- Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_
|
||||
as specified in YAML 1.2, because most parsers still use the old format.
|
||||
Octals in the _0o777_ format are supported though, so new files work.
|
||||
- Does not support base-60 floats. These are gone from YAML 1.2, and were
|
||||
actually never supported by this package as it's clearly a poor choice.
|
||||
|
||||
and offers backwards
|
||||
compatibility with YAML 1.1 in some cases.
|
||||
1.2, including support for
|
||||
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
|
||||
implemented, and base-60 floats from YAML 1.1 are purposefully not
|
||||
supported since they're a poor design and are gone in YAML 1.2.
|
||||
|
||||
Installation and usage
|
||||
----------------------
|
||||
|
||||
The import path for the package is *gopkg.in/yaml.v3*.
|
||||
|
||||
To install it, run:
|
||||
|
||||
go get gopkg.in/yaml.v3
|
||||
|
||||
API documentation
|
||||
-----------------
|
||||
|
||||
If opened in a browser, the import path itself leads to the API documentation:
|
||||
|
||||
- [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3)
|
||||
|
||||
API stability
|
||||
-------------
|
||||
|
||||
The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in).
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The yaml package is licensed under the MIT and Apache License 2.0 licenses.
|
||||
Please see the LICENSE file for details.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var data = `
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
`
|
||||
|
||||
// Note: struct fields must be public in order for unmarshal to
|
||||
// correctly populate the data.
|
||||
type T struct {
|
||||
A string
|
||||
B struct {
|
||||
RenamedC int `yaml:"c"`
|
||||
D []int `yaml:",flow"`
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := T{}
|
||||
|
||||
err := yaml.Unmarshal([]byte(data), &t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t:\n%v\n\n", t)
|
||||
|
||||
d, err := yaml.Marshal(&t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t dump:\n%s\n\n", string(d))
|
||||
|
||||
m := make(map[interface{}]interface{})
|
||||
|
||||
err = yaml.Unmarshal([]byte(data), &m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m:\n%v\n\n", m)
|
||||
|
||||
d, err = yaml.Marshal(&m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m dump:\n%s\n\n", string(d))
|
||||
}
|
||||
```
|
||||
|
||||
This example will generate the following output:
|
||||
|
||||
```
|
||||
--- t:
|
||||
{Easy! {2 [3 4]}}
|
||||
|
||||
--- t dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
|
||||
|
||||
--- m:
|
||||
map[a:Easy! b:map[c:2 d:[3 4]]]
|
||||
|
||||
--- m dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
|
@ -0,0 +1,746 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// 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.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
|
||||
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
|
||||
|
||||
// Check if we can move the queue at the beginning of the buffer.
|
||||
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
|
||||
if parser.tokens_head != len(parser.tokens) {
|
||||
copy(parser.tokens, parser.tokens[parser.tokens_head:])
|
||||
}
|
||||
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
|
||||
parser.tokens_head = 0
|
||||
}
|
||||
parser.tokens = append(parser.tokens, *token)
|
||||
if pos < 0 {
|
||||
return
|
||||
}
|
||||
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
|
||||
parser.tokens[parser.tokens_head+pos] = *token
|
||||
}
|
||||
|
||||
// Create a new parser object.
|
||||
func yaml_parser_initialize(parser *yaml_parser_t) bool {
|
||||
*parser = yaml_parser_t{
|
||||
raw_buffer: make([]byte, 0, input_raw_buffer_size),
|
||||
buffer: make([]byte, 0, input_buffer_size),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Destroy a parser object.
|
||||
func yaml_parser_delete(parser *yaml_parser_t) {
|
||||
*parser = yaml_parser_t{}
|
||||
}
|
||||
|
||||
// String read handler.
|
||||
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
if parser.input_pos == len(parser.input) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(buffer, parser.input[parser.input_pos:])
|
||||
parser.input_pos += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Reader read handler.
|
||||
func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
return parser.input_reader.Read(buffer)
|
||||
}
|
||||
|
||||
// Set a string input.
|
||||
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_string_read_handler
|
||||
parser.input = input
|
||||
parser.input_pos = 0
|
||||
}
|
||||
|
||||
// Set a file input.
|
||||
func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_reader_read_handler
|
||||
parser.input_reader = r
|
||||
}
|
||||
|
||||
// Set the source encoding.
|
||||
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
|
||||
if parser.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the encoding only once")
|
||||
}
|
||||
parser.encoding = encoding
|
||||
}
|
||||
|
||||
// Create a new emitter object.
|
||||
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{
|
||||
buffer: make([]byte, output_buffer_size),
|
||||
raw_buffer: make([]byte, 0, output_raw_buffer_size),
|
||||
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
|
||||
events: make([]yaml_event_t, 0, initial_queue_size),
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an emitter object.
|
||||
func yaml_emitter_delete(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{}
|
||||
}
|
||||
|
||||
// String write handler.
|
||||
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// yaml_writer_write_handler uses emitter.output_writer to write the
|
||||
// emitted text.
|
||||
func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
_, err := emitter.output_writer.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a string output.
|
||||
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_string_write_handler
|
||||
emitter.output_buffer = output_buffer
|
||||
}
|
||||
|
||||
// Set a file output.
|
||||
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_writer_write_handler
|
||||
emitter.output_writer = w
|
||||
}
|
||||
|
||||
// Set the output encoding.
|
||||
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
|
||||
if emitter.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the output encoding only once")
|
||||
}
|
||||
emitter.encoding = encoding
|
||||
}
|
||||
|
||||
// Set the canonical output style.
|
||||
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
|
||||
emitter.canonical = canonical
|
||||
}
|
||||
|
||||
// Set the indentation increment.
|
||||
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
|
||||
if indent < 2 || indent > 9 {
|
||||
indent = 2
|
||||
}
|
||||
emitter.best_indent = indent
|
||||
}
|
||||
|
||||
// Set the preferred line width.
|
||||
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
|
||||
if width < 0 {
|
||||
width = -1
|
||||
}
|
||||
emitter.best_width = width
|
||||
}
|
||||
|
||||
// Set if unescaped non-ASCII characters are allowed.
|
||||
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
|
||||
emitter.unicode = unicode
|
||||
}
|
||||
|
||||
// Set the preferred line break character.
|
||||
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
|
||||
emitter.line_break = line_break
|
||||
}
|
||||
|
||||
///*
|
||||
// * Destroy a token object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_token_delete(yaml_token_t *token)
|
||||
//{
|
||||
// assert(token); // Non-NULL token object expected.
|
||||
//
|
||||
// switch (token.type)
|
||||
// {
|
||||
// case YAML_TAG_DIRECTIVE_TOKEN:
|
||||
// yaml_free(token.data.tag_directive.handle);
|
||||
// yaml_free(token.data.tag_directive.prefix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ALIAS_TOKEN:
|
||||
// yaml_free(token.data.alias.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ANCHOR_TOKEN:
|
||||
// yaml_free(token.data.anchor.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_TAG_TOKEN:
|
||||
// yaml_free(token.data.tag.handle);
|
||||
// yaml_free(token.data.tag.suffix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_SCALAR_TOKEN:
|
||||
// yaml_free(token.data.scalar.value);
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// memset(token, 0, sizeof(yaml_token_t));
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Check if a string is a valid UTF-8 sequence.
|
||||
// *
|
||||
// * Check 'reader.c' for more details on UTF-8 encoding.
|
||||
// */
|
||||
//
|
||||
//static int
|
||||
//yaml_check_utf8(yaml_char_t *start, size_t length)
|
||||
//{
|
||||
// yaml_char_t *end = start+length;
|
||||
// yaml_char_t *pointer = start;
|
||||
//
|
||||
// while (pointer < end) {
|
||||
// unsigned char octet;
|
||||
// unsigned int width;
|
||||
// unsigned int value;
|
||||
// size_t k;
|
||||
//
|
||||
// octet = pointer[0];
|
||||
// width = (octet & 0x80) == 0x00 ? 1 :
|
||||
// (octet & 0xE0) == 0xC0 ? 2 :
|
||||
// (octet & 0xF0) == 0xE0 ? 3 :
|
||||
// (octet & 0xF8) == 0xF0 ? 4 : 0;
|
||||
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
|
||||
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
|
||||
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
|
||||
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
|
||||
// if (!width) return 0;
|
||||
// if (pointer+width > end) return 0;
|
||||
// for (k = 1; k < width; k ++) {
|
||||
// octet = pointer[k];
|
||||
// if ((octet & 0xC0) != 0x80) return 0;
|
||||
// value = (value << 6) + (octet & 0x3F);
|
||||
// }
|
||||
// if (!((width == 1) ||
|
||||
// (width == 2 && value >= 0x80) ||
|
||||
// (width == 3 && value >= 0x800) ||
|
||||
// (width == 4 && value >= 0x10000))) return 0;
|
||||
//
|
||||
// pointer += width;
|
||||
// }
|
||||
//
|
||||
// return 1;
|
||||
//}
|
||||
//
|
||||
|
||||
// Create STREAM-START.
|
||||
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_START_EVENT,
|
||||
encoding: encoding,
|
||||
}
|
||||
}
|
||||
|
||||
// Create STREAM-END.
|
||||
func yaml_stream_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-START.
|
||||
func yaml_document_start_event_initialize(
|
||||
event *yaml_event_t,
|
||||
version_directive *yaml_version_directive_t,
|
||||
tag_directives []yaml_tag_directive_t,
|
||||
implicit bool,
|
||||
) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_START_EVENT,
|
||||
version_directive: version_directive,
|
||||
tag_directives: tag_directives,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-END.
|
||||
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_END_EVENT,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
// Create ALIAS.
|
||||
func yaml_alias_event_initialize(event *yaml_event_t, anchor []byte) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_ALIAS_EVENT,
|
||||
anchor: anchor,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SCALAR.
|
||||
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SCALAR_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
value: value,
|
||||
implicit: plain_implicit,
|
||||
quoted_implicit: quoted_implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-START.
|
||||
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-END.
|
||||
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_END_EVENT,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create MAPPING-START.
|
||||
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
}
|
||||
|
||||
// Create MAPPING-END.
|
||||
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an event object.
|
||||
func yaml_event_delete(event *yaml_event_t) {
|
||||
*event = yaml_event_t{}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_initialize(document *yaml_document_t,
|
||||
// version_directive *yaml_version_directive_t,
|
||||
// tag_directives_start *yaml_tag_directive_t,
|
||||
// tag_directives_end *yaml_tag_directive_t,
|
||||
// start_implicit int, end_implicit int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// struct {
|
||||
// start *yaml_node_t
|
||||
// end *yaml_node_t
|
||||
// top *yaml_node_t
|
||||
// } nodes = { NULL, NULL, NULL }
|
||||
// version_directive_copy *yaml_version_directive_t = NULL
|
||||
// struct {
|
||||
// start *yaml_tag_directive_t
|
||||
// end *yaml_tag_directive_t
|
||||
// top *yaml_tag_directive_t
|
||||
// } tag_directives_copy = { NULL, NULL, NULL }
|
||||
// value yaml_tag_directive_t = { NULL, NULL }
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert((tag_directives_start && tag_directives_end) ||
|
||||
// (tag_directives_start == tag_directives_end))
|
||||
// // Valid tag directives are expected.
|
||||
//
|
||||
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// if (version_directive) {
|
||||
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
|
||||
// if (!version_directive_copy) goto error
|
||||
// version_directive_copy.major = version_directive.major
|
||||
// version_directive_copy.minor = version_directive.minor
|
||||
// }
|
||||
//
|
||||
// if (tag_directives_start != tag_directives_end) {
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
|
||||
// goto error
|
||||
// for (tag_directive = tag_directives_start
|
||||
// tag_directive != tag_directives_end; tag_directive ++) {
|
||||
// assert(tag_directive.handle)
|
||||
// assert(tag_directive.prefix)
|
||||
// if (!yaml_check_utf8(tag_directive.handle,
|
||||
// strlen((char *)tag_directive.handle)))
|
||||
// goto error
|
||||
// if (!yaml_check_utf8(tag_directive.prefix,
|
||||
// strlen((char *)tag_directive.prefix)))
|
||||
// goto error
|
||||
// value.handle = yaml_strdup(tag_directive.handle)
|
||||
// value.prefix = yaml_strdup(tag_directive.prefix)
|
||||
// if (!value.handle || !value.prefix) goto error
|
||||
// if (!PUSH(&context, tag_directives_copy, value))
|
||||
// goto error
|
||||
// value.handle = NULL
|
||||
// value.prefix = NULL
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
|
||||
// tag_directives_copy.start, tag_directives_copy.top,
|
||||
// start_implicit, end_implicit, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, nodes)
|
||||
// yaml_free(version_directive_copy)
|
||||
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
|
||||
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
// }
|
||||
// STACK_DEL(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Destroy a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_document_delete(document *yaml_document_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
//
|
||||
// context.error = YAML_NO_ERROR // Eliminate a compiler warning.
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// while (!STACK_EMPTY(&context, document.nodes)) {
|
||||
// node yaml_node_t = POP(&context, document.nodes)
|
||||
// yaml_free(node.tag)
|
||||
// switch (node.type) {
|
||||
// case YAML_SCALAR_NODE:
|
||||
// yaml_free(node.data.scalar.value)
|
||||
// break
|
||||
// case YAML_SEQUENCE_NODE:
|
||||
// STACK_DEL(&context, node.data.sequence.items)
|
||||
// break
|
||||
// case YAML_MAPPING_NODE:
|
||||
// STACK_DEL(&context, node.data.mapping.pairs)
|
||||
// break
|
||||
// default:
|
||||
// assert(0) // Should not happen.
|
||||
// }
|
||||
// }
|
||||
// STACK_DEL(&context, document.nodes)
|
||||
//
|
||||
// yaml_free(document.version_directive)
|
||||
// for (tag_directive = document.tag_directives.start
|
||||
// tag_directive != document.tag_directives.end
|
||||
// tag_directive++) {
|
||||
// yaml_free(tag_directive.handle)
|
||||
// yaml_free(tag_directive.prefix)
|
||||
// }
|
||||
// yaml_free(document.tag_directives.start)
|
||||
//
|
||||
// memset(document, 0, sizeof(yaml_document_t))
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get a document node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_node(document *yaml_document_t, index int)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
|
||||
// return document.nodes.start + index - 1
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get the root object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_root_node(document *yaml_document_t)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (document.nodes.top != document.nodes.start) {
|
||||
// return document.nodes.start
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a scalar node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_scalar(document *yaml_document_t,
|
||||
// tag *yaml_char_t, value *yaml_char_t, length int,
|
||||
// style yaml_scalar_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// value_copy *yaml_char_t = NULL
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert(value) // Non-NULL value is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (length < 0) {
|
||||
// length = strlen((char *)value)
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(value, length)) goto error
|
||||
// value_copy = yaml_malloc(length+1)
|
||||
// if (!value_copy) goto error
|
||||
// memcpy(value_copy, value, length)
|
||||
// value_copy[length] = '\0'
|
||||
//
|
||||
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// yaml_free(tag_copy)
|
||||
// yaml_free(value_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a sequence node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_sequence(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_sequence_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_item_t
|
||||
// end *yaml_node_item_t
|
||||
// top *yaml_node_item_t
|
||||
// } items = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, items)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a mapping node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_mapping(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_mapping_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_pair_t
|
||||
// end *yaml_node_pair_t
|
||||
// top *yaml_node_pair_t
|
||||
// } pairs = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, pairs)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append an item to a sequence node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_sequence_item(document *yaml_document_t,
|
||||
// sequence int, item int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(sequence > 0
|
||||
// && document.nodes.start + sequence <= document.nodes.top)
|
||||
// // Valid sequence id is required.
|
||||
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
|
||||
// // A sequence node is required.
|
||||
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
|
||||
// // Valid item id is required.
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[sequence-1].data.sequence.items, item))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append a pair of a key and a value to a mapping node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_mapping_pair(document *yaml_document_t,
|
||||
// mapping int, key int, value int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// pair yaml_node_pair_t
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(mapping > 0
|
||||
// && document.nodes.start + mapping <= document.nodes.top)
|
||||
// // Valid mapping id is required.
|
||||
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
|
||||
// // A mapping node is required.
|
||||
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
|
||||
// // Valid key id is required.
|
||||
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
|
||||
// // Valid value id is required.
|
||||
//
|
||||
// pair.key = key
|
||||
// pair.value = value
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
//
|
|
@ -0,0 +1,931 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Parser, produces a node tree out of a libyaml event stream.
|
||||
|
||||
type parser struct {
|
||||
parser yaml_parser_t
|
||||
event yaml_event_t
|
||||
doc *Node
|
||||
anchors map[string]*Node
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newParser(b []byte) *parser {
|
||||
p := parser{}
|
||||
if !yaml_parser_initialize(&p.parser) {
|
||||
panic("failed to initialize YAML emitter")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
b = []byte{'\n'}
|
||||
}
|
||||
yaml_parser_set_input_string(&p.parser, b)
|
||||
return &p
|
||||
}
|
||||
|
||||
func newParserFromReader(r io.Reader) *parser {
|
||||
p := parser{}
|
||||
if !yaml_parser_initialize(&p.parser) {
|
||||
panic("failed to initialize YAML emitter")
|
||||
}
|
||||
yaml_parser_set_input_reader(&p.parser, r)
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *parser) init() {
|
||||
if p.doneInit {
|
||||
return
|
||||
}
|
||||
p.anchors = make(map[string]*Node)
|
||||
p.expect(yaml_STREAM_START_EVENT)
|
||||
p.doneInit = true
|
||||
}
|
||||
|
||||
func (p *parser) destroy() {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
yaml_event_delete(&p.event)
|
||||
}
|
||||
yaml_parser_delete(&p.parser)
|
||||
}
|
||||
|
||||
// expect consumes an event from the event stream and
|
||||
// checks that it's of the expected type.
|
||||
func (p *parser) expect(e yaml_event_type_t) {
|
||||
if p.event.typ == yaml_NO_EVENT {
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
p.fail()
|
||||
}
|
||||
}
|
||||
if p.event.typ == yaml_STREAM_END_EVENT {
|
||||
failf("attempted to go past the end of stream; corrupted value?")
|
||||
}
|
||||
if p.event.typ != e {
|
||||
p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ)
|
||||
p.fail()
|
||||
}
|
||||
yaml_event_delete(&p.event)
|
||||
p.event.typ = yaml_NO_EVENT
|
||||
}
|
||||
|
||||
// peek peeks at the next event in the event stream,
|
||||
// puts the results into p.event and returns the event type.
|
||||
func (p *parser) peek() yaml_event_type_t {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
return p.event.typ
|
||||
}
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
p.fail()
|
||||
}
|
||||
return p.event.typ
|
||||
}
|
||||
|
||||
func (p *parser) fail() {
|
||||
var where string
|
||||
var line int
|
||||
if p.parser.problem_mark.line != 0 {
|
||||
line = p.parser.problem_mark.line
|
||||
// Scanner errors don't iterate line before returning error
|
||||
if p.parser.error == yaml_SCANNER_ERROR {
|
||||
line++
|
||||
}
|
||||
} else if p.parser.context_mark.line != 0 {
|
||||
line = p.parser.context_mark.line
|
||||
}
|
||||
if line != 0 {
|
||||
where = "line " + strconv.Itoa(line) + ": "
|
||||
}
|
||||
var msg string
|
||||
if len(p.parser.problem) > 0 {
|
||||
msg = p.parser.problem
|
||||
} else {
|
||||
msg = "unknown problem parsing YAML content"
|
||||
}
|
||||
failf("%s%s", where, msg)
|
||||
}
|
||||
|
||||
func (p *parser) anchor(n *Node, anchor []byte) {
|
||||
if anchor != nil {
|
||||
n.Anchor = string(anchor)
|
||||
p.anchors[n.Anchor] = n
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parse() *Node {
|
||||
p.init()
|
||||
switch p.peek() {
|
||||
case yaml_SCALAR_EVENT:
|
||||
return p.scalar()
|
||||
case yaml_ALIAS_EVENT:
|
||||
return p.alias()
|
||||
case yaml_MAPPING_START_EVENT:
|
||||
return p.mapping()
|
||||
case yaml_SEQUENCE_START_EVENT:
|
||||
return p.sequence()
|
||||
case yaml_DOCUMENT_START_EVENT:
|
||||
return p.document()
|
||||
case yaml_STREAM_END_EVENT:
|
||||
// Happens when attempting to decode an empty buffer.
|
||||
return nil
|
||||
case yaml_TAIL_COMMENT_EVENT:
|
||||
panic("internal error: unexpected tail comment event (please report)")
|
||||
default:
|
||||
panic("internal error: attempted to parse unknown event (please report): " + p.event.typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node {
|
||||
var style Style
|
||||
if tag != "" && tag != "!" {
|
||||
tag = shortTag(tag)
|
||||
style = TaggedStyle
|
||||
} else if defaultTag != "" {
|
||||
tag = defaultTag
|
||||
} else if kind == ScalarNode {
|
||||
tag, _ = resolve("", value)
|
||||
}
|
||||
return &Node{
|
||||
Kind: kind,
|
||||
Tag: tag,
|
||||
Value: value,
|
||||
Style: style,
|
||||
Line: p.event.start_mark.line + 1,
|
||||
Column: p.event.start_mark.column + 1,
|
||||
HeadComment: string(p.event.head_comment),
|
||||
LineComment: string(p.event.line_comment),
|
||||
FootComment: string(p.event.foot_comment),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseChild(parent *Node) *Node {
|
||||
child := p.parse()
|
||||
parent.Content = append(parent.Content, child)
|
||||
return child
|
||||
}
|
||||
|
||||
func (p *parser) document() *Node {
|
||||
n := p.node(DocumentNode, "", "", "")
|
||||
p.doc = n
|
||||
p.expect(yaml_DOCUMENT_START_EVENT)
|
||||
p.parseChild(n)
|
||||
if p.peek() == yaml_DOCUMENT_END_EVENT {
|
||||
n.FootComment = string(p.event.foot_comment)
|
||||
}
|
||||
p.expect(yaml_DOCUMENT_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) alias() *Node {
|
||||
n := p.node(AliasNode, "", "", string(p.event.anchor))
|
||||
n.Alias = p.anchors[n.Value]
|
||||
if n.Alias == nil {
|
||||
failf("unknown anchor '%s' referenced", n.Value)
|
||||
}
|
||||
p.expect(yaml_ALIAS_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) scalar() *Node {
|
||||
var parsedStyle = p.event.scalar_style()
|
||||
var nodeStyle Style
|
||||
switch {
|
||||
case parsedStyle&yaml_DOUBLE_QUOTED_SCALAR_STYLE != 0:
|
||||
nodeStyle = DoubleQuotedStyle
|
||||
case parsedStyle&yaml_SINGLE_QUOTED_SCALAR_STYLE != 0:
|
||||
nodeStyle = SingleQuotedStyle
|
||||
case parsedStyle&yaml_LITERAL_SCALAR_STYLE != 0:
|
||||
nodeStyle = LiteralStyle
|
||||
case parsedStyle&yaml_FOLDED_SCALAR_STYLE != 0:
|
||||
nodeStyle = FoldedStyle
|
||||
}
|
||||
var nodeValue = string(p.event.value)
|
||||
var nodeTag = string(p.event.tag)
|
||||
var defaultTag string
|
||||
if nodeStyle == 0 {
|
||||
if nodeValue == "<<" {
|
||||
defaultTag = mergeTag
|
||||
}
|
||||
} else {
|
||||
defaultTag = strTag
|
||||
}
|
||||
n := p.node(ScalarNode, defaultTag, nodeTag, nodeValue)
|
||||
n.Style |= nodeStyle
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_SCALAR_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) sequence() *Node {
|
||||
n := p.node(SequenceNode, seqTag, string(p.event.tag), "")
|
||||
if p.event.sequence_style()&yaml_FLOW_SEQUENCE_STYLE != 0 {
|
||||
n.Style |= FlowStyle
|
||||
}
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_SEQUENCE_START_EVENT)
|
||||
for p.peek() != yaml_SEQUENCE_END_EVENT {
|
||||
p.parseChild(n)
|
||||
}
|
||||
n.LineComment = string(p.event.line_comment)
|
||||
n.FootComment = string(p.event.foot_comment)
|
||||
p.expect(yaml_SEQUENCE_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) mapping() *Node {
|
||||
n := p.node(MappingNode, mapTag, string(p.event.tag), "")
|
||||
block := true
|
||||
if p.event.mapping_style()&yaml_FLOW_MAPPING_STYLE != 0 {
|
||||
block = false
|
||||
n.Style |= FlowStyle
|
||||
}
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_MAPPING_START_EVENT)
|
||||
for p.peek() != yaml_MAPPING_END_EVENT {
|
||||
k := p.parseChild(n)
|
||||
if block && k.FootComment != "" {
|
||||
// Must be a foot comment for the prior value when being dedented.
|
||||
if len(n.Content) > 2 {
|
||||
n.Content[len(n.Content)-3].FootComment = k.FootComment
|
||||
k.FootComment = ""
|
||||
}
|
||||
}
|
||||
v := p.parseChild(n)
|
||||
if k.FootComment == "" && v.FootComment != "" {
|
||||
k.FootComment = v.FootComment
|
||||
v.FootComment = ""
|
||||
}
|
||||
if p.peek() == yaml_TAIL_COMMENT_EVENT {
|
||||
if k.FootComment == "" {
|
||||
k.FootComment = string(p.event.foot_comment)
|
||||
}
|
||||
p.expect(yaml_TAIL_COMMENT_EVENT)
|
||||
}
|
||||
}
|
||||
n.LineComment = string(p.event.line_comment)
|
||||
n.FootComment = string(p.event.foot_comment)
|
||||
if n.Style&FlowStyle == 0 && n.FootComment != "" && len(n.Content) > 1 {
|
||||
n.Content[len(n.Content)-2].FootComment = n.FootComment
|
||||
n.FootComment = ""
|
||||
}
|
||||
p.expect(yaml_MAPPING_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Decoder, unmarshals a node into a provided value.
|
||||
|
||||
type decoder struct {
|
||||
doc *Node
|
||||
aliases map[*Node]bool
|
||||
terrors []string
|
||||
|
||||
stringMapType reflect.Type
|
||||
generalMapType reflect.Type
|
||||
|
||||
knownFields bool
|
||||
uniqueKeys bool
|
||||
decodeCount int
|
||||
aliasCount int
|
||||
aliasDepth int
|
||||
}
|
||||
|
||||
var (
|
||||
nodeType = reflect.TypeOf(Node{})
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
stringMapType = reflect.TypeOf(map[string]interface{}{})
|
||||
generalMapType = reflect.TypeOf(map[interface{}]interface{}{})
|
||||
ifaceType = generalMapType.Elem()
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
ptrTimeType = reflect.TypeOf(&time.Time{})
|
||||
)
|
||||
|
||||
func newDecoder() *decoder {
|
||||
d := &decoder{
|
||||
stringMapType: stringMapType,
|
||||
generalMapType: generalMapType,
|
||||
uniqueKeys: true,
|
||||
}
|
||||
d.aliases = make(map[*Node]bool)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decoder) terror(n *Node, tag string, out reflect.Value) {
|
||||
if n.Tag != "" {
|
||||
tag = n.Tag
|
||||
}
|
||||
value := n.Value
|
||||
if tag != seqTag && tag != mapTag {
|
||||
if len(value) > 10 {
|
||||
value = " `" + value[:7] + "...`"
|
||||
} else {
|
||||
value = " `" + value + "`"
|
||||
}
|
||||
}
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.Line, shortTag(tag), value, out.Type()))
|
||||
}
|
||||
|
||||
func (d *decoder) callUnmarshaler(n *Node, u Unmarshaler) (good bool) {
|
||||
err := u.UnmarshalYAML(n)
|
||||
if e, ok := err.(*TypeError); ok {
|
||||
d.terrors = append(d.terrors, e.Errors...)
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) callObsoleteUnmarshaler(n *Node, u obsoleteUnmarshaler) (good bool) {
|
||||
terrlen := len(d.terrors)
|
||||
err := u.UnmarshalYAML(func(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
d.unmarshal(n, reflect.ValueOf(v))
|
||||
if len(d.terrors) > terrlen {
|
||||
issues := d.terrors[terrlen:]
|
||||
d.terrors = d.terrors[:terrlen]
|
||||
return &TypeError{issues}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if e, ok := err.(*TypeError); ok {
|
||||
d.terrors = append(d.terrors, e.Errors...)
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// d.prepare initializes and dereferences pointers and calls UnmarshalYAML
|
||||
// if a value is found to implement it.
|
||||
// It returns the initialized and dereferenced out value, whether
|
||||
// unmarshalling was already done by UnmarshalYAML, and if so whether
|
||||
// its types unmarshalled appropriately.
|
||||
//
|
||||
// If n holds a null value, prepare returns before doing anything.
|
||||
func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
|
||||
if n.ShortTag() == nullTag {
|
||||
return out, false, false
|
||||
}
|
||||
again := true
|
||||
for again {
|
||||
again = false
|
||||
if out.Kind() == reflect.Ptr {
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.New(out.Type().Elem()))
|
||||
}
|
||||
out = out.Elem()
|
||||
again = true
|
||||
}
|
||||
if out.CanAddr() {
|
||||
outi := out.Addr().Interface()
|
||||
if u, ok := outi.(Unmarshaler); ok {
|
||||
good = d.callUnmarshaler(n, u)
|
||||
return out, true, good
|
||||
}
|
||||
if u, ok := outi.(obsoleteUnmarshaler); ok {
|
||||
good = d.callObsoleteUnmarshaler(n, u)
|
||||
return out, true, good
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, false, false
|
||||
}
|
||||
|
||||
func (d *decoder) fieldByIndex(n *Node, v reflect.Value, index []int) (field reflect.Value) {
|
||||
if n.ShortTag() == nullTag {
|
||||
return reflect.Value{}
|
||||
}
|
||||
for _, num := range index {
|
||||
for {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
v = v.Elem()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
v = v.Field(num)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
const (
|
||||
// 400,000 decode operations is ~500kb of dense object declarations, or
|
||||
// ~5kb of dense object declarations with 10000% alias expansion
|
||||
alias_ratio_range_low = 400000
|
||||
|
||||
// 4,000,000 decode operations is ~5MB of dense object declarations, or
|
||||
// ~4.5MB of dense object declarations with 10% alias expansion
|
||||
alias_ratio_range_high = 4000000
|
||||
|
||||
// alias_ratio_range is the range over which we scale allowed alias ratios
|
||||
alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low)
|
||||
)
|
||||
|
||||
func allowedAliasRatio(decodeCount int) float64 {
|
||||
switch {
|
||||
case decodeCount <= alias_ratio_range_low:
|
||||
// allow 99% to come from alias expansion for small-to-medium documents
|
||||
return 0.99
|
||||
case decodeCount >= alias_ratio_range_high:
|
||||
// allow 10% to come from alias expansion for very large documents
|
||||
return 0.10
|
||||
default:
|
||||
// scale smoothly from 99% down to 10% over the range.
|
||||
// this maps to 396,000 - 400,000 allowed alias-driven decodes over the range.
|
||||
// 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps).
|
||||
return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) {
|
||||
d.decodeCount++
|
||||
if d.aliasDepth > 0 {
|
||||
d.aliasCount++
|
||||
}
|
||||
if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) {
|
||||
failf("document contains excessive aliasing")
|
||||
}
|
||||
if out.Type() == nodeType {
|
||||
out.Set(reflect.ValueOf(n).Elem())
|
||||
return true
|
||||
}
|
||||
switch n.Kind {
|
||||
case DocumentNode:
|
||||
return d.document(n, out)
|
||||
case AliasNode:
|
||||
return d.alias(n, out)
|
||||
}
|
||||
out, unmarshaled, good := d.prepare(n, out)
|
||||
if unmarshaled {
|
||||
return good
|
||||
}
|
||||
switch n.Kind {
|
||||
case ScalarNode:
|
||||
good = d.scalar(n, out)
|
||||
case MappingNode:
|
||||
good = d.mapping(n, out)
|
||||
case SequenceNode:
|
||||
good = d.sequence(n, out)
|
||||
default:
|
||||
panic("internal error: unknown node kind: " + strconv.Itoa(int(n.Kind)))
|
||||
}
|
||||
return good
|
||||
}
|
||||
|
||||
func (d *decoder) document(n *Node, out reflect.Value) (good bool) {
|
||||
if len(n.Content) == 1 {
|
||||
d.doc = n
|
||||
d.unmarshal(n.Content[0], out)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *decoder) alias(n *Node, out reflect.Value) (good bool) {
|
||||
if d.aliases[n] {
|
||||
// TODO this could actually be allowed in some circumstances.
|
||||
failf("anchor '%s' value contains itself", n.Value)
|
||||
}
|
||||
d.aliases[n] = true
|
||||
d.aliasDepth++
|
||||
good = d.unmarshal(n.Alias, out)
|
||||
d.aliasDepth--
|
||||
delete(d.aliases, n)
|
||||
return good
|
||||
}
|
||||
|
||||
var zeroValue reflect.Value
|
||||
|
||||
func resetMap(out reflect.Value) {
|
||||
for _, k := range out.MapKeys() {
|
||||
out.SetMapIndex(k, zeroValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) scalar(n *Node, out reflect.Value) bool {
|
||||
var tag string
|
||||
var resolved interface{}
|
||||
if n.indicatedString() {
|
||||
tag = strTag
|
||||
resolved = n.Value
|
||||
} else {
|
||||
tag, resolved = resolve(n.Tag, n.Value)
|
||||
if tag == binaryTag {
|
||||
data, err := base64.StdEncoding.DecodeString(resolved.(string))
|
||||
if err != nil {
|
||||
failf("!!binary value contains invalid base64 data")
|
||||
}
|
||||
resolved = string(data)
|
||||
}
|
||||
}
|
||||
if resolved == nil {
|
||||
if out.CanAddr() {
|
||||
switch out.Kind() {
|
||||
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
// We've resolved to exactly the type we want, so use that.
|
||||
out.Set(resolvedv)
|
||||
return true
|
||||
}
|
||||
// Perhaps we can use the value as a TextUnmarshaler to
|
||||
// set its value.
|
||||
if out.CanAddr() {
|
||||
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
if ok {
|
||||
var text []byte
|
||||
if tag == binaryTag {
|
||||
text = []byte(resolved.(string))
|
||||
} else {
|
||||
// We let any value be unmarshaled into TextUnmarshaler.
|
||||
// That might be more lax than we'd like, but the
|
||||
// TextUnmarshaler itself should bowl out any dubious values.
|
||||
text = []byte(n.Value)
|
||||
}
|
||||
err := u.UnmarshalText(text)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
switch out.Kind() {
|
||||
case reflect.String:
|
||||
if tag == binaryTag {
|
||||
out.SetString(resolved.(string))
|
||||
return true
|
||||
}
|
||||
out.SetString(n.Value)
|
||||
return true
|
||||
case reflect.Interface:
|
||||
out.Set(reflect.ValueOf(resolved))
|
||||
return true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
// This used to work in v2, but it's very unfriendly.
|
||||
isDuration := out.Type() == durationType
|
||||
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
if !isDuration && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case int64:
|
||||
if !isDuration && !out.OverflowInt(resolved) {
|
||||
out.SetInt(resolved)
|
||||
return true
|
||||
}
|
||||
case uint64:
|
||||
if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case string:
|
||||
if out.Type() == durationType {
|
||||
d, err := time.ParseDuration(resolved)
|
||||
if err == nil {
|
||||
out.SetInt(int64(d))
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case int64:
|
||||
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case uint64:
|
||||
if !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
}
|
||||
case reflect.Bool:
|
||||
switch resolved := resolved.(type) {
|
||||
case bool:
|
||||
out.SetBool(resolved)
|
||||
return true
|
||||
case string:
|
||||
// This offers some compatibility with the 1.1 spec (https://yaml.org/type/bool.html).
|
||||
// It only works if explicitly attempting to unmarshal into a typed bool value.
|
||||
switch resolved {
|
||||
case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON":
|
||||
out.SetBool(true)
|
||||
return true
|
||||
case "n", "N", "no", "No", "NO", "off", "Off", "OFF":
|
||||
out.SetBool(false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case int64:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case uint64:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case float64:
|
||||
out.SetFloat(resolved)
|
||||
return true
|
||||
}
|
||||
case reflect.Struct:
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
out.Set(resolvedv)
|
||||
return true
|
||||
}
|
||||
case reflect.Ptr:
|
||||
panic("yaml internal error: please report the issue")
|
||||
}
|
||||
d.terror(n, tag, out)
|
||||
return false
|
||||
}
|
||||
|
||||
func settableValueOf(i interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(i)
|
||||
sv := reflect.New(v.Type()).Elem()
|
||||
sv.Set(v)
|
||||
return sv
|
||||
}
|
||||
|
||||
func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) {
|
||||
l := len(n.Content)
|
||||
|
||||
var iface reflect.Value
|
||||
switch out.Kind() {
|
||||
case reflect.Slice:
|
||||
out.Set(reflect.MakeSlice(out.Type(), l, l))
|
||||
case reflect.Array:
|
||||
if l != out.Len() {
|
||||
failf("invalid array: want %d elements but got %d", out.Len(), l)
|
||||
}
|
||||
case reflect.Interface:
|
||||
// No type hints. Will have to use a generic sequence.
|
||||
iface = out
|
||||
out = settableValueOf(make([]interface{}, l))
|
||||
default:
|
||||
d.terror(n, seqTag, out)
|
||||
return false
|
||||
}
|
||||
et := out.Type().Elem()
|
||||
|
||||
j := 0
|
||||
for i := 0; i < l; i++ {
|
||||
e := reflect.New(et).Elem()
|
||||
if ok := d.unmarshal(n.Content[i], e); ok {
|
||||
out.Index(j).Set(e)
|
||||
j++
|
||||
}
|
||||
}
|
||||
if out.Kind() != reflect.Array {
|
||||
out.Set(out.Slice(0, j))
|
||||
}
|
||||
if iface.IsValid() {
|
||||
iface.Set(out)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
|
||||
l := len(n.Content)
|
||||
if d.uniqueKeys {
|
||||
nerrs := len(d.terrors)
|
||||
for i := 0; i < l; i += 2 {
|
||||
ni := n.Content[i]
|
||||
for j := i + 2; j < l; j += 2 {
|
||||
nj := n.Content[j]
|
||||
if ni.Kind == nj.Kind && ni.Value == nj.Value {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: mapping key %#v already defined at line %d", nj.Line, nj.Value, ni.Line))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(d.terrors) > nerrs {
|
||||
return false
|
||||
}
|
||||
}
|
||||
switch out.Kind() {
|
||||
case reflect.Struct:
|
||||
return d.mappingStruct(n, out)
|
||||
case reflect.Map:
|
||||
// okay
|
||||
case reflect.Interface:
|
||||
iface := out
|
||||
if isStringMap(n) {
|
||||
out = reflect.MakeMap(d.stringMapType)
|
||||
} else {
|
||||
out = reflect.MakeMap(d.generalMapType)
|
||||
}
|
||||
iface.Set(out)
|
||||
default:
|
||||
d.terror(n, mapTag, out)
|
||||
return false
|
||||
}
|
||||
|
||||
outt := out.Type()
|
||||
kt := outt.Key()
|
||||
et := outt.Elem()
|
||||
|
||||
stringMapType := d.stringMapType
|
||||
generalMapType := d.generalMapType
|
||||
if outt.Elem() == ifaceType {
|
||||
if outt.Key().Kind() == reflect.String {
|
||||
d.stringMapType = outt
|
||||
} else if outt.Key() == ifaceType {
|
||||
d.generalMapType = outt
|
||||
}
|
||||
}
|
||||
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(outt))
|
||||
}
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.Content[i]) {
|
||||
d.merge(n.Content[i+1], out)
|
||||
continue
|
||||
}
|
||||
k := reflect.New(kt).Elem()
|
||||
if d.unmarshal(n.Content[i], k) {
|
||||
kkind := k.Kind()
|
||||
if kkind == reflect.Interface {
|
||||
kkind = k.Elem().Kind()
|
||||
}
|
||||
if kkind == reflect.Map || kkind == reflect.Slice {
|
||||
failf("invalid map key: %#v", k.Interface())
|
||||
}
|
||||
e := reflect.New(et).Elem()
|
||||
if d.unmarshal(n.Content[i+1], e) {
|
||||
out.SetMapIndex(k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.stringMapType = stringMapType
|
||||
d.generalMapType = generalMapType
|
||||
return true
|
||||
}
|
||||
|
||||
func isStringMap(n *Node) bool {
|
||||
if n.Kind != MappingNode {
|
||||
return false
|
||||
}
|
||||
l := len(n.Content)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if n.Content[i].ShortTag() != strTag {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
||||
sinfo, err := getStructInfo(out.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var inlineMap reflect.Value
|
||||
var elemType reflect.Type
|
||||
if sinfo.InlineMap != -1 {
|
||||
inlineMap = out.Field(sinfo.InlineMap)
|
||||
inlineMap.Set(reflect.New(inlineMap.Type()).Elem())
|
||||
elemType = inlineMap.Type().Elem()
|
||||
}
|
||||
|
||||
for _, index := range sinfo.InlineUnmarshalers {
|
||||
field := d.fieldByIndex(n, out, index)
|
||||
d.prepare(n, field)
|
||||
}
|
||||
|
||||
var doneFields []bool
|
||||
if d.uniqueKeys {
|
||||
doneFields = make([]bool, len(sinfo.FieldsList))
|
||||
}
|
||||
name := settableValueOf("")
|
||||
l := len(n.Content)
|
||||
for i := 0; i < l; i += 2 {
|
||||
ni := n.Content[i]
|
||||
if isMerge(ni) {
|
||||
d.merge(n.Content[i+1], out)
|
||||
continue
|
||||
}
|
||||
if !d.unmarshal(ni, name) {
|
||||
continue
|
||||
}
|
||||
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
||||
if d.uniqueKeys {
|
||||
if doneFields[info.Id] {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type()))
|
||||
continue
|
||||
}
|
||||
doneFields[info.Id] = true
|
||||
}
|
||||
var field reflect.Value
|
||||
if info.Inline == nil {
|
||||
field = out.Field(info.Num)
|
||||
} else {
|
||||
field = d.fieldByIndex(n, out, info.Inline)
|
||||
}
|
||||
d.unmarshal(n.Content[i+1], field)
|
||||
} else if sinfo.InlineMap != -1 {
|
||||
if inlineMap.IsNil() {
|
||||
inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
|
||||
}
|
||||
value := reflect.New(elemType).Elem()
|
||||
d.unmarshal(n.Content[i+1], value)
|
||||
inlineMap.SetMapIndex(name, value)
|
||||
} else if d.knownFields {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type()))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func failWantMap() {
|
||||
failf("map merge requires map or sequence of maps as the value")
|
||||
}
|
||||
|
||||
func (d *decoder) merge(n *Node, out reflect.Value) {
|
||||
switch n.Kind {
|
||||
case MappingNode:
|
||||
d.unmarshal(n, out)
|
||||
case AliasNode:
|
||||
if n.Alias != nil && n.Alias.Kind != MappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(n, out)
|
||||
case SequenceNode:
|
||||
// Step backwards as earlier nodes take precedence.
|
||||
for i := len(n.Content) - 1; i >= 0; i-- {
|
||||
ni := n.Content[i]
|
||||
if ni.Kind == AliasNode {
|
||||
if ni.Alias != nil && ni.Alias.Kind != MappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
} else if ni.Kind != MappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(ni, out)
|
||||
}
|
||||
default:
|
||||
failWantMap()
|
||||
}
|
||||
}
|
||||
|
||||
func isMerge(n *Node) bool {
|
||||
return n.Kind == ScalarNode && n.Value == "<<" && (n.Tag == "" || n.Tag == "!" || shortTag(n.Tag) == mergeTag)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,561 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
emitter yaml_emitter_t
|
||||
event yaml_event_t
|
||||
out []byte
|
||||
flow bool
|
||||
indent int
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newEncoder() *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_string(&e.emitter, &e.out)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func newEncoderWithWriter(w io.Writer) *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_writer(&e.emitter, w)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) init() {
|
||||
if e.doneInit {
|
||||
return
|
||||
}
|
||||
if e.indent == 0 {
|
||||
e.indent = 4
|
||||
}
|
||||
e.emitter.best_indent = e.indent
|
||||
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
|
||||
e.emit()
|
||||
e.doneInit = true
|
||||
}
|
||||
|
||||
func (e *encoder) finish() {
|
||||
e.emitter.open_ended = false
|
||||
yaml_stream_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) destroy() {
|
||||
yaml_emitter_delete(&e.emitter)
|
||||
}
|
||||
|
||||
func (e *encoder) emit() {
|
||||
// This will internally delete the e.event value.
|
||||
e.must(yaml_emitter_emit(&e.emitter, &e.event))
|
||||
}
|
||||
|
||||
func (e *encoder) must(ok bool) {
|
||||
if !ok {
|
||||
msg := e.emitter.problem
|
||||
if msg == "" {
|
||||
msg = "unknown problem generating YAML content"
|
||||
}
|
||||
failf("%s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) marshalDoc(tag string, in reflect.Value) {
|
||||
e.init()
|
||||
var node *Node
|
||||
if in.IsValid() {
|
||||
node, _ = in.Interface().(*Node)
|
||||
}
|
||||
if node != nil && node.Kind == DocumentNode {
|
||||
e.nodev(in)
|
||||
} else {
|
||||
yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
||||
e.emit()
|
||||
e.marshal(tag, in)
|
||||
yaml_document_end_event_initialize(&e.event, true)
|
||||
e.emit()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||
tag = shortTag(tag)
|
||||
if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
iface := in.Interface()
|
||||
switch value := iface.(type) {
|
||||
case *Node:
|
||||
e.nodev(in)
|
||||
return
|
||||
case time.Time:
|
||||
e.timev(tag, in)
|
||||
return
|
||||
case *time.Time:
|
||||
e.timev(tag, in.Elem())
|
||||
return
|
||||
case time.Duration:
|
||||
e.stringv(tag, reflect.ValueOf(value.String()))
|
||||
return
|
||||
case Marshaler:
|
||||
v, err := value.MarshalYAML()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if v == nil {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
e.marshal(tag, reflect.ValueOf(v))
|
||||
return
|
||||
case encoding.TextMarshaler:
|
||||
text, err := value.MarshalText()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
in = reflect.ValueOf(string(text))
|
||||
case nil:
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Interface:
|
||||
e.marshal(tag, in.Elem())
|
||||
case reflect.Map:
|
||||
e.mapv(tag, in)
|
||||
case reflect.Ptr:
|
||||
e.marshal(tag, in.Elem())
|
||||
case reflect.Struct:
|
||||
e.structv(tag, in)
|
||||
case reflect.Slice, reflect.Array:
|
||||
e.slicev(tag, in)
|
||||
case reflect.String:
|
||||
e.stringv(tag, in)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
e.intv(tag, in)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
e.uintv(tag, in)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
e.floatv(tag, in)
|
||||
case reflect.Bool:
|
||||
e.boolv(tag, in)
|
||||
default:
|
||||
panic("cannot marshal type: " + in.Type().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) mapv(tag string, in reflect.Value) {
|
||||
e.mappingv(tag, func() {
|
||||
keys := keyList(in.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
e.marshal("", k)
|
||||
e.marshal("", in.MapIndex(k))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
|
||||
for _, num := range index {
|
||||
for {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
v = v.Elem()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
v = v.Field(num)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (e *encoder) structv(tag string, in reflect.Value) {
|
||||
sinfo, err := getStructInfo(in.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e.mappingv(tag, func() {
|
||||
for _, info := range sinfo.FieldsList {
|
||||
var value reflect.Value
|
||||
if info.Inline == nil {
|
||||
value = in.Field(info.Num)
|
||||
} else {
|
||||
value = e.fieldByIndex(in, info.Inline)
|
||||
if !value.IsValid() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if info.OmitEmpty && isZero(value) {
|
||||
continue
|
||||
}
|
||||
e.marshal("", reflect.ValueOf(info.Key))
|
||||
e.flow = info.Flow
|
||||
e.marshal("", value)
|
||||
}
|
||||
if sinfo.InlineMap >= 0 {
|
||||
m := in.Field(sinfo.InlineMap)
|
||||
if m.Len() > 0 {
|
||||
e.flow = false
|
||||
keys := keyList(m.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
if _, found := sinfo.FieldsMap[k.String()]; found {
|
||||
panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
|
||||
}
|
||||
e.marshal("", k)
|
||||
e.flow = false
|
||||
e.marshal("", m.MapIndex(k))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) mappingv(tag string, f func()) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_MAPPING_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_MAPPING_STYLE
|
||||
}
|
||||
yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
|
||||
e.emit()
|
||||
f()
|
||||
yaml_mapping_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) slicev(tag string, in reflect.Value) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_SEQUENCE_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_SEQUENCE_STYLE
|
||||
}
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
||||
e.emit()
|
||||
n := in.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
e.marshal("", in.Index(i))
|
||||
}
|
||||
e.must(yaml_sequence_end_event_initialize(&e.event))
|
||||
e.emit()
|
||||
}
|
||||
|
||||
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
||||
//
|
||||
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
||||
// in YAML 1.2 and by this package, but these should be marshalled quoted for
|
||||
// the time being for compatibility with other parsers.
|
||||
func isBase60Float(s string) (result bool) {
|
||||
// Fast path.
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
c := s[0]
|
||||
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
||||
return false
|
||||
}
|
||||
// Do the full match.
|
||||
return base60float.MatchString(s)
|
||||
}
|
||||
|
||||
// From http://yaml.org/type/float.html, except the regular expression there
|
||||
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
||||
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
||||
|
||||
// isOldBool returns whether s is bool notation as defined in YAML 1.1.
|
||||
//
|
||||
// We continue to force strings that YAML 1.1 would interpret as booleans to be
|
||||
// rendered as quotes strings so that the marshalled output valid for YAML 1.1
|
||||
// parsing.
|
||||
func isOldBool(s string) (result bool) {
|
||||
switch s {
|
||||
case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
|
||||
"n", "N", "no", "No", "NO", "off", "Off", "OFF":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) stringv(tag string, in reflect.Value) {
|
||||
var style yaml_scalar_style_t
|
||||
s := in.String()
|
||||
canUsePlain := true
|
||||
switch {
|
||||
case !utf8.ValidString(s):
|
||||
if tag == binaryTag {
|
||||
failf("explicitly tagged !!binary data must be base64-encoded")
|
||||
}
|
||||
if tag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
||||
}
|
||||
// It can't be encoded directly as YAML so use a binary tag
|
||||
// and encode it as base64.
|
||||
tag = binaryTag
|
||||
s = encodeBase64(s)
|
||||
case tag == "":
|
||||
// Check to see if it would resolve to a specific
|
||||
// tag when encoded unquoted. If it doesn't,
|
||||
// there's no need to quote it.
|
||||
rtag, _ := resolve("", s)
|
||||
canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s))
|
||||
}
|
||||
// Note: it's possible for user code to emit invalid YAML
|
||||
// if they explicitly specify a tag and a string containing
|
||||
// text that's incompatible with that tag.
|
||||
switch {
|
||||
case strings.Contains(s, "\n"):
|
||||
if e.flow {
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
} else {
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
}
|
||||
case canUsePlain:
|
||||
style = yaml_PLAIN_SCALAR_STYLE
|
||||
default:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
}
|
||||
e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) boolv(tag string, in reflect.Value) {
|
||||
var s string
|
||||
if in.Bool() {
|
||||
s = "true"
|
||||
} else {
|
||||
s = "false"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) intv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatInt(in.Int(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) uintv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatUint(in.Uint(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) timev(tag string, in reflect.Value) {
|
||||
t := in.Interface().(time.Time)
|
||||
s := t.Format(time.RFC3339Nano)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) floatv(tag string, in reflect.Value) {
|
||||
// Issue #352: When formatting, use the precision of the underlying value
|
||||
precision := 64
|
||||
if in.Kind() == reflect.Float32 {
|
||||
precision = 32
|
||||
}
|
||||
|
||||
s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
|
||||
switch s {
|
||||
case "+Inf":
|
||||
s = ".inf"
|
||||
case "-Inf":
|
||||
s = "-.inf"
|
||||
case "NaN":
|
||||
s = ".nan"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) nilv() {
|
||||
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
|
||||
// TODO Kill this function. Replace all initialize calls by their underlining Go literals.
|
||||
implicit := tag == ""
|
||||
if !implicit {
|
||||
tag = longTag(tag)
|
||||
}
|
||||
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
||||
e.event.head_comment = head
|
||||
e.event.line_comment = line
|
||||
e.event.foot_comment = foot
|
||||
e.event.tail_comment = tail
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) nodev(in reflect.Value) {
|
||||
e.node(in.Interface().(*Node), "")
|
||||
}
|
||||
|
||||
func (e *encoder) node(node *Node, tail string) {
|
||||
// If the tag was not explicitly requested, and dropping it won't change the
|
||||
// implicit tag of the value, don't include it in the presentation.
|
||||
var tag = node.Tag
|
||||
var stag = shortTag(tag)
|
||||
var rtag string
|
||||
var forceQuoting bool
|
||||
if tag != "" && node.Style&TaggedStyle == 0 {
|
||||
if node.Kind == ScalarNode {
|
||||
if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
|
||||
tag = ""
|
||||
} else {
|
||||
rtag, _ = resolve("", node.Value)
|
||||
if rtag == stag {
|
||||
tag = ""
|
||||
} else if stag == strTag {
|
||||
tag = ""
|
||||
forceQuoting = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch node.Kind {
|
||||
case MappingNode:
|
||||
rtag = mapTag
|
||||
case SequenceNode:
|
||||
rtag = seqTag
|
||||
}
|
||||
if rtag == stag {
|
||||
tag = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch node.Kind {
|
||||
case DocumentNode:
|
||||
yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.emit()
|
||||
for _, node := range node.Content {
|
||||
e.node(node, "")
|
||||
}
|
||||
yaml_document_end_event_initialize(&e.event, true)
|
||||
e.event.foot_comment = []byte(node.FootComment)
|
||||
e.emit()
|
||||
|
||||
case SequenceNode:
|
||||
style := yaml_BLOCK_SEQUENCE_STYLE
|
||||
if node.Style&FlowStyle != 0 {
|
||||
style = yaml_FLOW_SEQUENCE_STYLE
|
||||
}
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style))
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.emit()
|
||||
for _, node := range node.Content {
|
||||
e.node(node, "")
|
||||
}
|
||||
e.must(yaml_sequence_end_event_initialize(&e.event))
|
||||
e.event.line_comment = []byte(node.LineComment)
|
||||
e.event.foot_comment = []byte(node.FootComment)
|
||||
e.emit()
|
||||
|
||||
case MappingNode:
|
||||
style := yaml_BLOCK_MAPPING_STYLE
|
||||
if node.Style&FlowStyle != 0 {
|
||||
style = yaml_FLOW_MAPPING_STYLE
|
||||
}
|
||||
yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style)
|
||||
e.event.tail_comment = []byte(tail)
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.emit()
|
||||
|
||||
// The tail logic below moves the foot comment of prior keys to the following key,
|
||||
// since the value for each key may be a nested structure and the foot needs to be
|
||||
// processed only the entirety of the value is streamed. The last tail is processed
|
||||
// with the mapping end event.
|
||||
var tail string
|
||||
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||
k := node.Content[i]
|
||||
foot := k.FootComment
|
||||
if foot != "" {
|
||||
kopy := *k
|
||||
kopy.FootComment = ""
|
||||
k = &kopy
|
||||
}
|
||||
e.node(k, tail)
|
||||
tail = foot
|
||||
|
||||
v := node.Content[i+1]
|
||||
e.node(v, "")
|
||||
}
|
||||
|
||||
yaml_mapping_end_event_initialize(&e.event)
|
||||
e.event.tail_comment = []byte(tail)
|
||||
e.event.line_comment = []byte(node.LineComment)
|
||||
e.event.foot_comment = []byte(node.FootComment)
|
||||
e.emit()
|
||||
|
||||
case AliasNode:
|
||||
yaml_alias_event_initialize(&e.event, []byte(node.Value))
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.event.line_comment = []byte(node.LineComment)
|
||||
e.event.foot_comment = []byte(node.FootComment)
|
||||
e.emit()
|
||||
|
||||
case ScalarNode:
|
||||
value := node.Value
|
||||
if !utf8.ValidString(value) {
|
||||
if tag == binaryTag {
|
||||
failf("explicitly tagged !!binary data must be base64-encoded")
|
||||
}
|
||||
if tag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
||||
}
|
||||
// It can't be encoded directly as YAML so use a binary tag
|
||||
// and encode it as base64.
|
||||
tag = binaryTag
|
||||
value = encodeBase64(value)
|
||||
}
|
||||
|
||||
style := yaml_PLAIN_SCALAR_STYLE
|
||||
switch {
|
||||
case node.Style&DoubleQuotedStyle != 0:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
case node.Style&SingleQuotedStyle != 0:
|
||||
style = yaml_SINGLE_QUOTED_SCALAR_STYLE
|
||||
case node.Style&LiteralStyle != 0:
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
case node.Style&FoldedStyle != 0:
|
||||
style = yaml_FOLDED_SCALAR_STYLE
|
||||
case strings.Contains(value, "\n"):
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
case forceQuoting:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
}
|
||||
|
||||
e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module "gopkg.in/yaml.v3"
|
||||
|
||||
require (
|
||||
"gopkg.in/check.v1" v0.0.0-20161208181325-20d25e280405
|
||||
)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,434 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// 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.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Set the reader error and return 0.
|
||||
func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool {
|
||||
parser.error = yaml_READER_ERROR
|
||||
parser.problem = problem
|
||||
parser.problem_offset = offset
|
||||
parser.problem_value = value
|
||||
return false
|
||||
}
|
||||
|
||||
// Byte order marks.
|
||||
const (
|
||||
bom_UTF8 = "\xef\xbb\xbf"
|
||||
bom_UTF16LE = "\xff\xfe"
|
||||
bom_UTF16BE = "\xfe\xff"
|
||||
)
|
||||
|
||||
// Determine the input stream encoding by checking the BOM symbol. If no BOM is
|
||||
// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure.
|
||||
func yaml_parser_determine_encoding(parser *yaml_parser_t) bool {
|
||||
// Ensure that we had enough bytes in the raw buffer.
|
||||
for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the encoding.
|
||||
buf := parser.raw_buffer
|
||||
pos := parser.raw_buffer_pos
|
||||
avail := len(buf) - pos
|
||||
if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] {
|
||||
parser.encoding = yaml_UTF16LE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] {
|
||||
parser.encoding = yaml_UTF16BE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
parser.raw_buffer_pos += 3
|
||||
parser.offset += 3
|
||||
} else {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Update the raw buffer.
|
||||
func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool {
|
||||
size_read := 0
|
||||
|
||||
// Return if the raw buffer is full.
|
||||
if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Return on EOF.
|
||||
if parser.eof {
|
||||
return true
|
||||
}
|
||||
|
||||
// Move the remaining bytes in the raw buffer to the beginning.
|
||||
if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) {
|
||||
copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:])
|
||||
}
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos]
|
||||
parser.raw_buffer_pos = 0
|
||||
|
||||
// Call the read handler to fill the buffer.
|
||||
size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)])
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read]
|
||||
if err == io.EOF {
|
||||
parser.eof = true
|
||||
} else if err != nil {
|
||||
return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure that the buffer contains at least `length` characters.
|
||||
// Return true on success, false on failure.
|
||||
//
|
||||
// The length is supposed to be significantly less that the buffer size.
|
||||
func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
|
||||
if parser.read_handler == nil {
|
||||
panic("read handler must be set")
|
||||
}
|
||||
|
||||
// [Go] This function was changed to guarantee the requested length size at EOF.
|
||||
// The fact we need to do this is pretty awful, but the description above implies
|
||||
// for that to be the case, and there are tests
|
||||
|
||||
// If the EOF flag is set and the raw buffer is empty, do nothing.
|
||||
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
// [Go] ACTUALLY! Read the documentation of this function above.
|
||||
// This is just broken. To return true, we need to have the
|
||||
// given length in the buffer. Not doing that means every single
|
||||
// check that calls this function to make sure the buffer has a
|
||||
// given length is Go) panicking; or C) accessing invalid memory.
|
||||
//return true
|
||||
}
|
||||
|
||||
// Return if the buffer contains enough characters.
|
||||
if parser.unread >= length {
|
||||
return true
|
||||
}
|
||||
|
||||
// Determine the input encoding if it is not known yet.
|
||||
if parser.encoding == yaml_ANY_ENCODING {
|
||||
if !yaml_parser_determine_encoding(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Move the unread characters to the beginning of the buffer.
|
||||
buffer_len := len(parser.buffer)
|
||||
if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len {
|
||||
copy(parser.buffer, parser.buffer[parser.buffer_pos:])
|
||||
buffer_len -= parser.buffer_pos
|
||||
parser.buffer_pos = 0
|
||||
} else if parser.buffer_pos == buffer_len {
|
||||
buffer_len = 0
|
||||
parser.buffer_pos = 0
|
||||
}
|
||||
|
||||
// Open the whole buffer for writing, and cut it before returning.
|
||||
parser.buffer = parser.buffer[:cap(parser.buffer)]
|
||||
|
||||
// Fill the buffer until it has enough characters.
|
||||
first := true
|
||||
for parser.unread < length {
|
||||
|
||||
// Fill the raw buffer if necessary.
|
||||
if !first || parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return false
|
||||
}
|
||||
}
|
||||
first = false
|
||||
|
||||
// Decode the raw buffer.
|
||||
inner:
|
||||
for parser.raw_buffer_pos != len(parser.raw_buffer) {
|
||||
var value rune
|
||||
var width int
|
||||
|
||||
raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos
|
||||
|
||||
// Decode the next character.
|
||||
switch parser.encoding {
|
||||
case yaml_UTF8_ENCODING:
|
||||
// Decode a UTF-8 character. Check RFC 3629
|
||||
// (http://www.ietf.org/rfc/rfc3629.txt) for more details.
|
||||
//
|
||||
// The following table (taken from the RFC) is used for
|
||||
// decoding.
|
||||
//
|
||||
// Char. number range | UTF-8 octet sequence
|
||||
// (hexadecimal) | (binary)
|
||||
// --------------------+------------------------------------
|
||||
// 0000 0000-0000 007F | 0xxxxxxx
|
||||
// 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
|
||||
// 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
|
||||
// 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
//
|
||||
// Additionally, the characters in the range 0xD800-0xDFFF
|
||||
// are prohibited as they are reserved for use with UTF-16
|
||||
// surrogate pairs.
|
||||
|
||||
// Determine the length of the UTF-8 sequence.
|
||||
octet := parser.raw_buffer[parser.raw_buffer_pos]
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
width = 1
|
||||
case octet&0xE0 == 0xC0:
|
||||
width = 2
|
||||
case octet&0xF0 == 0xE0:
|
||||
width = 3
|
||||
case octet&0xF8 == 0xF0:
|
||||
width = 4
|
||||
default:
|
||||
// The leading octet is invalid.
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid leading UTF-8 octet",
|
||||
parser.offset, int(octet))
|
||||
}
|
||||
|
||||
// Check if the raw buffer contains an incomplete character.
|
||||
if width > raw_unread {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-8 octet sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Decode the leading octet.
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
value = rune(octet & 0x7F)
|
||||
case octet&0xE0 == 0xC0:
|
||||
value = rune(octet & 0x1F)
|
||||
case octet&0xF0 == 0xE0:
|
||||
value = rune(octet & 0x0F)
|
||||
case octet&0xF8 == 0xF0:
|
||||
value = rune(octet & 0x07)
|
||||
default:
|
||||
value = 0
|
||||
}
|
||||
|
||||
// Check and decode the trailing octets.
|
||||
for k := 1; k < width; k++ {
|
||||
octet = parser.raw_buffer[parser.raw_buffer_pos+k]
|
||||
|
||||
// Check if the octet is valid.
|
||||
if (octet & 0xC0) != 0x80 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid trailing UTF-8 octet",
|
||||
parser.offset+k, int(octet))
|
||||
}
|
||||
|
||||
// Decode the octet.
|
||||
value = (value << 6) + rune(octet&0x3F)
|
||||
}
|
||||
|
||||
// Check the length of the sequence against the value.
|
||||
switch {
|
||||
case width == 1:
|
||||
case width == 2 && value >= 0x80:
|
||||
case width == 3 && value >= 0x800:
|
||||
case width == 4 && value >= 0x10000:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid length of a UTF-8 sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
|
||||
// Check the range of the value.
|
||||
if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid Unicode character",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING:
|
||||
var low, high int
|
||||
if parser.encoding == yaml_UTF16LE_ENCODING {
|
||||
low, high = 0, 1
|
||||
} else {
|
||||
low, high = 1, 0
|
||||
}
|
||||
|
||||
// The UTF-16 encoding is not as simple as one might
|
||||
// naively think. Check RFC 2781
|
||||
// (http://www.ietf.org/rfc/rfc2781.txt).
|
||||
//
|
||||
// Normally, two subsequent bytes describe a Unicode
|
||||
// character. However a special technique (called a
|
||||
// surrogate pair) is used for specifying character
|
||||
// values larger than 0xFFFF.
|
||||
//
|
||||
// A surrogate pair consists of two pseudo-characters:
|
||||
// high surrogate area (0xD800-0xDBFF)
|
||||
// low surrogate area (0xDC00-0xDFFF)
|
||||
//
|
||||
// The following formulas are used for decoding
|
||||
// and encoding characters using surrogate pairs:
|
||||
//
|
||||
// U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF)
|
||||
// U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF)
|
||||
// W1 = 110110yyyyyyyyyy
|
||||
// W2 = 110111xxxxxxxxxx
|
||||
//
|
||||
// where U is the character value, W1 is the high surrogate
|
||||
// area, W2 is the low surrogate area.
|
||||
|
||||
// Check for incomplete UTF-16 character.
|
||||
if raw_unread < 2 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 character",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the character.
|
||||
value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8)
|
||||
|
||||
// Check for unexpected low surrogate area.
|
||||
if value&0xFC00 == 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"unexpected low surrogate area",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Check for a high surrogate area.
|
||||
if value&0xFC00 == 0xD800 {
|
||||
width = 4
|
||||
|
||||
// Check for incomplete surrogate pair.
|
||||
if raw_unread < 4 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 surrogate pair",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the next character.
|
||||
value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8)
|
||||
|
||||
// Check for a low surrogate area.
|
||||
if value2&0xFC00 != 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"expected low surrogate area",
|
||||
parser.offset+2, int(value2))
|
||||
}
|
||||
|
||||
// Generate the value of the surrogate pair.
|
||||
value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF)
|
||||
} else {
|
||||
width = 2
|
||||
}
|
||||
|
||||
default:
|
||||
panic("impossible")
|
||||
}
|
||||
|
||||
// Check if the character is in the allowed range:
|
||||
// #x9 | #xA | #xD | [#x20-#x7E] (8 bit)
|
||||
// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit)
|
||||
// | [#x10000-#x10FFFF] (32 bit)
|
||||
switch {
|
||||
case value == 0x09:
|
||||
case value == 0x0A:
|
||||
case value == 0x0D:
|
||||
case value >= 0x20 && value <= 0x7E:
|
||||
case value == 0x85:
|
||||
case value >= 0xA0 && value <= 0xD7FF:
|
||||
case value >= 0xE000 && value <= 0xFFFD:
|
||||
case value >= 0x10000 && value <= 0x10FFFF:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"control characters are not allowed",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Move the raw pointers.
|
||||
parser.raw_buffer_pos += width
|
||||
parser.offset += width
|
||||
|
||||
// Finally put the character into the buffer.
|
||||
if value <= 0x7F {
|
||||
// 0000 0000-0000 007F . 0xxxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(value)
|
||||
buffer_len += 1
|
||||
} else if value <= 0x7FF {
|
||||
// 0000 0080-0000 07FF . 110xxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 2
|
||||
} else if value <= 0xFFFF {
|
||||
// 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 3
|
||||
} else {
|
||||
// 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 4
|
||||
}
|
||||
|
||||
parser.unread++
|
||||
}
|
||||
|
||||
// On EOF, put NUL into the buffer and return.
|
||||
if parser.eof {
|
||||
parser.buffer[buffer_len] = 0
|
||||
buffer_len++
|
||||
parser.unread++
|
||||
break
|
||||
}
|
||||
}
|
||||
// [Go] Read the documentation of this function above. To return true,
|
||||
// we need to have the given length in the buffer. Not doing that means
|
||||
// every single check that calls this function to make sure the buffer
|
||||
// has a given length is Go) panicking; or C) accessing invalid memory.
|
||||
// This happens here due to the EOF above breaking early.
|
||||
for buffer_len < length {
|
||||
parser.buffer[buffer_len] = 0
|
||||
buffer_len++
|
||||
}
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,326 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type resolveMapItem struct {
|
||||
value interface{}
|
||||
tag string
|
||||
}
|
||||
|
||||
var resolveTable = make([]byte, 256)
|
||||
var resolveMap = make(map[string]resolveMapItem)
|
||||
|
||||
func init() {
|
||||
t := resolveTable
|
||||
t[int('+')] = 'S' // Sign
|
||||
t[int('-')] = 'S'
|
||||
for _, c := range "0123456789" {
|
||||
t[int(c)] = 'D' // Digit
|
||||
}
|
||||
for _, c := range "yYnNtTfFoO~" {
|
||||
t[int(c)] = 'M' // In map
|
||||
}
|
||||
t[int('.')] = '.' // Float (potentially in map)
|
||||
|
||||
var resolveMapList = []struct {
|
||||
v interface{}
|
||||
tag string
|
||||
l []string
|
||||
}{
|
||||
{true, boolTag, []string{"true", "True", "TRUE"}},
|
||||
{false, boolTag, []string{"false", "False", "FALSE"}},
|
||||
{nil, nullTag, []string{"", "~", "null", "Null", "NULL"}},
|
||||
{math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}},
|
||||
{math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}},
|
||||
{math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}},
|
||||
{math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}},
|
||||
{"<<", mergeTag, []string{"<<"}},
|
||||
}
|
||||
|
||||
m := resolveMap
|
||||
for _, item := range resolveMapList {
|
||||
for _, s := range item.l {
|
||||
m[s] = resolveMapItem{item.v, item.tag}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
nullTag = "!!null"
|
||||
boolTag = "!!bool"
|
||||
strTag = "!!str"
|
||||
intTag = "!!int"
|
||||
floatTag = "!!float"
|
||||
timestampTag = "!!timestamp"
|
||||
seqTag = "!!seq"
|
||||
mapTag = "!!map"
|
||||
binaryTag = "!!binary"
|
||||
mergeTag = "!!merge"
|
||||
)
|
||||
|
||||
var longTags = make(map[string]string)
|
||||
var shortTags = make(map[string]string)
|
||||
|
||||
func init() {
|
||||
for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} {
|
||||
ltag := longTag(stag)
|
||||
longTags[stag] = ltag
|
||||
shortTags[ltag] = stag
|
||||
}
|
||||
}
|
||||
|
||||
const longTagPrefix = "tag:yaml.org,2002:"
|
||||
|
||||
func shortTag(tag string) string {
|
||||
if strings.HasPrefix(tag, longTagPrefix) {
|
||||
if stag, ok := shortTags[tag]; ok {
|
||||
return stag
|
||||
}
|
||||
return "!!" + tag[len(longTagPrefix):]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func longTag(tag string) string {
|
||||
if strings.HasPrefix(tag, "!!") {
|
||||
if ltag, ok := longTags[tag]; ok {
|
||||
return ltag
|
||||
}
|
||||
return longTagPrefix + tag[2:]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func resolvableTag(tag string) bool {
|
||||
switch tag {
|
||||
case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
|
||||
|
||||
func resolve(tag string, in string) (rtag string, out interface{}) {
|
||||
tag = shortTag(tag)
|
||||
if !resolvableTag(tag) {
|
||||
return tag, in
|
||||
}
|
||||
|
||||
defer func() {
|
||||
switch tag {
|
||||
case "", rtag, strTag, binaryTag:
|
||||
return
|
||||
case floatTag:
|
||||
if rtag == intTag {
|
||||
switch v := out.(type) {
|
||||
case int64:
|
||||
rtag = floatTag
|
||||
out = float64(v)
|
||||
return
|
||||
case int:
|
||||
rtag = floatTag
|
||||
out = float64(v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
|
||||
}()
|
||||
|
||||
// Any data is accepted as a !!str or !!binary.
|
||||
// Otherwise, the prefix is enough of a hint about what it might be.
|
||||
hint := byte('N')
|
||||
if in != "" {
|
||||
hint = resolveTable[in[0]]
|
||||
}
|
||||
if hint != 0 && tag != strTag && tag != binaryTag {
|
||||
// Handle things we can lookup in a map.
|
||||
if item, ok := resolveMap[in]; ok {
|
||||
return item.tag, item.value
|
||||
}
|
||||
|
||||
// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
|
||||
// are purposefully unsupported here. They're still quoted on
|
||||
// the way out for compatibility with other parser, though.
|
||||
|
||||
switch hint {
|
||||
case 'M':
|
||||
// We've already checked the map above.
|
||||
|
||||
case '.':
|
||||
// Not in the map, so maybe a normal float.
|
||||
floatv, err := strconv.ParseFloat(in, 64)
|
||||
if err == nil {
|
||||
return floatTag, floatv
|
||||
}
|
||||
|
||||
case 'D', 'S':
|
||||
// Int, float, or timestamp.
|
||||
// Only try values as a timestamp if the value is unquoted or there's an explicit
|
||||
// !!timestamp tag.
|
||||
if tag == "" || tag == timestampTag {
|
||||
t, ok := parseTimestamp(in)
|
||||
if ok {
|
||||
return timestampTag, t
|
||||
}
|
||||
}
|
||||
|
||||
plain := strings.Replace(in, "_", "", -1)
|
||||
intv, err := strconv.ParseInt(plain, 0, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain, 0, 64)
|
||||
if err == nil {
|
||||
return intTag, uintv
|
||||
}
|
||||
if yamlStyleFloat.MatchString(plain) {
|
||||
floatv, err := strconv.ParseFloat(plain, 64)
|
||||
if err == nil {
|
||||
return floatTag, floatv
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(plain, "0b") {
|
||||
intv, err := strconv.ParseInt(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
return intTag, uintv
|
||||
}
|
||||
} else if strings.HasPrefix(plain, "-0b") {
|
||||
intv, err := strconv.ParseInt("-"+plain[3:], 2, 64)
|
||||
if err == nil {
|
||||
if true || intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
}
|
||||
// Octals as introduced in version 1.2 of the spec.
|
||||
// Octals from the 1.1 spec, spelled as 0777, are still
|
||||
// decoded by default in v3 as well for compatibility.
|
||||
// May be dropped in v4 depending on how usage evolves.
|
||||
if strings.HasPrefix(plain, "0o") {
|
||||
intv, err := strconv.ParseInt(plain[2:], 8, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain[2:], 8, 64)
|
||||
if err == nil {
|
||||
return intTag, uintv
|
||||
}
|
||||
} else if strings.HasPrefix(plain, "-0o") {
|
||||
intv, err := strconv.ParseInt("-"+plain[3:], 8, 64)
|
||||
if err == nil {
|
||||
if true || intv == int64(int(intv)) {
|
||||
return intTag, int(intv)
|
||||
} else {
|
||||
return intTag, intv
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")")
|
||||
}
|
||||
}
|
||||
return strTag, in
|
||||
}
|
||||
|
||||
// encodeBase64 encodes s as base64 that is broken up into multiple lines
|
||||
// as appropriate for the resulting length.
|
||||
func encodeBase64(s string) string {
|
||||
const lineLen = 70
|
||||
encLen := base64.StdEncoding.EncodedLen(len(s))
|
||||
lines := encLen/lineLen + 1
|
||||
buf := make([]byte, encLen*2+lines)
|
||||
in := buf[0:encLen]
|
||||
out := buf[encLen:]
|
||||
base64.StdEncoding.Encode(in, []byte(s))
|
||||
k := 0
|
||||
for i := 0; i < len(in); i += lineLen {
|
||||
j := i + lineLen
|
||||
if j > len(in) {
|
||||
j = len(in)
|
||||
}
|
||||
k += copy(out[k:], in[i:j])
|
||||
if lines > 1 {
|
||||
out[k] = '\n'
|
||||
k++
|
||||
}
|
||||
}
|
||||
return string(out[:k])
|
||||
}
|
||||
|
||||
// This is a subset of the formats allowed by the regular expression
|
||||
// defined at http://yaml.org/type/timestamp.html.
|
||||
var allowedTimestampFormats = []string{
|
||||
"2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
|
||||
"2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
|
||||
"2006-1-2 15:4:5.999999999", // space separated with no time zone
|
||||
"2006-1-2", // date only
|
||||
// Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
|
||||
// from the set of examples.
|
||||
}
|
||||
|
||||
// parseTimestamp parses s as a timestamp string and
|
||||
// returns the timestamp and reports whether it succeeded.
|
||||
// Timestamp formats are defined at http://yaml.org/type/timestamp.html
|
||||
func parseTimestamp(s string) (time.Time, bool) {
|
||||
// TODO write code to check all the formats supported by
|
||||
// http://yaml.org/type/timestamp.html instead of using time.Parse.
|
||||
|
||||
// Quick check: all date formats start with YYYY-.
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if c := s[i]; c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i != 4 || i == len(s) || s[i] != '-' {
|
||||
return time.Time{}, false
|
||||
}
|
||||
for _, format := range allowedTimestampFormats {
|
||||
if t, err := time.Parse(format, s); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type keyList []reflect.Value
|
||||
|
||||
func (l keyList) Len() int { return len(l) }
|
||||
func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l keyList) Less(i, j int) bool {
|
||||
a := l[i]
|
||||
b := l[j]
|
||||
ak := a.Kind()
|
||||
bk := b.Kind()
|
||||
for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() {
|
||||
a = a.Elem()
|
||||
ak = a.Kind()
|
||||
}
|
||||
for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() {
|
||||
b = b.Elem()
|
||||
bk = b.Kind()
|
||||
}
|
||||
af, aok := keyFloat(a)
|
||||
bf, bok := keyFloat(b)
|
||||
if aok && bok {
|
||||
if af != bf {
|
||||
return af < bf
|
||||
}
|
||||
if ak != bk {
|
||||
return ak < bk
|
||||
}
|
||||
return numLess(a, b)
|
||||
}
|
||||
if ak != reflect.String || bk != reflect.String {
|
||||
return ak < bk
|
||||
}
|
||||
ar, br := []rune(a.String()), []rune(b.String())
|
||||
digits := false
|
||||
for i := 0; i < len(ar) && i < len(br); i++ {
|
||||
if ar[i] == br[i] {
|
||||
digits = unicode.IsDigit(ar[i])
|
||||
continue
|
||||
}
|
||||
al := unicode.IsLetter(ar[i])
|
||||
bl := unicode.IsLetter(br[i])
|
||||
if al && bl {
|
||||
return ar[i] < br[i]
|
||||
}
|
||||
if al || bl {
|
||||
if digits {
|
||||
return al
|
||||
} else {
|
||||
return bl
|
||||
}
|
||||
}
|
||||
var ai, bi int
|
||||
var an, bn int64
|
||||
if ar[i] == '0' || br[i] == '0' {
|
||||
for j := i - 1; j >= 0 && unicode.IsDigit(ar[j]); j-- {
|
||||
if ar[j] != '0' {
|
||||
an = 1
|
||||
bn = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ {
|
||||
an = an*10 + int64(ar[ai]-'0')
|
||||
}
|
||||
for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ {
|
||||
bn = bn*10 + int64(br[bi]-'0')
|
||||
}
|
||||
if an != bn {
|
||||
return an < bn
|
||||
}
|
||||
if ai != bi {
|
||||
return ai < bi
|
||||
}
|
||||
return ar[i] < br[i]
|
||||
}
|
||||
return len(ar) < len(br)
|
||||
}
|
||||
|
||||
// keyFloat returns a float value for v if it is a number/bool
|
||||
// and whether it is a number/bool or not.
|
||||
func keyFloat(v reflect.Value) (f float64, ok bool) {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return float64(v.Int()), true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float(), true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return float64(v.Uint()), true
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
return 1, true
|
||||
}
|
||||
return 0, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// numLess returns whether a < b.
|
||||
// a and b must necessarily have the same kind.
|
||||
func numLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
}
|
||||
panic("not a number")
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// 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.
|
||||
|
||||
package yaml
|
||||
|
||||
// Set the writer error and return false.
|
||||
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
|
||||
emitter.error = yaml_WRITER_ERROR
|
||||
emitter.problem = problem
|
||||
return false
|
||||
}
|
||||
|
||||
// Flush the output buffer.
|
||||
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
|
||||
if emitter.write_handler == nil {
|
||||
panic("write handler not set")
|
||||
}
|
||||
|
||||
// Check if the buffer is empty.
|
||||
if emitter.buffer_pos == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
|
||||
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
|
||||
}
|
||||
emitter.buffer_pos = 0
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,662 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package yaml implements YAML support for the Go language.
|
||||
//
|
||||
// Source code and other details for the project are available at GitHub:
|
||||
//
|
||||
// https://github.com/go-yaml/yaml
|
||||
//
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// The Unmarshaler interface may be implemented by types to customize their
|
||||
// behavior when being unmarshaled from a YAML document.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalYAML(value *Node) error
|
||||
}
|
||||
|
||||
type obsoleteUnmarshaler interface {
|
||||
UnmarshalYAML(unmarshal func(interface{}) error) error
|
||||
}
|
||||
|
||||
// The Marshaler interface may be implemented by types to customize their
|
||||
// behavior when being marshaled into a YAML document. The returned value
|
||||
// is marshaled in place of the original value implementing Marshaler.
|
||||
//
|
||||
// If an error is returned by MarshalYAML, the marshaling procedure stops
|
||||
// and returns with the provided error.
|
||||
type Marshaler interface {
|
||||
MarshalYAML() (interface{}, error)
|
||||
}
|
||||
|
||||
// Unmarshal decodes the first document found within the in byte slice
|
||||
// and assigns decoded values into the out value.
|
||||
//
|
||||
// Maps and pointers (to a struct, string, int, etc) are accepted as out
|
||||
// values. If an internal pointer within a struct is not initialized,
|
||||
// the yaml package will initialize it if necessary for unmarshalling
|
||||
// the provided data. The out parameter must not be nil.
|
||||
//
|
||||
// The type of the decoded values should be compatible with the respective
|
||||
// values in out. If one or more values cannot be decoded due to a type
|
||||
// mismatches, decoding continues partially until the end of the YAML
|
||||
// content, and a *yaml.TypeError is returned with details for all
|
||||
// missed values.
|
||||
//
|
||||
// Struct fields are only unmarshalled if they are exported (have an
|
||||
// upper case first letter), and are unmarshalled using the field name
|
||||
// lowercased as the default key. Custom keys may be defined via the
|
||||
// "yaml" name in the field tag: the content preceding the first comma
|
||||
// is used as the key, and the following comma-separated options are
|
||||
// used to tweak the marshalling process (see Marshal).
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// var t T
|
||||
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||||
//
|
||||
// See the documentation of Marshal for the format of tags and a list of
|
||||
// supported tag options.
|
||||
//
|
||||
func Unmarshal(in []byte, out interface{}) (err error) {
|
||||
return unmarshal(in, out, false)
|
||||
}
|
||||
|
||||
// A Decorder reads and decodes YAML values from an input stream.
|
||||
type Decoder struct {
|
||||
parser *parser
|
||||
knownFields bool
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read
|
||||
// data from r beyond the YAML values requested.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{
|
||||
parser: newParserFromReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
// KnownFields ensures that the keys in decoded mappings to
|
||||
// exist as fields in the struct being decoded into.
|
||||
func (dec *Decoder) KnownFields(enable bool) {
|
||||
dec.knownFields = enable
|
||||
}
|
||||
|
||||
// Decode reads the next YAML-encoded value from its input
|
||||
// and stores it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the
|
||||
// conversion of YAML into a Go value.
|
||||
func (dec *Decoder) Decode(v interface{}) (err error) {
|
||||
d := newDecoder()
|
||||
d.knownFields = dec.knownFields
|
||||
defer handleErr(&err)
|
||||
node := dec.parser.parse()
|
||||
if node == nil {
|
||||
return io.EOF
|
||||
}
|
||||
out := reflect.ValueOf(v)
|
||||
if out.Kind() == reflect.Ptr && !out.IsNil() {
|
||||
out = out.Elem()
|
||||
}
|
||||
d.unmarshal(node, out)
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode decodes the node and stores its data into the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the
|
||||
// conversion of YAML into a Go value.
|
||||
func (n *Node) Decode(v interface{}) (err error) {
|
||||
d := newDecoder()
|
||||
defer handleErr(&err)
|
||||
out := reflect.ValueOf(v)
|
||||
if out.Kind() == reflect.Ptr && !out.IsNil() {
|
||||
out = out.Elem()
|
||||
}
|
||||
d.unmarshal(n, out)
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshal(in []byte, out interface{}, strict bool) (err error) {
|
||||
defer handleErr(&err)
|
||||
d := newDecoder()
|
||||
p := newParser(in)
|
||||
defer p.destroy()
|
||||
node := p.parse()
|
||||
if node != nil {
|
||||
v := reflect.ValueOf(out)
|
||||
if v.Kind() == reflect.Ptr && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
d.unmarshal(node, v)
|
||||
}
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal serializes the value provided into a YAML document. The structure
|
||||
// of the generated document will reflect the structure of the value itself.
|
||||
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||||
//
|
||||
// Struct fields are only marshalled if they are exported (have an upper case
|
||||
// first letter), and are marshalled using the field name lowercased as the
|
||||
// default key. Custom keys may be defined via the "yaml" name in the field
|
||||
// tag: the content preceding the first comma is used as the key, and the
|
||||
// following comma-separated options are used to tweak the marshalling process.
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// The field tag format accepted is:
|
||||
//
|
||||
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||||
//
|
||||
// The following flags are currently supported:
|
||||
//
|
||||
// omitempty Only include the field if it's not set to the zero
|
||||
// value for the type or to empty slices or maps.
|
||||
// Zero valued structs will be omitted if all their public
|
||||
// fields are zero, unless they implement an IsZero
|
||||
// method (see the IsZeroer interface type), in which
|
||||
// case the field will be included if that method returns true.
|
||||
//
|
||||
// flow Marshal using a flow style (useful for structs,
|
||||
// sequences and maps).
|
||||
//
|
||||
// inline Inline the field, which must be a struct or a map,
|
||||
// causing all of its fields or keys to be processed as if
|
||||
// they were part of the outer struct. For maps, keys must
|
||||
// not conflict with the yaml keys of other struct fields.
|
||||
//
|
||||
// In addition, if the key is "-", the field is ignored.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||||
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
||||
//
|
||||
func Marshal(in interface{}) (out []byte, err error) {
|
||||
defer handleErr(&err)
|
||||
e := newEncoder()
|
||||
defer e.destroy()
|
||||
e.marshalDoc("", reflect.ValueOf(in))
|
||||
e.finish()
|
||||
out = e.out
|
||||
return
|
||||
}
|
||||
|
||||
// An Encoder writes YAML values to an output stream.
|
||||
type Encoder struct {
|
||||
encoder *encoder
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
// The Encoder should be closed after use to flush all data
|
||||
// to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
encoder: newEncoderWithWriter(w),
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes the YAML encoding of v to the stream.
|
||||
// If multiple items are encoded to the stream, the
|
||||
// second and subsequent document will be preceded
|
||||
// with a "---" document separator, but the first will not.
|
||||
//
|
||||
// See the documentation for Marshal for details about the conversion of Go
|
||||
// values to YAML.
|
||||
func (e *Encoder) Encode(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
e.encoder.marshalDoc("", reflect.ValueOf(v))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetIndent changes the used indentation used when encoding.
|
||||
func (e *Encoder) SetIndent(spaces int) {
|
||||
if spaces < 0 {
|
||||
panic("yaml: cannot indent to a negative number of spaces")
|
||||
}
|
||||
e.encoder.indent = spaces
|
||||
}
|
||||
|
||||
// Close closes the encoder by writing any remaining data.
|
||||
// It does not write a stream terminating string "...".
|
||||
func (e *Encoder) Close() (err error) {
|
||||
defer handleErr(&err)
|
||||
e.encoder.finish()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleErr(err *error) {
|
||||
if v := recover(); v != nil {
|
||||
if e, ok := v.(yamlError); ok {
|
||||
*err = e.err
|
||||
} else {
|
||||
panic(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type yamlError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func fail(err error) {
|
||||
panic(yamlError{err})
|
||||
}
|
||||
|
||||
func failf(format string, args ...interface{}) {
|
||||
panic(yamlError{fmt.Errorf("yaml: "+format, args...)})
|
||||
}
|
||||
|
||||
// A TypeError is returned by Unmarshal when one or more fields in
|
||||
// the YAML document cannot be properly decoded into the requested
|
||||
// types. When this error is returned, the value is still
|
||||
// unmarshaled partially.
|
||||
type TypeError struct {
|
||||
Errors []string
|
||||
}
|
||||
|
||||
func (e *TypeError) Error() string {
|
||||
return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
|
||||
}
|
||||
|
||||
type Kind uint32
|
||||
|
||||
const (
|
||||
DocumentNode Kind = 1 << iota
|
||||
SequenceNode
|
||||
MappingNode
|
||||
ScalarNode
|
||||
AliasNode
|
||||
)
|
||||
|
||||
type Style uint32
|
||||
|
||||
const (
|
||||
TaggedStyle Style = 1 << iota
|
||||
DoubleQuotedStyle
|
||||
SingleQuotedStyle
|
||||
LiteralStyle
|
||||
FoldedStyle
|
||||
FlowStyle
|
||||
)
|
||||
|
||||
// Node represents an element in the YAML document hierarchy. While documents
|
||||
// are typically encoded and decoded into higher level types, such as structs
|
||||
// and maps, Node is an intermediate representation that allows detailed
|
||||
// control over the content being decoded or encoded.
|
||||
//
|
||||
// Values that make use of the Node type interact with the yaml package in the
|
||||
// same way any other type would do, by encoding and decoding yaml data
|
||||
// directly or indirectly into them.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var person struct {
|
||||
// Name string
|
||||
// Address yaml.Node
|
||||
// }
|
||||
// err := yaml.Unmarshal(data, &person)
|
||||
//
|
||||
// Or by itself:
|
||||
//
|
||||
// var person Node
|
||||
// err := yaml.Unmarshal(data, &person)
|
||||
//
|
||||
type Node struct {
|
||||
// Kind defines whether the node is a document, a mapping, a sequence,
|
||||
// a scalar value, or an alias to another node. The specific data type of
|
||||
// scalar nodes may be obtained via the ShortTag and LongTag methods.
|
||||
Kind Kind
|
||||
|
||||
// Style allows customizing the apperance of the node in the tree.
|
||||
Style Style
|
||||
|
||||
// Tag holds the YAML tag defining the data type for the value.
|
||||
// When decoding, this field will always be set to the resolved tag,
|
||||
// even when it wasn't explicitly provided in the YAML content.
|
||||
// When encoding, if this field is unset the value type will be
|
||||
// implied from the node properties, and if it is set, it will only
|
||||
// be serialized into the representation if TaggedStyle is used or
|
||||
// the implicit tag diverges from the provided one.
|
||||
Tag string
|
||||
|
||||
// Value holds the unescaped and unquoted represenation of the value.
|
||||
Value string
|
||||
|
||||
// Anchor holds the anchor name for this node, which allows aliases to point to it.
|
||||
Anchor string
|
||||
|
||||
// Alias holds the node that this alias points to. Only valid when Kind is AliasNode.
|
||||
Alias *Node
|
||||
|
||||
// Content holds contained nodes for documents, mappings, and sequences.
|
||||
Content []*Node
|
||||
|
||||
// HeadComment holds any comments in the lines preceding the node and
|
||||
// not separated by an empty line.
|
||||
HeadComment string
|
||||
|
||||
// LineComment holds any comments at the end of the line where the node is in.
|
||||
LineComment string
|
||||
|
||||
// FootComment holds any comments following the node and before empty lines.
|
||||
FootComment string
|
||||
|
||||
// Line and Column hold the node position in the decoded YAML text.
|
||||
// These fields are not respected when encoding the node.
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// LongTag returns the long form of the tag that indicates the data type for
|
||||
// the node. If the Tag field isn't explicitly defined, one will be computed
|
||||
// based on the node properties.
|
||||
func (n *Node) LongTag() string {
|
||||
return longTag(n.ShortTag())
|
||||
}
|
||||
|
||||
// ShortTag returns the short form of the YAML tag that indicates data type for
|
||||
// the node. If the Tag field isn't explicitly defined, one will be computed
|
||||
// based on the node properties.
|
||||
func (n *Node) ShortTag() string {
|
||||
if n.indicatedString() {
|
||||
return strTag
|
||||
}
|
||||
if n.Tag == "" || n.Tag == "!" {
|
||||
switch n.Kind {
|
||||
case MappingNode:
|
||||
return mapTag
|
||||
case SequenceNode:
|
||||
return seqTag
|
||||
case AliasNode:
|
||||
if n.Alias != nil {
|
||||
return n.Alias.ShortTag()
|
||||
}
|
||||
case ScalarNode:
|
||||
tag, _ := resolve("", n.Value)
|
||||
return tag
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return shortTag(n.Tag)
|
||||
}
|
||||
|
||||
func (n *Node) indicatedString() bool {
|
||||
return n.Kind == ScalarNode &&
|
||||
(shortTag(n.Tag) == strTag ||
|
||||
(n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0)
|
||||
}
|
||||
|
||||
// SetString is a convenience function that sets the node to a string value
|
||||
// and defines its style in a pleasant way depending on its content.
|
||||
func (n *Node) SetString(s string) {
|
||||
n.Kind = ScalarNode
|
||||
if utf8.ValidString(s) {
|
||||
n.Value = s
|
||||
n.Tag = strTag
|
||||
} else {
|
||||
n.Value = encodeBase64(s)
|
||||
n.Tag = binaryTag
|
||||
}
|
||||
if strings.Contains(n.Value, "\n") {
|
||||
n.Style = LiteralStyle
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Maintain a mapping of keys to structure field indexes
|
||||
|
||||
// The code in this section was copied from mgo/bson.
|
||||
|
||||
// structInfo holds details for the serialization of fields of
|
||||
// a given struct.
|
||||
type structInfo struct {
|
||||
FieldsMap map[string]fieldInfo
|
||||
FieldsList []fieldInfo
|
||||
|
||||
// InlineMap is the number of the field in the struct that
|
||||
// contains an ,inline map, or -1 if there's none.
|
||||
InlineMap int
|
||||
|
||||
// InlineUnmarshalers holds indexes to inlined fields that
|
||||
// contain unmarshaler values.
|
||||
InlineUnmarshalers [][]int
|
||||
}
|
||||
|
||||
type fieldInfo struct {
|
||||
Key string
|
||||
Num int
|
||||
OmitEmpty bool
|
||||
Flow bool
|
||||
// Id holds the unique field identifier, so we can cheaply
|
||||
// check for field duplicates without maintaining an extra map.
|
||||
Id int
|
||||
|
||||
// Inline holds the field index if the field is part of an inlined struct.
|
||||
Inline []int
|
||||
}
|
||||
|
||||
var structMap = make(map[reflect.Type]*structInfo)
|
||||
var fieldMapMutex sync.RWMutex
|
||||
var unmarshalerType reflect.Type
|
||||
|
||||
func init() {
|
||||
var v Unmarshaler
|
||||
unmarshalerType = reflect.ValueOf(&v).Elem().Type()
|
||||
}
|
||||
|
||||
func getStructInfo(st reflect.Type) (*structInfo, error) {
|
||||
fieldMapMutex.RLock()
|
||||
sinfo, found := structMap[st]
|
||||
fieldMapMutex.RUnlock()
|
||||
if found {
|
||||
return sinfo, nil
|
||||
}
|
||||
|
||||
n := st.NumField()
|
||||
fieldsMap := make(map[string]fieldInfo)
|
||||
fieldsList := make([]fieldInfo, 0, n)
|
||||
inlineMap := -1
|
||||
inlineUnmarshalers := [][]int(nil)
|
||||
for i := 0; i != n; i++ {
|
||||
field := st.Field(i)
|
||||
if field.PkgPath != "" && !field.Anonymous {
|
||||
continue // Private field
|
||||
}
|
||||
|
||||
info := fieldInfo{Num: i}
|
||||
|
||||
tag := field.Tag.Get("yaml")
|
||||
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
|
||||
tag = string(field.Tag)
|
||||
}
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
inline := false
|
||||
fields := strings.Split(tag, ",")
|
||||
if len(fields) > 1 {
|
||||
for _, flag := range fields[1:] {
|
||||
switch flag {
|
||||
case "omitempty":
|
||||
info.OmitEmpty = true
|
||||
case "flow":
|
||||
info.Flow = true
|
||||
case "inline":
|
||||
inline = true
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf("unsupported flag %q in tag %q of type %s", flag, tag, st))
|
||||
}
|
||||
}
|
||||
tag = fields[0]
|
||||
}
|
||||
|
||||
if inline {
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Map:
|
||||
if inlineMap >= 0 {
|
||||
return nil, errors.New("multiple ,inline maps in struct " + st.String())
|
||||
}
|
||||
if field.Type.Key() != reflect.TypeOf("") {
|
||||
return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String())
|
||||
}
|
||||
inlineMap = info.Num
|
||||
case reflect.Struct, reflect.Ptr:
|
||||
ftype := field.Type
|
||||
for ftype.Kind() == reflect.Ptr {
|
||||
ftype = ftype.Elem()
|
||||
}
|
||||
if ftype.Kind() != reflect.Struct {
|
||||
return nil, errors.New("option ,inline may only be used on a struct or map field")
|
||||
}
|
||||
if reflect.PtrTo(ftype).Implements(unmarshalerType) {
|
||||
inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
|
||||
} else {
|
||||
sinfo, err := getStructInfo(ftype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, index := range sinfo.InlineUnmarshalers {
|
||||
inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...))
|
||||
}
|
||||
for _, finfo := range sinfo.FieldsList {
|
||||
if _, found := fieldsMap[finfo.Key]; found {
|
||||
msg := "duplicated key '" + finfo.Key + "' in struct " + st.String()
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
if finfo.Inline == nil {
|
||||
finfo.Inline = []int{i, finfo.Num}
|
||||
} else {
|
||||
finfo.Inline = append([]int{i}, finfo.Inline...)
|
||||
}
|
||||
finfo.Id = len(fieldsList)
|
||||
fieldsMap[finfo.Key] = finfo
|
||||
fieldsList = append(fieldsList, finfo)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("option ,inline may only be used on a struct or map field")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
info.Key = tag
|
||||
} else {
|
||||
info.Key = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
if _, found = fieldsMap[info.Key]; found {
|
||||
msg := "duplicated key '" + info.Key + "' in struct " + st.String()
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
|
||||
info.Id = len(fieldsList)
|
||||
fieldsList = append(fieldsList, info)
|
||||
fieldsMap[info.Key] = info
|
||||
}
|
||||
|
||||
sinfo = &structInfo{
|
||||
FieldsMap: fieldsMap,
|
||||
FieldsList: fieldsList,
|
||||
InlineMap: inlineMap,
|
||||
InlineUnmarshalers: inlineUnmarshalers,
|
||||
}
|
||||
|
||||
fieldMapMutex.Lock()
|
||||
structMap[st] = sinfo
|
||||
fieldMapMutex.Unlock()
|
||||
return sinfo, nil
|
||||
}
|
||||
|
||||
// IsZeroer is used to check whether an object is zero to
|
||||
// determine whether it should be omitted when marshaling
|
||||
// with the omitempty flag. One notable implementation
|
||||
// is time.Time.
|
||||
type IsZeroer interface {
|
||||
IsZero() bool
|
||||
}
|
||||
|
||||
func isZero(v reflect.Value) bool {
|
||||
kind := v.Kind()
|
||||
if z, ok := v.Interface().(IsZeroer); ok {
|
||||
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return z.IsZero()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
return len(v.String()) == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
case reflect.Slice:
|
||||
return v.Len() == 0
|
||||
case reflect.Map:
|
||||
return v.Len() == 0
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Struct:
|
||||
vt := v.Type()
|
||||
for i := v.NumField() - 1; i >= 0; i-- {
|
||||
if vt.Field(i).PkgPath != "" {
|
||||
continue // Private field
|
||||
}
|
||||
if !isZero(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,805 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// 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.
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// The version directive data.
|
||||
type yaml_version_directive_t struct {
|
||||
major int8 // The major version number.
|
||||
minor int8 // The minor version number.
|
||||
}
|
||||
|
||||
// The tag directive data.
|
||||
type yaml_tag_directive_t struct {
|
||||
handle []byte // The tag handle.
|
||||
prefix []byte // The tag prefix.
|
||||
}
|
||||
|
||||
type yaml_encoding_t int
|
||||
|
||||
// The stream encoding.
|
||||
const (
|
||||
// Let the parser choose the encoding.
|
||||
yaml_ANY_ENCODING yaml_encoding_t = iota
|
||||
|
||||
yaml_UTF8_ENCODING // The default UTF-8 encoding.
|
||||
yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM.
|
||||
yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM.
|
||||
)
|
||||
|
||||
type yaml_break_t int
|
||||
|
||||
// Line break types.
|
||||
const (
|
||||
// Let the parser choose the break type.
|
||||
yaml_ANY_BREAK yaml_break_t = iota
|
||||
|
||||
yaml_CR_BREAK // Use CR for line breaks (Mac style).
|
||||
yaml_LN_BREAK // Use LN for line breaks (Unix style).
|
||||
yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style).
|
||||
)
|
||||
|
||||
type yaml_error_type_t int
|
||||
|
||||
// Many bad things could happen with the parser and emitter.
|
||||
const (
|
||||
// No error is produced.
|
||||
yaml_NO_ERROR yaml_error_type_t = iota
|
||||
|
||||
yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory.
|
||||
yaml_READER_ERROR // Cannot read or decode the input stream.
|
||||
yaml_SCANNER_ERROR // Cannot scan the input stream.
|
||||
yaml_PARSER_ERROR // Cannot parse the input stream.
|
||||
yaml_COMPOSER_ERROR // Cannot compose a YAML document.
|
||||
yaml_WRITER_ERROR // Cannot write to the output stream.
|
||||
yaml_EMITTER_ERROR // Cannot emit a YAML stream.
|
||||
)
|
||||
|
||||
// The pointer position.
|
||||
type yaml_mark_t struct {
|
||||
index int // The position index.
|
||||
line int // The position line.
|
||||
column int // The position column.
|
||||
}
|
||||
|
||||
// Node Styles
|
||||
|
||||
type yaml_style_t int8
|
||||
|
||||
type yaml_scalar_style_t yaml_style_t
|
||||
|
||||
// Scalar styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = 0
|
||||
|
||||
yaml_PLAIN_SCALAR_STYLE yaml_scalar_style_t = 1 << iota // The plain scalar style.
|
||||
yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
|
||||
yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
|
||||
yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
|
||||
yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
|
||||
)
|
||||
|
||||
type yaml_sequence_style_t yaml_style_t
|
||||
|
||||
// Sequence styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota
|
||||
|
||||
yaml_BLOCK_SEQUENCE_STYLE // The block sequence style.
|
||||
yaml_FLOW_SEQUENCE_STYLE // The flow sequence style.
|
||||
)
|
||||
|
||||
type yaml_mapping_style_t yaml_style_t
|
||||
|
||||
// Mapping styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota
|
||||
|
||||
yaml_BLOCK_MAPPING_STYLE // The block mapping style.
|
||||
yaml_FLOW_MAPPING_STYLE // The flow mapping style.
|
||||
)
|
||||
|
||||
// Tokens
|
||||
|
||||
type yaml_token_type_t int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
// An empty token.
|
||||
yaml_NO_TOKEN yaml_token_type_t = iota
|
||||
|
||||
yaml_STREAM_START_TOKEN // A STREAM-START token.
|
||||
yaml_STREAM_END_TOKEN // A STREAM-END token.
|
||||
|
||||
yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token.
|
||||
yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token.
|
||||
yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token.
|
||||
yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token.
|
||||
|
||||
yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token.
|
||||
yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token.
|
||||
yaml_BLOCK_END_TOKEN // A BLOCK-END token.
|
||||
|
||||
yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token.
|
||||
yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token.
|
||||
yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token.
|
||||
yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token.
|
||||
|
||||
yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token.
|
||||
yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token.
|
||||
yaml_KEY_TOKEN // A KEY token.
|
||||
yaml_VALUE_TOKEN // A VALUE token.
|
||||
|
||||
yaml_ALIAS_TOKEN // An ALIAS token.
|
||||
yaml_ANCHOR_TOKEN // An ANCHOR token.
|
||||
yaml_TAG_TOKEN // A TAG token.
|
||||
yaml_SCALAR_TOKEN // A SCALAR token.
|
||||
)
|
||||
|
||||
func (tt yaml_token_type_t) String() string {
|
||||
switch tt {
|
||||
case yaml_NO_TOKEN:
|
||||
return "yaml_NO_TOKEN"
|
||||
case yaml_STREAM_START_TOKEN:
|
||||
return "yaml_STREAM_START_TOKEN"
|
||||
case yaml_STREAM_END_TOKEN:
|
||||
return "yaml_STREAM_END_TOKEN"
|
||||
case yaml_VERSION_DIRECTIVE_TOKEN:
|
||||
return "yaml_VERSION_DIRECTIVE_TOKEN"
|
||||
case yaml_TAG_DIRECTIVE_TOKEN:
|
||||
return "yaml_TAG_DIRECTIVE_TOKEN"
|
||||
case yaml_DOCUMENT_START_TOKEN:
|
||||
return "yaml_DOCUMENT_START_TOKEN"
|
||||
case yaml_DOCUMENT_END_TOKEN:
|
||||
return "yaml_DOCUMENT_END_TOKEN"
|
||||
case yaml_BLOCK_SEQUENCE_START_TOKEN:
|
||||
return "yaml_BLOCK_SEQUENCE_START_TOKEN"
|
||||
case yaml_BLOCK_MAPPING_START_TOKEN:
|
||||
return "yaml_BLOCK_MAPPING_START_TOKEN"
|
||||
case yaml_BLOCK_END_TOKEN:
|
||||
return "yaml_BLOCK_END_TOKEN"
|
||||
case yaml_FLOW_SEQUENCE_START_TOKEN:
|
||||
return "yaml_FLOW_SEQUENCE_START_TOKEN"
|
||||
case yaml_FLOW_SEQUENCE_END_TOKEN:
|
||||
return "yaml_FLOW_SEQUENCE_END_TOKEN"
|
||||
case yaml_FLOW_MAPPING_START_TOKEN:
|
||||
return "yaml_FLOW_MAPPING_START_TOKEN"
|
||||
case yaml_FLOW_MAPPING_END_TOKEN:
|
||||
return "yaml_FLOW_MAPPING_END_TOKEN"
|
||||
case yaml_BLOCK_ENTRY_TOKEN:
|
||||
return "yaml_BLOCK_ENTRY_TOKEN"
|
||||
case yaml_FLOW_ENTRY_TOKEN:
|
||||
return "yaml_FLOW_ENTRY_TOKEN"
|
||||
case yaml_KEY_TOKEN:
|
||||
return "yaml_KEY_TOKEN"
|
||||
case yaml_VALUE_TOKEN:
|
||||
return "yaml_VALUE_TOKEN"
|
||||
case yaml_ALIAS_TOKEN:
|
||||
return "yaml_ALIAS_TOKEN"
|
||||
case yaml_ANCHOR_TOKEN:
|
||||
return "yaml_ANCHOR_TOKEN"
|
||||
case yaml_TAG_TOKEN:
|
||||
return "yaml_TAG_TOKEN"
|
||||
case yaml_SCALAR_TOKEN:
|
||||
return "yaml_SCALAR_TOKEN"
|
||||
}
|
||||
return "<unknown token>"
|
||||
}
|
||||
|
||||
// The token structure.
|
||||
type yaml_token_t struct {
|
||||
// The token type.
|
||||
typ yaml_token_type_t
|
||||
|
||||
// The start/end of the token.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
|
||||
// The stream encoding (for yaml_STREAM_START_TOKEN).
|
||||
encoding yaml_encoding_t
|
||||
|
||||
// The alias/anchor/scalar value or tag/tag directive handle
|
||||
// (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN).
|
||||
value []byte
|
||||
|
||||
// The tag suffix (for yaml_TAG_TOKEN).
|
||||
suffix []byte
|
||||
|
||||
// The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN).
|
||||
prefix []byte
|
||||
|
||||
// The scalar style (for yaml_SCALAR_TOKEN).
|
||||
style yaml_scalar_style_t
|
||||
|
||||
// The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN).
|
||||
major, minor int8
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
type yaml_event_type_t int8
|
||||
|
||||
// Event types.
|
||||
const (
|
||||
// An empty event.
|
||||
yaml_NO_EVENT yaml_event_type_t = iota
|
||||
|
||||
yaml_STREAM_START_EVENT // A STREAM-START event.
|
||||
yaml_STREAM_END_EVENT // A STREAM-END event.
|
||||
yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event.
|
||||
yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event.
|
||||
yaml_ALIAS_EVENT // An ALIAS event.
|
||||
yaml_SCALAR_EVENT // A SCALAR event.
|
||||
yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event.
|
||||
yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event.
|
||||
yaml_MAPPING_START_EVENT // A MAPPING-START event.
|
||||
yaml_MAPPING_END_EVENT // A MAPPING-END event.
|
||||
yaml_TAIL_COMMENT_EVENT
|
||||
)
|
||||
|
||||
var eventStrings = []string{
|
||||
yaml_NO_EVENT: "none",
|
||||
yaml_STREAM_START_EVENT: "stream start",
|
||||
yaml_STREAM_END_EVENT: "stream end",
|
||||
yaml_DOCUMENT_START_EVENT: "document start",
|
||||
yaml_DOCUMENT_END_EVENT: "document end",
|
||||
yaml_ALIAS_EVENT: "alias",
|
||||
yaml_SCALAR_EVENT: "scalar",
|
||||
yaml_SEQUENCE_START_EVENT: "sequence start",
|
||||
yaml_SEQUENCE_END_EVENT: "sequence end",
|
||||
yaml_MAPPING_START_EVENT: "mapping start",
|
||||
yaml_MAPPING_END_EVENT: "mapping end",
|
||||
yaml_TAIL_COMMENT_EVENT: "tail comment",
|
||||
}
|
||||
|
||||
func (e yaml_event_type_t) String() string {
|
||||
if e < 0 || int(e) >= len(eventStrings) {
|
||||
return fmt.Sprintf("unknown event %d", e)
|
||||
}
|
||||
return eventStrings[e]
|
||||
}
|
||||
|
||||
// The event structure.
|
||||
type yaml_event_t struct {
|
||||
|
||||
// The event type.
|
||||
typ yaml_event_type_t
|
||||
|
||||
// The start and end of the event.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
|
||||
// The document encoding (for yaml_STREAM_START_EVENT).
|
||||
encoding yaml_encoding_t
|
||||
|
||||
// The version directive (for yaml_DOCUMENT_START_EVENT).
|
||||
version_directive *yaml_version_directive_t
|
||||
|
||||
// The list of tag directives (for yaml_DOCUMENT_START_EVENT).
|
||||
tag_directives []yaml_tag_directive_t
|
||||
|
||||
// The comments
|
||||
head_comment []byte
|
||||
line_comment []byte
|
||||
foot_comment []byte
|
||||
tail_comment []byte
|
||||
|
||||
// The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT).
|
||||
anchor []byte
|
||||
|
||||
// The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||
tag []byte
|
||||
|
||||
// The scalar value (for yaml_SCALAR_EVENT).
|
||||
value []byte
|
||||
|
||||
// Is the document start/end indicator implicit, or the tag optional?
|
||||
// (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT).
|
||||
implicit bool
|
||||
|
||||
// Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT).
|
||||
quoted_implicit bool
|
||||
|
||||
// The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||
style yaml_style_t
|
||||
}
|
||||
|
||||
func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) }
|
||||
func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) }
|
||||
func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) }
|
||||
|
||||
// Nodes
|
||||
|
||||
const (
|
||||
yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null.
|
||||
yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false.
|
||||
yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values.
|
||||
yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values.
|
||||
yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values.
|
||||
yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values.
|
||||
|
||||
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
|
||||
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
|
||||
|
||||
// Not in original libyaml.
|
||||
yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
|
||||
yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
|
||||
|
||||
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
|
||||
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
|
||||
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.
|
||||
)
|
||||
|
||||
type yaml_node_type_t int
|
||||
|
||||
// Node types.
|
||||
const (
|
||||
// An empty node.
|
||||
yaml_NO_NODE yaml_node_type_t = iota
|
||||
|
||||
yaml_SCALAR_NODE // A scalar node.
|
||||
yaml_SEQUENCE_NODE // A sequence node.
|
||||
yaml_MAPPING_NODE // A mapping node.
|
||||
)
|
||||
|
||||
// An element of a sequence node.
|
||||
type yaml_node_item_t int
|
||||
|
||||
// An element of a mapping node.
|
||||
type yaml_node_pair_t struct {
|
||||
key int // The key of the element.
|
||||
value int // The value of the element.
|
||||
}
|
||||
|
||||
// The node structure.
|
||||
type yaml_node_t struct {
|
||||
typ yaml_node_type_t // The node type.
|
||||
tag []byte // The node tag.
|
||||
|
||||
// The node data.
|
||||
|
||||
// The scalar parameters (for yaml_SCALAR_NODE).
|
||||
scalar struct {
|
||||
value []byte // The scalar value.
|
||||
length int // The length of the scalar value.
|
||||
style yaml_scalar_style_t // The scalar style.
|
||||
}
|
||||
|
||||
// The sequence parameters (for YAML_SEQUENCE_NODE).
|
||||
sequence struct {
|
||||
items_data []yaml_node_item_t // The stack of sequence items.
|
||||
style yaml_sequence_style_t // The sequence style.
|
||||
}
|
||||
|
||||
// The mapping parameters (for yaml_MAPPING_NODE).
|
||||
mapping struct {
|
||||
pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value).
|
||||
pairs_start *yaml_node_pair_t // The beginning of the stack.
|
||||
pairs_end *yaml_node_pair_t // The end of the stack.
|
||||
pairs_top *yaml_node_pair_t // The top of the stack.
|
||||
style yaml_mapping_style_t // The mapping style.
|
||||
}
|
||||
|
||||
start_mark yaml_mark_t // The beginning of the node.
|
||||
end_mark yaml_mark_t // The end of the node.
|
||||
|
||||
}
|
||||
|
||||
// The document structure.
|
||||
type yaml_document_t struct {
|
||||
|
||||
// The document nodes.
|
||||
nodes []yaml_node_t
|
||||
|
||||
// The version directive.
|
||||
version_directive *yaml_version_directive_t
|
||||
|
||||
// The list of tag directives.
|
||||
tag_directives_data []yaml_tag_directive_t
|
||||
tag_directives_start int // The beginning of the tag directives list.
|
||||
tag_directives_end int // The end of the tag directives list.
|
||||
|
||||
start_implicit int // Is the document start indicator implicit?
|
||||
end_implicit int // Is the document end indicator implicit?
|
||||
|
||||
// The start/end of the document.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
}
|
||||
|
||||
// The prototype of a read handler.
|
||||
//
|
||||
// The read handler is called when the parser needs to read more bytes from the
|
||||
// source. The handler should write not more than size bytes to the buffer.
|
||||
// The number of written bytes should be set to the size_read variable.
|
||||
//
|
||||
// [in,out] data A pointer to an application data specified by
|
||||
// yaml_parser_set_input().
|
||||
// [out] buffer The buffer to write the data from the source.
|
||||
// [in] size The size of the buffer.
|
||||
// [out] size_read The actual number of bytes read from the source.
|
||||
//
|
||||
// On success, the handler should return 1. If the handler failed,
|
||||
// the returned value should be 0. On EOF, the handler should set the
|
||||
// size_read to 0 and return 1.
|
||||
type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error)
|
||||
|
||||
// This structure holds information about a potential simple key.
|
||||
type yaml_simple_key_t struct {
|
||||
possible bool // Is a simple key possible?
|
||||
required bool // Is a simple key required?
|
||||
token_number int // The number of the token.
|
||||
mark yaml_mark_t // The position mark.
|
||||
}
|
||||
|
||||
// The states of the parser.
|
||||
type yaml_parser_state_t int
|
||||
|
||||
const (
|
||||
yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota
|
||||
|
||||
yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document.
|
||||
yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START.
|
||||
yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||
yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||
yaml_PARSE_BLOCK_NODE_STATE // Expect a block node.
|
||||
yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence.
|
||||
yaml_PARSE_FLOW_NODE_STATE // Expect a flow node.
|
||||
yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence.
|
||||
yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence.
|
||||
yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence.
|
||||
yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||
yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key.
|
||||
yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value.
|
||||
yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry.
|
||||
yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping.
|
||||
yaml_PARSE_END_STATE // Expect nothing.
|
||||
)
|
||||
|
||||
func (ps yaml_parser_state_t) String() string {
|
||||
switch ps {
|
||||
case yaml_PARSE_STREAM_START_STATE:
|
||||
return "yaml_PARSE_STREAM_START_STATE"
|
||||
case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE:
|
||||
return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE"
|
||||
case yaml_PARSE_DOCUMENT_START_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_START_STATE"
|
||||
case yaml_PARSE_DOCUMENT_CONTENT_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_CONTENT_STATE"
|
||||
case yaml_PARSE_DOCUMENT_END_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_END_STATE"
|
||||
case yaml_PARSE_BLOCK_NODE_STATE:
|
||||
return "yaml_PARSE_BLOCK_NODE_STATE"
|
||||
case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE:
|
||||
return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE"
|
||||
case yaml_PARSE_FLOW_NODE_STATE:
|
||||
return "yaml_PARSE_FLOW_NODE_STATE"
|
||||
case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE:
|
||||
return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE"
|
||||
case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE"
|
||||
case yaml_PARSE_END_STATE:
|
||||
return "yaml_PARSE_END_STATE"
|
||||
}
|
||||
return "<unknown parser state>"
|
||||
}
|
||||
|
||||
// This structure holds aliases data.
|
||||
type yaml_alias_data_t struct {
|
||||
anchor []byte // The anchor.
|
||||
index int // The node id.
|
||||
mark yaml_mark_t // The anchor mark.
|
||||
}
|
||||
|
||||
// The parser structure.
|
||||
//
|
||||
// All members are internal. Manage the structure using the
|
||||
// yaml_parser_ family of functions.
|
||||
type yaml_parser_t struct {
|
||||
|
||||
// Error handling
|
||||
|
||||
error yaml_error_type_t // Error type.
|
||||
|
||||
problem string // Error description.
|
||||
|
||||
// The byte about which the problem occurred.
|
||||
problem_offset int
|
||||
problem_value int
|
||||
problem_mark yaml_mark_t
|
||||
|
||||
// The error context.
|
||||
context string
|
||||
context_mark yaml_mark_t
|
||||
|
||||
// Reader stuff
|
||||
|
||||
read_handler yaml_read_handler_t // Read handler.
|
||||
|
||||
input_reader io.Reader // File input data.
|
||||
input []byte // String input data.
|
||||
input_pos int
|
||||
|
||||
eof bool // EOF flag
|
||||
|
||||
buffer []byte // The working buffer.
|
||||
buffer_pos int // The current position of the buffer.
|
||||
|
||||
unread int // The number of unread characters in the buffer.
|
||||
|
||||
newlines int // The number of line breaks since last non-break/non-blank character
|
||||
|
||||
raw_buffer []byte // The raw buffer.
|
||||
raw_buffer_pos int // The current position of the buffer.
|
||||
|
||||
encoding yaml_encoding_t // The input encoding.
|
||||
|
||||
offset int // The offset of the current position (in bytes).
|
||||
mark yaml_mark_t // The mark of the current position.
|
||||
|
||||
// Comments
|
||||
|
||||
head_comment []byte // The current head comments
|
||||
line_comment []byte // The current line comments
|
||||
foot_comment []byte // The current foot comments
|
||||
tail_comment []byte // Foot comment that happens at the end of a block.
|
||||
stem_comment []byte // Comment in item preceding a nested structure (list inside list item, etc)
|
||||
|
||||
comments []yaml_comment_t // The folded comments for all parsed tokens
|
||||
comments_head int
|
||||
|
||||
// Scanner stuff
|
||||
|
||||
stream_start_produced bool // Have we started to scan the input stream?
|
||||
stream_end_produced bool // Have we reached the end of the input stream?
|
||||
|
||||
flow_level int // The number of unclosed '[' and '{' indicators.
|
||||
|
||||
tokens []yaml_token_t // The tokens queue.
|
||||
tokens_head int // The head of the tokens queue.
|
||||
tokens_parsed int // The number of tokens fetched from the queue.
|
||||
token_available bool // Does the tokens queue contain a token ready for dequeueing.
|
||||
|
||||
indent int // The current indentation level.
|
||||
indents []int // The indentation levels stack.
|
||||
|
||||
simple_key_allowed bool // May a simple key occur at the current position?
|
||||
simple_keys []yaml_simple_key_t // The stack of simple keys.
|
||||
simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number
|
||||
|
||||
// Parser stuff
|
||||
|
||||
state yaml_parser_state_t // The current parser state.
|
||||
states []yaml_parser_state_t // The parser states stack.
|
||||
marks []yaml_mark_t // The stack of marks.
|
||||
tag_directives []yaml_tag_directive_t // The list of TAG directives.
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
aliases []yaml_alias_data_t // The alias data.
|
||||
|
||||
document *yaml_document_t // The currently parsed document.
|
||||
}
|
||||
|
||||
type yaml_comment_t struct {
|
||||
|
||||
scan_mark yaml_mark_t // Position where scanning for comments started
|
||||
token_mark yaml_mark_t // Position after which tokens will be associated with this comment
|
||||
start_mark yaml_mark_t // Position of '#' comment mark
|
||||
end_mark yaml_mark_t // Position where comment terminated
|
||||
|
||||
head []byte
|
||||
line []byte
|
||||
foot []byte
|
||||
}
|
||||
|
||||
// Emitter Definitions
|
||||
|
||||
// The prototype of a write handler.
|
||||
//
|
||||
// The write handler is called when the emitter needs to flush the accumulated
|
||||
// characters to the output. The handler should write @a size bytes of the
|
||||
// @a buffer to the output.
|
||||
//
|
||||
// @param[in,out] data A pointer to an application data specified by
|
||||
// yaml_emitter_set_output().
|
||||
// @param[in] buffer The buffer with bytes to be written.
|
||||
// @param[in] size The size of the buffer.
|
||||
//
|
||||
// @returns On success, the handler should return @c 1. If the handler failed,
|
||||
// the returned value should be @c 0.
|
||||
//
|
||||
type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error
|
||||
|
||||
type yaml_emitter_state_t int
|
||||
|
||||
// The emitter states.
|
||||
const (
|
||||
// Expect STREAM-START.
|
||||
yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota
|
||||
|
||||
yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END.
|
||||
yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END.
|
||||
yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||
yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence.
|
||||
yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE // Expect the next item of a flow sequence, with the comma already written out
|
||||
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence.
|
||||
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE // Expect the next key of a flow mapping, with the comma already written out
|
||||
yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||
yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence.
|
||||
yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence.
|
||||
yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping.
|
||||
yaml_EMIT_END_STATE // Expect nothing.
|
||||
)
|
||||
|
||||
// The emitter structure.
|
||||
//
|
||||
// All members are internal. Manage the structure using the @c yaml_emitter_
|
||||
// family of functions.
|
||||
type yaml_emitter_t struct {
|
||||
|
||||
// Error handling
|
||||
|
||||
error yaml_error_type_t // Error type.
|
||||
problem string // Error description.
|
||||
|
||||
// Writer stuff
|
||||
|
||||
write_handler yaml_write_handler_t // Write handler.
|
||||
|
||||
output_buffer *[]byte // String output data.
|
||||
output_writer io.Writer // File output data.
|
||||
|
||||
buffer []byte // The working buffer.
|
||||
buffer_pos int // The current position of the buffer.
|
||||
|
||||
raw_buffer []byte // The raw buffer.
|
||||
raw_buffer_pos int // The current position of the buffer.
|
||||
|
||||
encoding yaml_encoding_t // The stream encoding.
|
||||
|
||||
// Emitter stuff
|
||||
|
||||
canonical bool // If the output is in the canonical style?
|
||||
best_indent int // The number of indentation spaces.
|
||||
best_width int // The preferred width of the output lines.
|
||||
unicode bool // Allow unescaped non-ASCII characters?
|
||||
line_break yaml_break_t // The preferred line break.
|
||||
|
||||
state yaml_emitter_state_t // The current emitter state.
|
||||
states []yaml_emitter_state_t // The stack of states.
|
||||
|
||||
events []yaml_event_t // The event queue.
|
||||
events_head int // The head of the event queue.
|
||||
|
||||
indents []int // The stack of indentation levels.
|
||||
|
||||
tag_directives []yaml_tag_directive_t // The list of tag directives.
|
||||
|
||||
indent int // The current indentation level.
|
||||
|
||||
flow_level int // The current flow level.
|
||||
|
||||
root_context bool // Is it the document root context?
|
||||
sequence_context bool // Is it a sequence context?
|
||||
mapping_context bool // Is it a mapping context?
|
||||
simple_key_context bool // Is it a simple mapping key context?
|
||||
|
||||
line int // The current line.
|
||||
column int // The current column.
|
||||
whitespace bool // If the last character was a whitespace?
|
||||
indention bool // If the last character was an indentation character (' ', '-', '?', ':')?
|
||||
open_ended bool // If an explicit document end is required?
|
||||
|
||||
space_above bool // Is there's an empty line above?
|
||||
foot_indent int // The indent used to write the foot comment above, or -1 if none.
|
||||
|
||||
// Anchor analysis.
|
||||
anchor_data struct {
|
||||
anchor []byte // The anchor value.
|
||||
alias bool // Is it an alias?
|
||||
}
|
||||
|
||||
// Tag analysis.
|
||||
tag_data struct {
|
||||
handle []byte // The tag handle.
|
||||
suffix []byte // The tag suffix.
|
||||
}
|
||||
|
||||
// Scalar analysis.
|
||||
scalar_data struct {
|
||||
value []byte // The scalar value.
|
||||
multiline bool // Does the scalar contain line breaks?
|
||||
flow_plain_allowed bool // Can the scalar be expessed in the flow plain style?
|
||||
block_plain_allowed bool // Can the scalar be expressed in the block plain style?
|
||||
single_quoted_allowed bool // Can the scalar be expressed in the single quoted style?
|
||||
block_allowed bool // Can the scalar be expressed in the literal or folded styles?
|
||||
style yaml_scalar_style_t // The output style.
|
||||
}
|
||||
|
||||
// Comments
|
||||
head_comment []byte
|
||||
line_comment []byte
|
||||
foot_comment []byte
|
||||
tail_comment []byte
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
opened bool // If the stream was already opened?
|
||||
closed bool // If the stream was already closed?
|
||||
|
||||
// The information associated with the document nodes.
|
||||
anchors *struct {
|
||||
references int // The number of references.
|
||||
anchor int // The anchor id.
|
||||
serialized bool // If the node has been emitted?
|
||||
}
|
||||
|
||||
last_anchor_id int // The last assigned anchor id.
|
||||
|
||||
document *yaml_document_t // The currently emitted document.
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
//
|
||||
// Copyright (c) 2011-2019 Canonical Ltd
|
||||
// Copyright (c) 2006-2010 Kirill Simonov
|
||||
//
|
||||
// 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.
|
||||
|
||||
package yaml
|
||||
|
||||
const (
|
||||
// The size of the input raw buffer.
|
||||
input_raw_buffer_size = 512
|
||||
|
||||
// The size of the input buffer.
|
||||
// It should be possible to decode the whole raw buffer.
|
||||
input_buffer_size = input_raw_buffer_size * 3
|
||||
|
||||
// The size of the output buffer.
|
||||
output_buffer_size = 128
|
||||
|
||||
// The size of the output raw buffer.
|
||||
// It should be possible to encode the whole output buffer.
|
||||
output_raw_buffer_size = (output_buffer_size*2 + 2)
|
||||
|
||||
// The size of other stacks and queues.
|
||||
initial_stack_size = 16
|
||||
initial_queue_size = 16
|
||||
initial_string_size = 16
|
||||
)
|
||||
|
||||
// Check if the character at the specified position is an alphabetical
|
||||
// character, a digit, '_', or '-'.
|
||||
func is_alpha(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a digit.
|
||||
func is_digit(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9'
|
||||
}
|
||||
|
||||
// Get the value of a digit.
|
||||
func as_digit(b []byte, i int) int {
|
||||
return int(b[i]) - '0'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a hex-digit.
|
||||
func is_hex(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f'
|
||||
}
|
||||
|
||||
// Get the value of a hex-digit.
|
||||
func as_hex(b []byte, i int) int {
|
||||
bi := b[i]
|
||||
if bi >= 'A' && bi <= 'F' {
|
||||
return int(bi) - 'A' + 10
|
||||
}
|
||||
if bi >= 'a' && bi <= 'f' {
|
||||
return int(bi) - 'a' + 10
|
||||
}
|
||||
return int(bi) - '0'
|
||||
}
|
||||
|
||||
// Check if the character is ASCII.
|
||||
func is_ascii(b []byte, i int) bool {
|
||||
return b[i] <= 0x7F
|
||||
}
|
||||
|
||||
// Check if the character at the start of the buffer can be printed unescaped.
|
||||
func is_printable(b []byte, i int) bool {
|
||||
return ((b[i] == 0x0A) || // . == #x0A
|
||||
(b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E
|
||||
(b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF
|
||||
(b[i] > 0xC2 && b[i] < 0xED) ||
|
||||
(b[i] == 0xED && b[i+1] < 0xA0) ||
|
||||
(b[i] == 0xEE) ||
|
||||
(b[i] == 0xEF && // #xE000 <= . <= #xFFFD
|
||||
!(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF
|
||||
!(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF))))
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is NUL.
|
||||
func is_z(b []byte, i int) bool {
|
||||
return b[i] == 0x00
|
||||
}
|
||||
|
||||
// Check if the beginning of the buffer is a BOM.
|
||||
func is_bom(b []byte, i int) bool {
|
||||
return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is space.
|
||||
func is_space(b []byte, i int) bool {
|
||||
return b[i] == ' '
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is tab.
|
||||
func is_tab(b []byte, i int) bool {
|
||||
return b[i] == '\t'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is blank (space or tab).
|
||||
func is_blank(b []byte, i int) bool {
|
||||
//return is_space(b, i) || is_tab(b, i)
|
||||
return b[i] == ' ' || b[i] == '\t'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a line break.
|
||||
func is_break(b []byte, i int) bool {
|
||||
return (b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029)
|
||||
}
|
||||
|
||||
func is_crlf(b []byte, i int) bool {
|
||||
return b[i] == '\r' && b[i+1] == '\n'
|
||||
}
|
||||
|
||||
// Check if the character is a line break or NUL.
|
||||
func is_breakz(b []byte, i int) bool {
|
||||
//return is_break(b, i) || is_z(b, i)
|
||||
return (
|
||||
// is_break:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
// is_z:
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Check if the character is a line break, space, or NUL.
|
||||
func is_spacez(b []byte, i int) bool {
|
||||
//return is_space(b, i) || is_breakz(b, i)
|
||||
return (
|
||||
// is_space:
|
||||
b[i] == ' ' ||
|
||||
// is_breakz:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Check if the character is a line break, space, tab, or NUL.
|
||||
func is_blankz(b []byte, i int) bool {
|
||||
//return is_blank(b, i) || is_breakz(b, i)
|
||||
return (
|
||||
// is_blank:
|
||||
b[i] == ' ' || b[i] == '\t' ||
|
||||
// is_breakz:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Determine the width of the character.
|
||||
func width(b byte) int {
|
||||
// Don't replace these by a switch without first
|
||||
// confirming that it is being inlined.
|
||||
if b&0x80 == 0x00 {
|
||||
return 1
|
||||
}
|
||||
if b&0xE0 == 0xC0 {
|
||||
return 2
|
||||
}
|
||||
if b&0xF0 == 0xE0 {
|
||||
return 3
|
||||
}
|
||||
if b&0xF8 == 0xF0 {
|
||||
return 4
|
||||
}
|
||||
return 0
|
||||
|
||||
}
|
|
@ -471,7 +471,7 @@ github.com/spf13/pflag
|
|||
## explicit
|
||||
# github.com/stretchr/objx v0.2.0
|
||||
github.com/stretchr/objx
|
||||
# github.com/stretchr/testify v1.5.1
|
||||
# github.com/stretchr/testify v1.6.1
|
||||
## explicit
|
||||
github.com/stretchr/testify/assert
|
||||
github.com/stretchr/testify/mock
|
||||
|
@ -500,6 +500,14 @@ github.com/theupdateframework/notary/tuf/data
|
|||
github.com/theupdateframework/notary/tuf/signed
|
||||
github.com/theupdateframework/notary/tuf/utils
|
||||
github.com/theupdateframework/notary/tuf/validation
|
||||
# github.com/vmihailenco/msgpack/v5 v5.0.0-rc.2
|
||||
## explicit
|
||||
github.com/vmihailenco/msgpack/v5
|
||||
github.com/vmihailenco/msgpack/v5/msgpcode
|
||||
# github.com/vmihailenco/tagparser v0.1.2
|
||||
github.com/vmihailenco/tagparser
|
||||
github.com/vmihailenco/tagparser/internal
|
||||
github.com/vmihailenco/tagparser/internal/parser
|
||||
# github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f
|
||||
github.com/xeipuuv/gojsonpointer
|
||||
# github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415
|
||||
|
@ -675,6 +683,8 @@ gopkg.in/square/go-jose.v2/json
|
|||
# gopkg.in/yaml.v2 v2.3.0
|
||||
## explicit
|
||||
gopkg.in/yaml.v2
|
||||
# gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
gopkg.in/yaml.v3
|
||||
# helm.sh/helm/v3 v3.1.1
|
||||
## explicit
|
||||
helm.sh/helm/v3/cmd/helm/search
|
||||
|
|
Loading…
Reference in New Issue