Update Godeps to aws-sdk-go v1.0.2.

This commit is contained in:
Trevor Pounds 2015-11-24 13:01:17 -06:00
parent 9c5ede63fc
commit bd9adaf8eb
734 changed files with 15264 additions and 1080 deletions

59
Godeps/Godeps.json generated
View File

@ -49,53 +49,58 @@
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/autoscaling",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/elb",
"Comment": "v0.9.16-3-g4944a94",
"Rev": "4944a94a3b092d1dad8a964415700a70f55e580a"
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/beorn7/perks/quantile",
@ -411,6 +416,11 @@
"ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
},
{
"ImportPath": "github.com/go-ini/ini",
"Comment": "v0-54-g2e44421",
"Rev": "2e44421e256d82ebbf3d4d4fcabe8930b905eff3"
},
{
"ImportPath": "github.com/godbus/dbus",
"Comment": "v3",
@ -553,6 +563,11 @@
"Comment": "v0.8.8",
"Rev": "afde71eb1740fd763ab9450e1f700ba0e53c36d0"
},
{
"ImportPath": "github.com/jmespath/go-jmespath",
"Comment": "0.2.2",
"Rev": "3433f3ea46d9f8019119e7dd41274e112a2359a9"
},
{
"ImportPath": "github.com/jonboulle/clockwork",
"Rev": "3f831b65b61282ba6bece21b91beea2edc4c887a"
@ -733,10 +748,6 @@
"ImportPath": "github.com/ugorji/go/codec",
"Rev": "f1f1a805ed361a0e078bb537e4ea78cd37dcf065"
},
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
},
{
"ImportPath": "github.com/vishvananda/netlink",
"Rev": "1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270"

View File

@ -57,16 +57,13 @@ func rcopy(dst, src reflect.Value, root bool) {
}
}
case reflect.Struct:
if !root {
dst.Set(reflect.New(src.Type()).Elem())
}
t := dst.Type()
for i := 0; i < t.NumField(); i++ {
name := t.Field(i).Name
srcval := src.FieldByName(name)
if srcval.IsValid() {
rcopy(dst.FieldByName(name), srcval, false)
srcVal := src.FieldByName(name)
dstVal := dst.FieldByName(name)
if srcVal.IsValid() && dstVal.CanSet() {
rcopy(dstVal, srcVal, false)
}
}
case reflect.Slice:

View File

@ -0,0 +1,27 @@
package awsutil
import (
"reflect"
)
// DeepEqual returns if the two values are deeply equal like reflect.DeepEqual.
// In addition to this, this method will also dereference the input values if
// possible so the DeepEqual performed will not fail if one parameter is a
// pointer and the other is not.
//
// DeepEqual will not perform indirection of nested values of the input parameters.
func DeepEqual(a, b interface{}) bool {
ra := reflect.Indirect(reflect.ValueOf(a))
rb := reflect.Indirect(reflect.ValueOf(b))
if raValid, rbValid := ra.IsValid(), rb.IsValid(); !raValid && !rbValid {
// If the elements are both nil, and of the same type the are equal
// If they are of different types they are not equal
return reflect.TypeOf(a) == reflect.TypeOf(b)
} else if raValid != rbValid {
// Both values must be valid to be equal
return false
}
return reflect.DeepEqual(ra.Interface(), rb.Interface())
}

View File

@ -5,18 +5,20 @@ import (
"regexp"
"strconv"
"strings"
"github.com/jmespath/go-jmespath"
)
var indexRe = regexp.MustCompile(`(.+)\[(-?\d+)?\]$`)
// rValuesAtPath returns a slice of values found in value v. The values
// in v are explored recursively so all nested values are collected.
func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool) []reflect.Value {
func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTerm bool) []reflect.Value {
pathparts := strings.Split(path, "||")
if len(pathparts) > 1 {
for _, pathpart := range pathparts {
vals := rValuesAtPath(v, pathpart, create, caseSensitive)
if vals != nil && len(vals) > 0 {
vals := rValuesAtPath(v, pathpart, createPath, caseSensitive, nilTerm)
if len(vals) > 0 {
return vals
}
}
@ -74,7 +76,16 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool)
return false
})
if create && value.Kind() == reflect.Ptr && value.IsNil() {
if nilTerm && value.Kind() == reflect.Ptr && len(components[1:]) == 0 {
if !value.IsNil() {
value.Set(reflect.Zero(value.Type()))
}
return []reflect.Value{value}
}
if createPath && value.Kind() == reflect.Ptr && value.IsNil() {
// TODO if the value is the terminus it should not be created
// if the value to be set to its position is nil.
value.Set(reflect.New(value.Type().Elem()))
value = value.Elem()
} else {
@ -82,7 +93,7 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool)
}
if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
if !create && value.IsNil() {
if !createPath && value.IsNil() {
value = reflect.ValueOf(nil)
}
}
@ -114,7 +125,7 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool)
// pull out index
i := int(*index)
if i >= value.Len() { // check out of bounds
if create {
if createPath {
// TODO resize slice
} else {
continue
@ -125,7 +136,7 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool)
value = reflect.Indirect(value.Index(i))
if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
if !create && value.IsNil() {
if !createPath && value.IsNil() {
value = reflect.ValueOf(nil)
}
}
@ -142,46 +153,70 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool)
return values
}
// ValuesAtPath returns a list of objects at the lexical path inside of a structure
func ValuesAtPath(i interface{}, path string) []interface{} {
if rvals := rValuesAtPath(i, path, false, true); rvals != nil {
vals := make([]interface{}, len(rvals))
for i, rval := range rvals {
vals[i] = rval.Interface()
// ValuesAtPath returns a list of values at the case insensitive lexical
// path inside of a structure.
func ValuesAtPath(i interface{}, path string) ([]interface{}, error) {
result, err := jmespath.Search(path, i)
if err != nil {
return nil, err
}
return vals
v := reflect.ValueOf(result)
if !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) {
return nil, nil
}
return nil
if s, ok := result.([]interface{}); ok {
return s, err
}
if v.Kind() == reflect.Map && v.Len() == 0 {
return nil, nil
}
if v.Kind() == reflect.Slice {
out := make([]interface{}, v.Len())
for i := 0; i < v.Len(); i++ {
out[i] = v.Index(i).Interface()
}
return out, nil
}
return []interface{}{result}, nil
}
// ValuesAtAnyPath returns a list of objects at the case-insensitive lexical
// path inside of a structure
func ValuesAtAnyPath(i interface{}, path string) []interface{} {
if rvals := rValuesAtPath(i, path, false, false); rvals != nil {
vals := make([]interface{}, len(rvals))
for i, rval := range rvals {
vals[i] = rval.Interface()
}
return vals
}
return nil
}
// SetValueAtPath sets an object at the lexical path inside of a structure
// SetValueAtPath sets a value at the case insensitive lexical path inside
// of a structure.
func SetValueAtPath(i interface{}, path string, v interface{}) {
if rvals := rValuesAtPath(i, path, true, true); rvals != nil {
if rvals := rValuesAtPath(i, path, true, false, v == nil); rvals != nil {
for _, rval := range rvals {
rval.Set(reflect.ValueOf(v))
if rval.Kind() == reflect.Ptr && rval.IsNil() {
continue
}
setValue(rval, v)
}
}
}
// SetValueAtAnyPath sets an object at the case insensitive lexical path inside
// of a structure
func SetValueAtAnyPath(i interface{}, path string, v interface{}) {
if rvals := rValuesAtPath(i, path, true, false); rvals != nil {
for _, rval := range rvals {
rval.Set(reflect.ValueOf(v))
func setValue(dstVal reflect.Value, src interface{}) {
if dstVal.Kind() == reflect.Ptr {
dstVal = reflect.Indirect(dstVal)
}
srcVal := reflect.ValueOf(src)
if !srcVal.IsValid() { // src is literal nil
if dstVal.CanAddr() {
// Convert to pointer so that pointer's value can be nil'ed
// dstVal = dstVal.Addr()
}
dstVal.Set(reflect.Zero(dstVal.Type()))
} else if srcVal.Kind() == reflect.Ptr {
if srcVal.IsNil() {
srcVal = reflect.Zero(dstVal.Type())
} else {
srcVal = reflect.ValueOf(src).Elem()
}
dstVal.Set(srcVal)
} else {
dstVal.Set(srcVal)
}
}

View File

@ -0,0 +1,89 @@
package awsutil
import (
"bytes"
"fmt"
"reflect"
"strings"
)
// StringValue returns the string representation of a value.
func StringValue(i interface{}) string {
var buf bytes.Buffer
stringValue(reflect.ValueOf(i), 0, &buf)
return buf.String()
}
func stringValue(v reflect.Value, indent int, buf *bytes.Buffer) {
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
switch v.Kind() {
case reflect.Struct:
buf.WriteString("{\n")
names := []string{}
for i := 0; i < v.Type().NumField(); i++ {
name := v.Type().Field(i).Name
f := v.Field(i)
if name[0:1] == strings.ToLower(name[0:1]) {
continue // ignore unexported fields
}
if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice) && f.IsNil() {
continue // ignore unset fields
}
names = append(names, name)
}
for i, n := range names {
val := v.FieldByName(n)
buf.WriteString(strings.Repeat(" ", indent+2))
buf.WriteString(n + ": ")
stringValue(val, indent+2, buf)
if i < len(names)-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
case reflect.Slice:
nl, id, id2 := "", "", ""
if v.Len() > 3 {
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
}
buf.WriteString("[" + nl)
for i := 0; i < v.Len(); i++ {
buf.WriteString(id2)
stringValue(v.Index(i), indent+2, buf)
if i < v.Len()-1 {
buf.WriteString("," + nl)
}
}
buf.WriteString(nl + id + "]")
case reflect.Map:
buf.WriteString("{\n")
for i, k := range v.MapKeys() {
buf.WriteString(strings.Repeat(" ", indent+2))
buf.WriteString(k.String() + ": ")
stringValue(v.MapIndex(k), indent+2, buf)
if i < v.Len()-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
default:
format := "%v"
switch v.Interface().(type) {
case string:
format = "%q"
}
fmt.Fprintf(buf, format, v.Interface())
}
}

View File

@ -0,0 +1,120 @@
package client
import (
"fmt"
"io/ioutil"
"net/http/httputil"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
)
// A Config provides configuration to a service client instance.
type Config struct {
Config *aws.Config
Handlers request.Handlers
Endpoint, SigningRegion string
}
// ConfigProvider provides a generic way for a service client to receive
// the ClientConfig without circular dependencies.
type ConfigProvider interface {
ClientConfig(serviceName string, cfgs ...*aws.Config) Config
}
// A Client implements the base client request and response handling
// used by all service clients.
type Client struct {
request.Retryer
metadata.ClientInfo
Config aws.Config
Handlers request.Handlers
}
// New will return a pointer to a new initialized service client.
func New(cfg aws.Config, info metadata.ClientInfo, handlers request.Handlers, options ...func(*Client)) *Client {
svc := &Client{
Config: cfg,
ClientInfo: info,
Handlers: handlers,
}
switch retryer, ok := cfg.Retryer.(request.Retryer); {
case ok:
svc.Retryer = retryer
case cfg.Retryer != nil && cfg.Logger != nil:
s := fmt.Sprintf("WARNING: %T does not implement request.Retryer; using DefaultRetryer instead", cfg.Retryer)
cfg.Logger.Log(s)
fallthrough
default:
maxRetries := aws.IntValue(cfg.MaxRetries)
if cfg.MaxRetries == nil || maxRetries == aws.UseServiceDefaultRetries {
maxRetries = 3
}
svc.Retryer = DefaultRetryer{NumMaxRetries: maxRetries}
}
svc.AddDebugHandlers()
for _, option := range options {
option(svc)
}
return svc
}
// NewRequest returns a new Request pointer for the service API
// operation and parameters.
func (c *Client) NewRequest(operation *request.Operation, params interface{}, data interface{}) *request.Request {
return request.New(c.Config, c.ClientInfo, c.Handlers, c.Retryer, operation, params, data)
}
// AddDebugHandlers injects debug logging handlers into the service to log request
// debug information.
func (c *Client) AddDebugHandlers() {
if !c.Config.LogLevel.AtLeast(aws.LogDebug) {
return
}
c.Handlers.Send.PushFront(logRequest)
c.Handlers.Send.PushBack(logResponse)
}
const logReqMsg = `DEBUG: Request %s/%s Details:
---[ REQUEST POST-SIGN ]-----------------------------
%s
-----------------------------------------------------`
func logRequest(r *request.Request) {
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
dumpedBody, _ := httputil.DumpRequestOut(r.HTTPRequest, logBody)
if logBody {
// Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's
// Body as a NoOpCloser and will not be reset after read by the HTTP
// client reader.
r.Body.Seek(r.BodyStart, 0)
r.HTTPRequest.Body = ioutil.NopCloser(r.Body)
}
r.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.ClientInfo.ServiceName, r.Operation.Name, string(dumpedBody)))
}
const logRespMsg = `DEBUG: Response %s/%s Details:
---[ RESPONSE ]--------------------------------------
%s
-----------------------------------------------------`
func logResponse(r *request.Request) {
var msg = "no reponse data"
if r.HTTPResponse != nil {
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, logBody)
msg = string(dumpedBody)
} else if r.Error != nil {
msg = r.Error.Error()
}
r.Config.Logger.Log(fmt.Sprintf(logRespMsg, r.ClientInfo.ServiceName, r.Operation.Name, msg))
}

View File

@ -1,11 +1,10 @@
package service
package client
import (
"math"
"math/rand"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
)
@ -22,16 +21,13 @@ import (
// // This implementation always has 100 max retries
// func (d retryer) MaxRetries() uint { return 100 }
type DefaultRetryer struct {
*Service
NumMaxRetries int
}
// MaxRetries returns the number of maximum returns the service will use to make
// an individual API request.
func (d DefaultRetryer) MaxRetries() uint {
if aws.IntValue(d.Service.Config.MaxRetries) < 0 {
return d.DefaultMaxRetries
}
return uint(aws.IntValue(d.Service.Config.MaxRetries))
func (d DefaultRetryer) MaxRetries() int {
return d.NumMaxRetries
}
// RetryRules returns the delay duration before retrying this request again

View File

@ -0,0 +1,12 @@
package metadata
// ClientInfo wraps immutable data from the client.Client structure.
type ClientInfo struct {
ServiceName string
APIVersion string
Endpoint string
SigningName string
SigningRegion string
JSONVersion string
TargetPrefix string
}

View File

@ -7,15 +7,20 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
)
// DefaultRetries is the default number of retries for a service. The value of
// -1 indicates that the service specific retry default will be used.
const DefaultRetries = -1
// UseServiceDefaultRetries instructs the config to use the service's own default
// number of retries. This will be the default action if Config.MaxRetries
// is nil also.
const UseServiceDefaultRetries = -1
// RequestRetryer is an alias for a type that implements the request.Retryer interface.
type RequestRetryer interface{}
// A Config provides service configuration for service clients. By default,
// all clients will use the {defaults.DefaultConfig} structure.
type Config struct {
// The credentials object to use when signing requests. Defaults to
// {defaults.DefaultChainCredentials}.
// a chain of credential providers to search for credentials in environment
// variables, shared credential file, and EC2 Instance Roles.
Credentials *credentials.Credentials
// An optional endpoint URL (hostname only or fully qualified URI)
@ -57,6 +62,21 @@ type Config struct {
// configuration.
MaxRetries *int
// Retryer guides how HTTP requests should be retried in case of recoverable failures.
//
// When nil or the value does not implement the request.Retryer interface,
// the request.DefaultRetryer will be used.
//
// When both Retryer and MaxRetries are non-nil, the former is used and
// the latter ignored.
//
// To set the Retryer field in a type-safe manner and with chaining, use
// the request.WithRetryer helper function:
//
// cfg := request.WithRetryer(aws.NewConfig(), myRetryer)
//
Retryer RequestRetryer
// Disables semantic parameter validation, which validates input for missing
// required fields and/or other semantic request input errors.
DisableParamValidation *bool
@ -171,15 +191,17 @@ func (c *Config) WithSleepDelay(fn func(time.Duration)) *Config {
return c
}
// Merge returns a new Config with the other Config's attribute values merged into
// this Config. If the other Config's attribute is nil it will not be merged into
// the new Config to be returned.
func (c Config) Merge(other *Config) *Config {
if other == nil {
return &c
// MergeIn merges the passed in configs into the existing config object.
func (c *Config) MergeIn(cfgs ...*Config) {
for _, other := range cfgs {
mergeInConfig(c, other)
}
}
dst := c
func mergeInConfig(dst *Config, other *Config) {
if other == nil {
return
}
if other.Credentials != nil {
dst.Credentials = other.Credentials
@ -213,6 +235,10 @@ func (c Config) Merge(other *Config) *Config {
dst.MaxRetries = other.MaxRetries
}
if other.Retryer != nil {
dst.Retryer = other.Retryer
}
if other.DisableParamValidation != nil {
dst.DisableParamValidation = other.DisableParamValidation
}
@ -228,12 +254,17 @@ func (c Config) Merge(other *Config) *Config {
if other.SleepDelay != nil {
dst.SleepDelay = other.SleepDelay
}
return &dst
}
// Copy will return a shallow copy of the Config object.
func (c Config) Copy() *Config {
dst := c
return &dst
// Copy will return a shallow copy of the Config object. If any additional
// configurations are provided they will be merged into the new config returned.
func (c *Config) Copy(cfgs ...*Config) *Config {
dst := &Config{}
dst.MergeIn(c)
for _, cfg := range cfgs {
dst.MergeIn(cfg)
}
return dst
}

View File

@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
"regexp"
"runtime"
"strconv"
"github.com/aws/aws-sdk-go/aws"
@ -23,7 +24,7 @@ type lener interface {
// BuildContentLengthHandler builds the content length of a request based on the body,
// or will use the HTTPRequest.Header's "Content-Length" if defined. If unable
// to determine request body length and no "Content-Length" was specified it will panic.
var BuildContentLengthHandler = request.NamedHandler{"core.BuildContentLengthHandler", func(r *request.Request) {
var BuildContentLengthHandler = request.NamedHandler{Name: "core.BuildContentLengthHandler", Fn: func(r *request.Request) {
if slength := r.HTTPRequest.Header.Get("Content-Length"); slength != "" {
length, _ := strconv.ParseInt(slength, 10, 64)
r.HTTPRequest.ContentLength = length
@ -49,17 +50,19 @@ var BuildContentLengthHandler = request.NamedHandler{"core.BuildContentLengthHan
r.HTTPRequest.Header.Set("Content-Length", fmt.Sprintf("%d", length))
}}
// UserAgentHandler is a request handler for injecting User agent into requests.
var UserAgentHandler = request.NamedHandler{"core.UserAgentHandler", func(r *request.Request) {
r.HTTPRequest.Header.Set("User-Agent", aws.SDKName+"/"+aws.SDKVersion)
}}
// SDKVersionUserAgentHandler is a request handler for adding the SDK Version to the user agent.
var SDKVersionUserAgentHandler = request.NamedHandler{
Name: "core.SDKVersionUserAgentHandler",
Fn: request.MakeAddToUserAgentHandler(aws.SDKName, aws.SDKVersion,
runtime.Version(), runtime.GOOS, runtime.GOARCH),
}
var reStatusCode = regexp.MustCompile(`^(\d{3})`)
// SendHandler is a request handler to send service request using HTTP client.
var SendHandler = request.NamedHandler{"core.SendHandler", func(r *request.Request) {
var SendHandler = request.NamedHandler{Name: "core.SendHandler", Fn: func(r *request.Request) {
var err error
r.HTTPResponse, err = r.Service.Config.HTTPClient.Do(r.HTTPRequest)
r.HTTPResponse, err = r.Config.HTTPClient.Do(r.HTTPRequest)
if err != nil {
// Capture the case where url.Error is returned for error processing
// response. e.g. 301 without location header comes back as string
@ -92,7 +95,7 @@ var SendHandler = request.NamedHandler{"core.SendHandler", func(r *request.Reque
}}
// ValidateResponseHandler is a request handler to validate service response.
var ValidateResponseHandler = request.NamedHandler{"core.ValidateResponseHandler", func(r *request.Request) {
var ValidateResponseHandler = request.NamedHandler{Name: "core.ValidateResponseHandler", Fn: func(r *request.Request) {
if r.HTTPResponse.StatusCode == 0 || r.HTTPResponse.StatusCode >= 300 {
// this may be replaced by an UnmarshalError handler
r.Error = awserr.New("UnknownError", "unknown error", nil)
@ -101,7 +104,7 @@ var ValidateResponseHandler = request.NamedHandler{"core.ValidateResponseHandler
// AfterRetryHandler performs final checks to determine if the request should
// be retried and how long to delay.
var AfterRetryHandler = request.NamedHandler{"core.AfterRetryHandler", func(r *request.Request) {
var AfterRetryHandler = request.NamedHandler{Name: "core.AfterRetryHandler", Fn: func(r *request.Request) {
// If one of the other handlers already set the retry state
// we don't want to override it based on the service's state
if r.Retryable == nil {
@ -110,13 +113,13 @@ var AfterRetryHandler = request.NamedHandler{"core.AfterRetryHandler", func(r *r
if r.WillRetry() {
r.RetryDelay = r.RetryRules(r)
r.Service.Config.SleepDelay(r.RetryDelay)
r.Config.SleepDelay(r.RetryDelay)
// when the expired token exception occurs the credentials
// need to be expired locally so that the next request to
// get credentials will trigger a credentials refresh.
if r.IsErrorExpired() {
r.Service.Config.Credentials.Expire()
r.Config.Credentials.Expire()
}
r.RetryCount++
@ -127,10 +130,10 @@ var AfterRetryHandler = request.NamedHandler{"core.AfterRetryHandler", func(r *r
// ValidateEndpointHandler is a request handler to validate a request had the
// appropriate Region and Endpoint set. Will set r.Error if the endpoint or
// region is not valid.
var ValidateEndpointHandler = request.NamedHandler{"core.ValidateEndpointHandler", func(r *request.Request) {
if r.Service.SigningRegion == "" && aws.StringValue(r.Service.Config.Region) == "" {
var ValidateEndpointHandler = request.NamedHandler{Name: "core.ValidateEndpointHandler", Fn: func(r *request.Request) {
if r.ClientInfo.SigningRegion == "" && aws.StringValue(r.Config.Region) == "" {
r.Error = aws.ErrMissingRegion
} else if r.Service.Endpoint == "" {
} else if r.ClientInfo.Endpoint == "" {
r.Error = aws.ErrMissingEndpoint
}
}}

View File

@ -12,7 +12,7 @@ import (
// ValidateParametersHandler is a request handler to validate the input parameters.
// Validating parameters only has meaning if done prior to the request being sent.
var ValidateParametersHandler = request.NamedHandler{"core.ValidateParametersHandler", func(r *request.Request) {
var ValidateParametersHandler = request.NamedHandler{Name: "core.ValidateParametersHandler", Fn: func(r *request.Request) {
if r.ParamsFilled() {
v := validator{errors: []string{}}
v.validateAny(reflect.ValueOf(r.Params), "")

View File

@ -36,7 +36,9 @@ var (
// creds := NewChainCredentials(
// []Provider{
// &EnvProvider{},
// &EC2RoleProvider{},
// &EC2RoleProvider{
// Client: ec2metadata.New(sess),
// },
// })
//
// // Usage of ChainCredentials with aws.Config

View File

@ -9,6 +9,7 @@ import (
"time"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
)
@ -22,12 +23,10 @@ import (
// p := &ec2rolecreds.EC2RoleProvider{
// // Pass in a custom timeout to be used when requesting
// // IAM EC2 Role credentials.
// Client: &http.Client{
// Timeout: 10 * time.Second,
// },
// // Use default EC2 Role metadata endpoint, Alternate endpoints can be
// // specified setting Endpoint to something else.
// Endpoint: "",
// Client: ec2metadata.New(sess, aws.Config{
// HTTPClient: &http.Client{Timeout: 10 * time.Second},
// }),
//
// // Do not use early expiry of credentials. If a non zero value is
// // specified the credentials will be expired early
// ExpiryWindow: 0,
@ -35,8 +34,8 @@ import (
type EC2RoleProvider struct {
credentials.Expiry
// EC2Metadata client to use when connecting to EC2 metadata service
Client *ec2metadata.Client
// Required EC2Metadata client to use when connecting to EC2 metadata service.
Client *ec2metadata.EC2Metadata
// ExpiryWindow will allow the credentials to trigger refreshing prior to
// the credentials actually expiring. This is beneficial so race conditions
@ -50,33 +49,40 @@ type EC2RoleProvider struct {
ExpiryWindow time.Duration
}
// NewCredentials returns a pointer to a new Credentials object
// wrapping the EC2RoleProvider.
//
// Takes a custom http.Client which can be configured for custom handling of
// things such as timeout.
//
// Endpoint is the URL that the EC2RoleProvider will connect to when retrieving
// role and credentials.
//
// Window is the expiry window that will be subtracted from the expiry returned
// by the role credential request. This is done so that the credentials will
// expire sooner than their actual lifespan.
func NewCredentials(client *ec2metadata.Client, window time.Duration) *credentials.Credentials {
return credentials.NewCredentials(&EC2RoleProvider{
// NewCredentials returns a pointer to a new Credentials object wrapping
// the EC2RoleProvider. Takes a ConfigProvider to create a EC2Metadata client.
// The ConfigProvider is satisfied by the session.Session type.
func NewCredentials(c client.ConfigProvider, options ...func(*EC2RoleProvider)) *credentials.Credentials {
p := &EC2RoleProvider{
Client: ec2metadata.New(c),
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// NewCredentialsWithClient returns a pointer to a new Credentials object wrapping
// the EC2RoleProvider. Takes a EC2Metadata client to use when connecting to EC2
// metadata service.
func NewCredentialsWithClient(client *ec2metadata.EC2Metadata, options ...func(*EC2RoleProvider)) *credentials.Credentials {
p := &EC2RoleProvider{
Client: client,
ExpiryWindow: window,
})
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// Retrieve retrieves credentials from the EC2 service.
// Error will be returned if the request fails, or unable to extract
// the desired credentials.
func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) {
if m.Client == nil {
m.Client = ec2metadata.New(nil)
}
credsList, err := requestCredList(m.Client)
if err != nil {
return credentials.Value{}, err
@ -101,7 +107,7 @@ func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) {
}, nil
}
// A ec2RoleCredRespBody provides the shape for deserializing credential
// A ec2RoleCredRespBody provides the shape for unmarshalling credential
// request responses.
type ec2RoleCredRespBody struct {
// Success State
@ -119,7 +125,7 @@ const iamSecurityCredsPath = "/iam/security-credentials"
// requestCredList requests a list of credentials from the EC2 service.
// If there are no credentials, or there is an error making or receiving the request
func requestCredList(client *ec2metadata.Client) ([]string, error) {
func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) {
resp, err := client.GetMetadata(iamSecurityCredsPath)
if err != nil {
return nil, awserr.New("EC2RoleRequestError", "failed to list EC2 Roles", err)
@ -142,7 +148,7 @@ func requestCredList(client *ec2metadata.Client) ([]string, error) {
//
// If the credentials cannot be found, or there is an error reading the response
// and error will be returned.
func requestCred(client *ec2metadata.Client, credsName string) (ec2RoleCredRespBody, error) {
func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCredRespBody, error) {
resp, err := client.GetMetadata(path.Join(iamSecurityCredsPath, credsName))
if err != nil {
return ec2RoleCredRespBody{},

View File

@ -6,3 +6,7 @@ aws_session_token = token
[no_token]
aws_access_key_id = accessKey
aws_secret_access_key = secret
[with_colon]
aws_access_key_id: accessKey
aws_secret_access_key: secret

View File

@ -5,7 +5,7 @@ import (
"os"
"path/filepath"
"github.com/vaughan0/go-ini"
"github.com/go-ini/ini"
"github.com/aws/aws-sdk-go/aws/awserr"
)
@ -76,32 +76,36 @@ func (p *SharedCredentialsProvider) IsExpired() bool {
// The credentials retrieved from the profile will be returned or error. Error will be
// returned if it fails to read from the file, or the data is invalid.
func loadProfile(filename, profile string) (Value, error) {
config, err := ini.LoadFile(filename)
config, err := ini.Load(filename)
if err != nil {
return Value{}, awserr.New("SharedCredsLoad", "failed to load shared credentials file", err)
}
iniProfile := config.Section(profile)
id, ok := iniProfile["aws_access_key_id"]
if !ok {
return Value{}, awserr.New("SharedCredsAccessKey",
fmt.Sprintf("shared credentials %s in %s did not contain aws_access_key_id", profile, filename),
nil)
iniProfile, err := config.GetSection(profile)
if err != nil {
return Value{}, awserr.New("SharedCredsLoad", "failed to get profile", err)
}
secret, ok := iniProfile["aws_secret_access_key"]
if !ok {
id, err := iniProfile.GetKey("aws_access_key_id")
if err != nil {
return Value{}, awserr.New("SharedCredsAccessKey",
fmt.Sprintf("shared credentials %s in %s did not contain aws_access_key_id", profile, filename),
err)
}
secret, err := iniProfile.GetKey("aws_secret_access_key")
if err != nil {
return Value{}, awserr.New("SharedCredsSecret",
fmt.Sprintf("shared credentials %s in %s did not contain aws_secret_access_key", profile, filename),
nil)
}
token := iniProfile["aws_session_token"]
// Default to empty string if not found
token := iniProfile.Key("aws_session_token")
return Value{
AccessKeyID: id,
SecretAccessKey: secret,
SessionToken: token,
AccessKeyID: id.String(),
SecretAccessKey: secret.String(),
SessionToken: token.String(),
}, nil
}

View File

@ -9,6 +9,7 @@ import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/service/sts"
)
@ -18,31 +19,17 @@ type AssumeRoler interface {
AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
}
// DefaultDuration is the default amount of time in minutes that the credentials
// will be valid for.
var DefaultDuration = time.Duration(15) * time.Minute
// AssumeRoleProvider retrieves temporary credentials from the STS service, and
// keeps track of their expiration time. This provider must be used explicitly,
// as it is not included in the credentials chain.
//
// Example how to configure a service to use this provider:
//
// config := &aws.Config{
// Credentials: stscreds.NewCredentials(nil, "arn-of-the-role-to-assume", 10*time.Second),
// })
// // Use config for creating your AWS service.
//
// Example how to obtain customised credentials:
//
// provider := &stscreds.Provider{
// // Extend the duration to 1 hour.
// Duration: time.Hour,
// // Custom role name.
// RoleSessionName: "custom-session-name",
// }
// creds := credentials.NewCredentials(provider)
//
type AssumeRoleProvider struct {
credentials.Expiry
// Custom STS client. If not set the default STS client will be used.
// STS client to make assume role request with.
Client AssumeRoler
// Role to be assumed.
@ -73,34 +60,52 @@ type AssumeRoleProvider struct {
// AssumeRoleProvider. The credentials will expire every 15 minutes and the
// role will be named after a nanosecond timestamp of this operation.
//
// The sts and roleARN parameters are used for building the "AssumeRole" call.
// Pass nil as sts to use the default client.
//
// Window is the expiry window that will be subtracted from the expiry returned
// by the role credential request. This is done so that the credentials will
// expire sooner than their actual lifespan.
func NewCredentials(client AssumeRoler, roleARN string, window time.Duration) *credentials.Credentials {
return credentials.NewCredentials(&AssumeRoleProvider{
Client: client,
// Takes a Config provider to create the STS client. The ConfigProvider is
// satisfied by the session.Session type.
func NewCredentials(c client.ConfigProvider, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
p := &AssumeRoleProvider{
Client: sts.New(c),
RoleARN: roleARN,
ExpiryWindow: window,
})
Duration: DefaultDuration,
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// NewCredentialsWithClient returns a pointer to a new Credentials object wrapping the
// AssumeRoleProvider. The credentials will expire every 15 minutes and the
// role will be named after a nanosecond timestamp of this operation.
//
// Takes an AssumeRoler which can be satisfiede by the STS client.
func NewCredentialsWithClient(svc AssumeRoler, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
p := &AssumeRoleProvider{
Client: svc,
RoleARN: roleARN,
Duration: DefaultDuration,
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// Retrieve generates a new set of temporary credentials using STS.
func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
// Apply defaults where parameters are not set.
if p.Client == nil {
p.Client = sts.New(nil)
}
if p.RoleSessionName == "" {
// Try to work out a role name that will hopefully end up unique.
p.RoleSessionName = fmt.Sprintf("%d", time.Now().UTC().UnixNano())
}
if p.Duration == 0 {
// Expire as often as AWS permits.
p.Duration = 15 * time.Minute
p.Duration = DefaultDuration
}
roleOutput, err := p.Client.AssumeRole(&sts.AssumeRoleInput{

View File

@ -1,3 +1,10 @@
// Package defaults is a collection of helpers to retrieve the SDK's default
// configuration and handlers.
//
// Generally this package shouldn't be used directly, but session.Session
// instead. This package is useful when you need to reset the defaults
// of a session or service client to the SDK defaults before setting
// additional parameters.
package defaults
import (
@ -6,34 +13,83 @@ import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/corehandlers"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/endpoints"
)
// DefaultChainCredentials is a Credentials which will find the first available
// credentials Value from the list of Providers.
//
// This should be used in the default case. Once the type of credentials are
// known switching to the specific Credentials will be more efficient.
var DefaultChainCredentials = credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
&ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute},
})
// A Defaults provides a collection of default values for SDK clients.
type Defaults struct {
Config *aws.Config
Handlers request.Handlers
}
// DefaultConfig is the default all service configuration will be based off of.
// By default, all clients use this structure for initialization options unless
// a custom configuration object is passed in.
// Get returns the SDK's default values with Config and handlers pre-configured.
func Get() Defaults {
cfg := Config()
handlers := Handlers()
cfg.Credentials = CredChain(cfg, handlers)
return Defaults{
Config: cfg,
Handlers: handlers,
}
}
// Config returns the default configuration without credentials.
// To retrieve a config with credentials also included use
// `defaults.Get().Config` instead.
//
// You may modify this global structure to change all default configuration
// in the SDK. Note that configuration options are copied by value, so any
// modifications must happen before constructing a client.
var DefaultConfig = aws.NewConfig().
WithCredentials(DefaultChainCredentials).
// Generally you shouldn't need to use this method directly, but
// is available if you need to reset the configuration of an
// existing service client or session.
func Config() *aws.Config {
return aws.NewConfig().
WithCredentials(credentials.AnonymousCredentials).
WithRegion(os.Getenv("AWS_REGION")).
WithHTTPClient(http.DefaultClient).
WithMaxRetries(aws.DefaultRetries).
WithMaxRetries(aws.UseServiceDefaultRetries).
WithLogger(aws.NewDefaultLogger()).
WithLogLevel(aws.LogOff).
WithSleepDelay(time.Sleep)
}
// Handlers returns the default request handlers.
//
// Generally you shouldn't need to use this method directly, but
// is available if you need to reset the request handlers of an
// existing service client or session.
func Handlers() request.Handlers {
var handlers request.Handlers
handlers.Validate.PushBackNamed(corehandlers.ValidateEndpointHandler)
handlers.Build.PushBackNamed(corehandlers.SDKVersionUserAgentHandler)
handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
handlers.Send.PushBackNamed(corehandlers.SendHandler)
handlers.AfterRetry.PushBackNamed(corehandlers.AfterRetryHandler)
handlers.ValidateResponse.PushBackNamed(corehandlers.ValidateResponseHandler)
return handlers
}
// CredChain returns the default credential chain.
//
// Generally you shouldn't need to use this method directly, but
// is available if you need to reset the credentials of an
// existing service client or session's Config.
func CredChain(cfg *aws.Config, handlers request.Handlers) *credentials.Credentials {
endpoint, signingRegion := endpoints.EndpointForRegion(ec2metadata.ServiceName, *cfg.Region, true)
return credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
&ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.NewClient(*cfg, handlers, endpoint, signingRegion),
ExpiryWindow: 5 * time.Minute,
},
})
}

View File

@ -7,7 +7,7 @@ import (
)
// GetMetadata uses the path provided to request
func (c *Client) GetMetadata(p string) (string, error) {
func (c *EC2Metadata) GetMetadata(p string) (string, error) {
op := &request.Operation{
Name: "GetMetadata",
HTTPMethod: "GET",
@ -15,13 +15,13 @@ func (c *Client) GetMetadata(p string) (string, error) {
}
output := &metadataOutput{}
req := request.New(c.Service.ServiceInfo, c.Service.Handlers, c.Service.Retryer, op, nil, output)
req := c.NewRequest(op, nil, output)
return output.Content, req.Send()
}
// Region returns the region the instance is running in.
func (c *Client) Region() (string, error) {
func (c *EC2Metadata) Region() (string, error) {
resp, err := c.GetMetadata("placement/availability-zone")
if err != nil {
return "", err
@ -34,7 +34,7 @@ func (c *Client) Region() (string, error) {
// Available returns if the application has access to the EC2 Metadata service.
// Can be used to determine if application is running within an EC2 Instance and
// the metadata service is available.
func (c *Client) Available() bool {
func (c *EC2Metadata) Available() bool {
if _, err := c.GetMetadata("instance-id"); err != nil {
return false
}

View File

@ -1,3 +1,5 @@
// Package ec2metadata provides the client for making API calls to the
// EC2 Metadata service.
package ec2metadata
import (
@ -8,89 +10,41 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/service"
"github.com/aws/aws-sdk-go/aws/service/serviceinfo"
)
// DefaultRetries states the default number of times the service client will
// attempt to retry a failed request before failing.
const DefaultRetries = 3
// ServiceName is the name of the service.
const ServiceName = "ec2metadata"
// A Config provides the configuration for the EC2 Metadata service.
type Config struct {
// An optional endpoint URL (hostname only or fully qualified URI)
// that overrides the default service endpoint for a client. Set this
// to nil, or `""` to use the default service endpoint.
Endpoint *string
// The HTTP client to use when sending requests. Defaults to
// `http.DefaultClient`.
HTTPClient *http.Client
// An integer value representing the logging level. The default log level
// is zero (LogOff), which represents no logging. To enable logging set
// to a LogLevel Value.
Logger aws.Logger
// The logger writer interface to write logging messages to. Defaults to
// standard out.
LogLevel *aws.LogLevelType
// The maximum number of times that a request will be retried for failures.
// Defaults to DefaultRetries for the number of retries to be performed
// per request.
MaxRetries *int
// A EC2Metadata is an EC2 Metadata service Client.
type EC2Metadata struct {
*client.Client
}
// A Client is an EC2 Metadata service Client.
type Client struct {
*service.Service
}
// New creates a new instance of the EC2 Metadata service client.
// New creates a new instance of the EC2Metadata client with a session.
// This client is safe to use across multiple goroutines.
//
// In the general use case the configuration for this service client should not
// be needed and `nil` can be provided. Configuration is only needed if the
// `ec2metadata.Config` defaults need to be overridden. Eg. Setting LogLevel.
// Example:
// // Create a EC2Metadata client from just a session.
// svc := ec2metadata.New(mySession)
//
// @note This configuration will NOT be merged with the default AWS service
// client configuration `defaults.DefaultConfig`. Due to circular dependencies
// with the defaults package and credentials EC2 Role Provider.
func New(config *Config) *Client {
service := &service.Service{
ServiceInfo: serviceinfo.ServiceInfo{
Config: copyConfig(config),
ServiceName: "Client",
Endpoint: "http://169.254.169.254/latest",
APIVersion: "latest",
},
}
service.Initialize()
service.Handlers.Unmarshal.PushBack(unmarshalHandler)
service.Handlers.UnmarshalError.PushBack(unmarshalError)
service.Handlers.Validate.Clear()
service.Handlers.Validate.PushBack(validateEndpointHandler)
return &Client{service}
// // Create a EC2Metadata client with additional configuration
// svc := ec2metadata.New(mySession, aws.NewConfig().WithLogLevel(aws.LogDebugHTTPBody))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata {
c := p.ClientConfig(ServiceName, cfgs...)
return NewClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
}
func copyConfig(config *Config) *aws.Config {
if config == nil {
config = &Config{}
}
c := &aws.Config{
Credentials: credentials.AnonymousCredentials,
Endpoint: config.Endpoint,
HTTPClient: config.HTTPClient,
Logger: config.Logger,
LogLevel: config.LogLevel,
MaxRetries: config.MaxRetries,
}
if c.HTTPClient == nil {
c.HTTPClient = &http.Client{
// NewClient returns a new EC2Metadata client. Should be used to create
// a client when not using a session. Generally using just New with a session
// is preferred.
func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string, opts ...func(*client.Client)) *EC2Metadata {
// If the default http client is provided, replace it with a custom
// client using default timeouts.
if cfg.HTTPClient == http.DefaultClient {
cfg.HTTPClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
@ -104,17 +58,30 @@ func copyConfig(config *Config) *aws.Config {
},
}
}
if c.Logger == nil {
c.Logger = aws.NewDefaultLogger()
}
if c.LogLevel == nil {
c.LogLevel = aws.LogLevel(aws.LogOff)
}
if c.MaxRetries == nil {
c.MaxRetries = aws.Int(DefaultRetries)
svc := &EC2Metadata{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
Endpoint: endpoint,
APIVersion: "latest",
},
handlers,
),
}
return c
svc.Handlers.Unmarshal.PushBack(unmarshalHandler)
svc.Handlers.UnmarshalError.PushBack(unmarshalError)
svc.Handlers.Validate.Clear()
svc.Handlers.Validate.PushBack(validateEndpointHandler)
// Add additional options to the service config
for _, option := range opts {
option(svc.Client)
}
return svc
}
type metadataOutput struct {
@ -143,7 +110,7 @@ func unmarshalError(r *request.Request) {
}
func validateEndpointHandler(r *request.Request) {
if r.Service.Endpoint == "" {
if r.ClientInfo.Endpoint == "" {
r.Error = aws.ErrMissingEndpoint
}
}

View File

@ -7,11 +7,11 @@ var (
// not found.
//
// @readonly
ErrMissingRegion error = awserr.New("MissingRegion", "could not find region configuration", nil)
ErrMissingRegion = awserr.New("MissingRegion", "could not find region configuration", nil)
// ErrMissingEndpoint is an error that is returned if an endpoint cannot be
// resolved for a service.
//
// @readonly
ErrMissingEndpoint error = awserr.New("MissingEndpoint", "'Endpoint' configuration is required for this service", nil)
ErrMissingEndpoint = awserr.New("MissingEndpoint", "'Endpoint' configuration is required for this service", nil)
)

View File

@ -1,5 +1,10 @@
package request
import (
"fmt"
"strings"
)
// A Handlers provides a collection of request handlers for various
// stages of handling requests.
type Handlers struct {
@ -110,3 +115,26 @@ func (l *HandlerList) Run(r *Request) {
f.Fn(r)
}
}
// MakeAddToUserAgentHandler will add the name/version pair to the User-Agent request
// header. If the extra parameters are provided they will be added as metadata to the
// name/version pair resulting in the following format.
// "name/version (extra0; extra1; ...)"
// The user agent part will be concatenated with this current request's user agent string.
func MakeAddToUserAgentHandler(name, version string, extra ...string) func(*Request) {
ua := fmt.Sprintf("%s/%s", name, version)
if len(extra) > 0 {
ua += fmt.Sprintf(" (%s)", strings.Join(extra, "; "))
}
return func(r *Request) {
AddToUserAgent(r, ua)
}
}
// MakeAddToUserAgentFreeFormHandler adds the input to the User-Agent request header.
// The input string will be concatenated with the current request's user agent string.
func MakeAddToUserAgentFreeFormHandler(s string) func(*Request) {
return func(r *Request) {
AddToUserAgent(r, s)
}
}

View File

@ -12,15 +12,16 @@ import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/aws/aws-sdk-go/aws/service/serviceinfo"
"github.com/aws/aws-sdk-go/aws/client/metadata"
)
// A Request is the service request to be made.
type Request struct {
Retryer
Service serviceinfo.ServiceInfo
Config aws.Config
ClientInfo metadata.ClientInfo
Handlers Handlers
Retryer
Time time.Time
ExpireTime time.Duration
Operation *Operation
@ -32,7 +33,7 @@ type Request struct {
Error error
Data interface{}
RequestID string
RetryCount uint
RetryCount int
Retryable *bool
RetryDelay time.Duration
@ -61,7 +62,9 @@ type Paginator struct {
// Params is any value of input parameters to be the request payload.
// Data is pointer value to an object which the request's response
// payload will be deserialized to.
func New(service serviceinfo.ServiceInfo, handlers Handlers, retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
method := operation.HTTPMethod
if method == "" {
method = "POST"
@ -72,12 +75,14 @@ func New(service serviceinfo.ServiceInfo, handlers Handlers, retryer Retryer, op
}
httpReq, _ := http.NewRequest(method, "", nil)
httpReq.URL, _ = url.Parse(service.Endpoint + p)
httpReq.URL, _ = url.Parse(clientInfo.Endpoint + p)
r := &Request{
Retryer: retryer,
Service: service,
Config: cfg,
ClientInfo: clientInfo,
Handlers: handlers.Copy(),
Retryer: retryer,
Time: time.Now(),
ExpireTime: 0,
Operation: operation,
@ -140,7 +145,7 @@ func (r *Request) Presign(expireTime time.Duration) (string, error) {
}
func debugLogReqError(r *Request, stage string, retrying bool, err error) {
if !r.Service.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
return
}
@ -149,8 +154,8 @@ func debugLogReqError(r *Request, stage string, retrying bool, err error) {
retryStr = "will retry"
}
r.Service.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v",
stage, r.Service.ServiceName, r.Operation.Name, retryStr, err))
r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v",
stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err))
}
// Build will build the request's object so it can be signed and sent
@ -205,9 +210,9 @@ func (r *Request) Send() error {
}
if aws.BoolValue(r.Retryable) {
if r.Service.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
r.Service.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
r.Service.ServiceName, r.Operation.Name, r.RetryCount))
if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount))
}
// Re-seek the body back to the original point in for a retry so that
@ -264,85 +269,11 @@ func (r *Request) Send() error {
return nil
}
// HasNextPage returns true if this request has more pages of data available.
func (r *Request) HasNextPage() bool {
return r.nextPageTokens() != nil
}
// nextPageTokens returns the tokens to use when asking for the next page of
// data.
func (r *Request) nextPageTokens() []interface{} {
if r.Operation.Paginator == nil {
return nil
}
if r.Operation.TruncationToken != "" {
tr := awsutil.ValuesAtAnyPath(r.Data, r.Operation.TruncationToken)
if tr == nil || len(tr) == 0 {
return nil
}
switch v := tr[0].(type) {
case bool:
if v == false {
return nil
}
}
}
found := false
tokens := make([]interface{}, len(r.Operation.OutputTokens))
for i, outtok := range r.Operation.OutputTokens {
v := awsutil.ValuesAtAnyPath(r.Data, outtok)
if v != nil && len(v) > 0 {
found = true
tokens[i] = v[0]
}
}
if found {
return tokens
}
return nil
}
// NextPage returns a new Request that can be executed to return the next
// page of result data. Call .Send() on this request to execute it.
func (r *Request) NextPage() *Request {
tokens := r.nextPageTokens()
if tokens == nil {
return nil
}
data := reflect.New(reflect.TypeOf(r.Data).Elem()).Interface()
nr := New(r.Service, r.Handlers, r.Retryer, r.Operation, awsutil.CopyOf(r.Params), data)
for i, intok := range nr.Operation.InputTokens {
awsutil.SetValueAtAnyPath(nr.Params, intok, tokens[i])
}
return nr
}
// EachPage iterates over each page of a paginated request object. The fn
// parameter should be a function with the following sample signature:
//
// func(page *T, lastPage bool) bool {
// return true // return false to stop iterating
// }
//
// Where "T" is the structure type matching the output structure of the given
// operation. For example, a request object generated by
// DynamoDB.ListTablesRequest() would expect to see dynamodb.ListTablesOutput
// as the structure "T". The lastPage value represents whether the page is
// the last page of data or not. The return value of this function should
// return true to keep iterating or false to stop.
func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error {
for page := r; page != nil; page = page.NextPage() {
page.Send()
shouldContinue := fn(page.Data, !page.HasNextPage())
if page.Error != nil || !shouldContinue {
return page.Error
}
}
return nil
// AddToUserAgent adds the string to the end of the request's current user agent.
func AddToUserAgent(r *Request, s string) {
curUA := r.HTTPRequest.Header.Get("User-Agent")
if len(curUA) > 0 {
s = curUA + " " + s
}
r.HTTPRequest.Header.Set("User-Agent", s)
}

View File

@ -0,0 +1,104 @@
package request
import (
"reflect"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
)
//type Paginater interface {
// HasNextPage() bool
// NextPage() *Request
// EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error
//}
// HasNextPage returns true if this request has more pages of data available.
func (r *Request) HasNextPage() bool {
return len(r.nextPageTokens()) > 0
}
// nextPageTokens returns the tokens to use when asking for the next page of
// data.
func (r *Request) nextPageTokens() []interface{} {
if r.Operation.Paginator == nil {
return nil
}
if r.Operation.TruncationToken != "" {
tr, _ := awsutil.ValuesAtPath(r.Data, r.Operation.TruncationToken)
if len(tr) == 0 {
return nil
}
switch v := tr[0].(type) {
case *bool:
if !aws.BoolValue(v) {
return nil
}
case bool:
if v == false {
return nil
}
}
}
tokens := []interface{}{}
tokenAdded := false
for _, outToken := range r.Operation.OutputTokens {
v, _ := awsutil.ValuesAtPath(r.Data, outToken)
if len(v) > 0 {
tokens = append(tokens, v[0])
tokenAdded = true
} else {
tokens = append(tokens, nil)
}
}
if !tokenAdded {
return nil
}
return tokens
}
// NextPage returns a new Request that can be executed to return the next
// page of result data. Call .Send() on this request to execute it.
func (r *Request) NextPage() *Request {
tokens := r.nextPageTokens()
if len(tokens) == 0 {
return nil
}
data := reflect.New(reflect.TypeOf(r.Data).Elem()).Interface()
nr := New(r.Config, r.ClientInfo, r.Handlers, r.Retryer, r.Operation, awsutil.CopyOf(r.Params), data)
for i, intok := range nr.Operation.InputTokens {
awsutil.SetValueAtPath(nr.Params, intok, tokens[i])
}
return nr
}
// EachPage iterates over each page of a paginated request object. The fn
// parameter should be a function with the following sample signature:
//
// func(page *T, lastPage bool) bool {
// return true // return false to stop iterating
// }
//
// Where "T" is the structure type matching the output structure of the given
// operation. For example, a request object generated by
// DynamoDB.ListTablesRequest() would expect to see dynamodb.ListTablesOutput
// as the structure "T". The lastPage value represents whether the page is
// the last page of data or not. The return value of this function should
// return true to keep iterating or false to stop.
func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error {
for page := r; page != nil; page = page.NextPage() {
if err := page.Send(); err != nil {
return err
}
if getNextPage := fn(page.Data, !page.HasNextPage()); !getNextPage {
return page.Error
}
}
return nil
}

View File

@ -3,6 +3,7 @@ package request
import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
)
@ -12,13 +13,21 @@ import (
type Retryer interface {
RetryRules(*Request) time.Duration
ShouldRetry(*Request) bool
MaxRetries() uint
MaxRetries() int
}
// WithRetryer sets a config Retryer value to the given Config returning it
// for chaining.
func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config {
cfg.Retryer = retryer
return cfg
}
// retryableCodes is a collection of service response codes which are retry-able
// without any further action.
var retryableCodes = map[string]struct{}{
"RequestError": {},
"RequestTimeout": {},
"ProvisionedThroughputExceededException": {},
"Throttling": {},
"ThrottlingException": {},

View File

@ -1,133 +0,0 @@
package service
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
"regexp"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/corehandlers"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/service/serviceinfo"
"github.com/aws/aws-sdk-go/private/endpoints"
)
// A Service implements the base service request and response handling
// used by all services.
type Service struct {
serviceinfo.ServiceInfo
request.Retryer
DefaultMaxRetries uint
Handlers request.Handlers
}
var schemeRE = regexp.MustCompile("^([^:]+)://")
// New will return a pointer to a new Server object initialized.
func New(config *aws.Config) *Service {
svc := &Service{ServiceInfo: serviceinfo.ServiceInfo{Config: config}}
svc.Initialize()
return svc
}
// Initialize initializes the service.
func (s *Service) Initialize() {
if s.Config == nil {
s.Config = &aws.Config{}
}
if s.Config.HTTPClient == nil {
s.Config.HTTPClient = http.DefaultClient
}
if s.Config.SleepDelay == nil {
s.Config.SleepDelay = time.Sleep
}
s.Retryer = DefaultRetryer{s}
s.DefaultMaxRetries = 3
s.Handlers.Validate.PushBackNamed(corehandlers.ValidateEndpointHandler)
s.Handlers.Build.PushBackNamed(corehandlers.UserAgentHandler)
s.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
s.Handlers.Send.PushBackNamed(corehandlers.SendHandler)
s.Handlers.AfterRetry.PushBackNamed(corehandlers.AfterRetryHandler)
s.Handlers.ValidateResponse.PushBackNamed(corehandlers.ValidateResponseHandler)
if !aws.BoolValue(s.Config.DisableParamValidation) {
s.Handlers.Validate.PushBackNamed(corehandlers.ValidateParametersHandler)
}
s.AddDebugHandlers()
s.buildEndpoint()
}
// NewRequest returns a new Request pointer for the service API
// operation and parameters.
func (s *Service) NewRequest(operation *request.Operation, params interface{}, data interface{}) *request.Request {
return request.New(s.ServiceInfo, s.Handlers, s.Retryer, operation, params, data)
}
// buildEndpoint builds the endpoint values the service will use to make requests with.
func (s *Service) buildEndpoint() {
if aws.StringValue(s.Config.Endpoint) != "" {
s.Endpoint = *s.Config.Endpoint
} else if s.Endpoint == "" {
s.Endpoint, s.SigningRegion =
endpoints.EndpointForRegion(s.ServiceName, aws.StringValue(s.Config.Region))
}
if s.Endpoint != "" && !schemeRE.MatchString(s.Endpoint) {
scheme := "https"
if aws.BoolValue(s.Config.DisableSSL) {
scheme = "http"
}
s.Endpoint = scheme + "://" + s.Endpoint
}
}
// AddDebugHandlers injects debug logging handlers into the service to log request
// debug information.
func (s *Service) AddDebugHandlers() {
if !s.Config.LogLevel.AtLeast(aws.LogDebug) {
return
}
s.Handlers.Send.PushFront(logRequest)
s.Handlers.Send.PushBack(logResponse)
}
const logReqMsg = `DEBUG: Request %s/%s Details:
---[ REQUEST POST-SIGN ]-----------------------------
%s
-----------------------------------------------------`
func logRequest(r *request.Request) {
logBody := r.Service.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
dumpedBody, _ := httputil.DumpRequestOut(r.HTTPRequest, logBody)
if logBody {
// Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's
// Body as a NoOpCloser and will not be reset after read by the HTTP
// client reader.
r.Body.Seek(r.BodyStart, 0)
r.HTTPRequest.Body = ioutil.NopCloser(r.Body)
}
r.Service.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.Service.ServiceName, r.Operation.Name, string(dumpedBody)))
}
const logRespMsg = `DEBUG: Response %s/%s Details:
---[ RESPONSE ]--------------------------------------
%s
-----------------------------------------------------`
func logResponse(r *request.Request) {
var msg = "no reponse data"
if r.HTTPResponse != nil {
logBody := r.Service.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, logBody)
msg = string(dumpedBody)
} else if r.Error != nil {
msg = r.Error.Error()
}
r.Service.Config.Logger.Log(fmt.Sprintf(logRespMsg, r.Service.ServiceName, r.Operation.Name, msg))
}

View File

@ -1,15 +0,0 @@
package serviceinfo
import "github.com/aws/aws-sdk-go/aws"
// ServiceInfo wraps immutable data from the service.Service structure.
type ServiceInfo struct {
Config *aws.Config
ServiceName string
APIVersion string
Endpoint string
SigningName string
SigningRegion string
JSONVersion string
TargetPrefix string
}

View File

@ -0,0 +1,111 @@
// Package session provides a way to create service clients with shared configuration
// and handlers.
//
// Generally this package should be used instead of the `defaults` package.
//
// A session should be used to share configurations and request handlers between multiple
// service clients. When service clients need specific configuration aws.Config can be
// used to provide additional configuration directly to the service client.
package session
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/corehandlers"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/endpoints"
)
// A Session provides a central location to create service clients from and
// store configurations and request handlers for those services.
//
// Sessions are safe to create service clients concurrently, but it is not safe
// to mutate the session concurrently.
type Session struct {
Config *aws.Config
Handlers request.Handlers
}
// New creates a new instance of the handlers merging in the provided Configs
// on top of the SDK's default configurations. Once the session is created it
// can be mutated to modify Configs or Handlers. The session is safe to be read
// concurrently, but it should not be written to concurrently.
//
// Example:
// // Create a session with the default config and request handlers.
// sess := session.New()
//
// // Create a session with a custom region
// sess := session.New(&aws.Config{Region: aws.String("us-east-1")})
//
// // Create a session, and add additional handlers for all service
// // clients created with the session to inherit. Adds logging handler.
// sess := session.New()
// sess.Handlers.Send.PushFront(func(r *request.Request) {
// // Log every request made and its payload
// logger.Println("Request: %s/%s, Payload: %s", r.ClientInfo.ServiceName, r.Operation, r.Params)
// })
//
// // Create a S3 client instance from a session
// sess := session.New()
// svc := s3.New(sess)
func New(cfgs ...*aws.Config) *Session {
def := defaults.Get()
s := &Session{
Config: def.Config,
Handlers: def.Handlers,
}
s.Config.MergeIn(cfgs...)
initHandlers(s)
return s
}
func initHandlers(s *Session) {
// Add the Validate parameter handler if it is not disabled.
s.Handlers.Validate.Remove(corehandlers.ValidateParametersHandler)
if !aws.BoolValue(s.Config.DisableParamValidation) {
s.Handlers.Validate.PushBackNamed(corehandlers.ValidateParametersHandler)
}
}
// Copy creates and returns a copy of the current session, coping the config
// and handlers. If any additional configs are provided they will be merged
// on top of the session's copied config.
//
// Example:
// // Create a copy of the current session, configured for the us-west-2 region.
// sess.Copy(&aws.Config{Region: aws.String("us-west-2"})
func (s *Session) Copy(cfgs ...*aws.Config) *Session {
newSession := &Session{
Config: s.Config.Copy(cfgs...),
Handlers: s.Handlers.Copy(),
}
initHandlers(newSession)
return newSession
}
// ClientConfig satisfies the client.ConfigProvider interface and is used to
// configure the service client instances. Passing the Session to the service
// client's constructor (New) will use this method to configure the client.
//
// Example:
// sess := session.New()
// s3.New(sess)
func (s *Session) ClientConfig(serviceName string, cfgs ...*aws.Config) client.Config {
s = s.Copy(cfgs...)
endpoint, signingRegion := endpoints.NormalizeEndpoint(
aws.StringValue(s.Config.Endpoint), serviceName,
aws.StringValue(s.Config.Region), aws.BoolValue(s.Config.DisableSSL))
return client.Config{
Config: s.Config,
Handlers: s.Handlers,
Endpoint: endpoint,
SigningRegion: signingRegion,
}
}

View File

@ -5,7 +5,7 @@ import (
"sync"
)
// ReadSeekCloser wraps a io.Reader returning a ReaderSeakerCloser
// ReadSeekCloser wraps a io.Reader returning a ReaderSeekerCloser
func ReadSeekCloser(r io.Reader) ReaderSeekerCloser {
return ReaderSeekerCloser{r}
}

View File

@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK
const SDKVersion = "0.9.16"
const SDKVersion = "1.0.2"

View File

@ -4,11 +4,27 @@ package endpoints
//go:generate go run ../model/cli/gen-endpoints/main.go endpoints.json endpoints_map.go
//go:generate gofmt -s -w endpoints_map.go
import "strings"
import (
"fmt"
"regexp"
"strings"
)
// NormalizeEndpoint takes and endpoint and service API information to return a
// normalized endpoint and signing region. If the endpoint is not an empty string
// the service name and region will be used to look up the service's API endpoint.
// If the endpoint is provided the scheme will be added if it is not present.
func NormalizeEndpoint(endpoint, serviceName, region string, disableSSL bool) (normEndpoint, signingRegion string) {
if endpoint == "" {
return EndpointForRegion(serviceName, region, disableSSL)
}
return AddScheme(endpoint, disableSSL), ""
}
// EndpointForRegion returns an endpoint and its signing region for a service and region.
// if the service and region pair are not found endpoint and signingRegion will be empty.
func EndpointForRegion(svcName, region string) (endpoint, signingRegion string) {
func EndpointForRegion(svcName, region string, disableSSL bool) (endpoint, signingRegion string) {
derivedKeys := []string{
region + "/" + svcName,
region + "/*",
@ -24,8 +40,26 @@ func EndpointForRegion(svcName, region string) (endpoint, signingRegion string)
endpoint = ep
signingRegion = val.SigningRegion
return
break
}
}
return
return AddScheme(endpoint, disableSSL), signingRegion
}
// Regular expression to determine if the endpoint string is prefixed with a scheme.
var schemeRE = regexp.MustCompile("^([^:]+)://")
// AddScheme adds the HTTP or HTTPS schemes to a endpoint URL if there is no
// scheme. If disableSSL is true HTTP will be added instead of the default HTTPS.
func AddScheme(endpoint string, disableSSL bool) string {
if endpoint != "" && !schemeRE.MatchString(endpoint) {
scheme := "https"
if disableSSL {
scheme = "http"
}
endpoint = fmt.Sprintf("%s://%s", scheme, endpoint)
}
return endpoint
}

View File

@ -29,6 +29,10 @@
"endpoint": "",
"signingRegion": "us-east-1"
},
"*/ec2metadata": {
"endpoint": "http://169.254.169.254/latest",
"signingRegion": "us-east-1"
},
"*/iam": {
"endpoint": "iam.amazonaws.com",
"signingRegion": "us-east-1"
@ -45,6 +49,10 @@
"endpoint": "sts.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/waf": {
"endpoint": "waf.amazonaws.com",
"signingRegion": "us-east-1"
},
"us-east-1/sdb": {
"endpoint": "sdb.amazonaws.com",
"signingRegion": "us-east-1"

View File

@ -30,6 +30,10 @@ var endpointsMap = endpointStruct{
Endpoint: "",
SigningRegion: "us-east-1",
},
"*/ec2metadata": {
Endpoint: "http://169.254.169.254/latest",
SigningRegion: "us-east-1",
},
"*/iam": {
Endpoint: "iam.amazonaws.com",
SigningRegion: "us-east-1",
@ -46,6 +50,10 @@ var endpointsMap = endpointStruct{
Endpoint: "sts.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/waf": {
Endpoint: "waf.amazonaws.com",
SigningRegion: "us-east-1",
},
"ap-northeast-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},

View File

@ -15,7 +15,7 @@ import (
func Build(r *request.Request) {
body := url.Values{
"Action": {r.Operation.Name},
"Version": {r.Service.APIVersion},
"Version": {r.ClientInfo.APIVersion},
}
if err := queryutil.Parse(body, r.Params, true); err != nil {
r.Error = awserr.New("SerializationError", "failed encoding EC2 Query request", err)

View File

@ -15,7 +15,7 @@ import (
func Build(r *request.Request) {
body := url.Values{
"Action": {r.Operation.Name},
"Version": {r.Service.APIVersion},
"Version": {r.ClientInfo.APIVersion},
}
if err := queryutil.Parse(body, r.Params, false); err != nil {
r.Error = awserr.New("SerializationError", "failed encoding Query request", err)

View File

@ -72,7 +72,7 @@ func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix stri
continue // ignore unexported fields
}
value := elemOf(value.Field(i))
elemValue := elemOf(value.Field(i))
field := t.Field(i)
var name string
@ -97,7 +97,7 @@ func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix stri
name = prefix + "." + name
}
if err := q.parseValue(v, value, name, field.Tag); err != nil {
if err := q.parseValue(v, elemValue, name, field.Tag); err != nil {
return err
}
}

View File

@ -67,18 +67,18 @@ type signer struct {
func Sign(req *request.Request) {
// If the request does not need to be signed ignore the signing of the
// request if the AnonymousCredentials object is used.
if req.Service.Config.Credentials == credentials.AnonymousCredentials {
if req.Config.Credentials == credentials.AnonymousCredentials {
return
}
region := req.Service.SigningRegion
region := req.ClientInfo.SigningRegion
if region == "" {
region = aws.StringValue(req.Service.Config.Region)
region = aws.StringValue(req.Config.Region)
}
name := req.Service.SigningName
name := req.ClientInfo.SigningName
if name == "" {
name = req.Service.ServiceName
name = req.ClientInfo.ServiceName
}
s := signer{
@ -89,9 +89,9 @@ func Sign(req *request.Request) {
Body: req.Body,
ServiceName: name,
Region: region,
Credentials: req.Service.Config.Credentials,
Debug: req.Service.Config.LogLevel.Value(),
Logger: req.Service.Config.Logger,
Credentials: req.Config.Credentials,
Debug: req.Config.LogLevel.Value(),
Logger: req.Config.Logger,
}
req.Error = s.sign()

View File

@ -0,0 +1,136 @@
package waiter
import (
"fmt"
"reflect"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/aws/aws-sdk-go/aws/request"
)
// A Config provides a collection of configuration values to setup a generated
// waiter code with.
type Config struct {
Name string
Delay int
MaxAttempts int
Operation string
Acceptors []WaitAcceptor
}
// A WaitAcceptor provides the information needed to wait for an API operation
// to complete.
type WaitAcceptor struct {
Expected interface{}
Matcher string
State string
Argument string
}
// A Waiter provides waiting for an operation to complete.
type Waiter struct {
Config
Client interface{}
Input interface{}
}
// Wait waits for an operation to complete, expire max attempts, or fail. Error
// is returned if the operation fails.
func (w *Waiter) Wait() error {
client := reflect.ValueOf(w.Client)
in := reflect.ValueOf(w.Input)
method := client.MethodByName(w.Config.Operation + "Request")
for i := 0; i < w.MaxAttempts; i++ {
res := method.Call([]reflect.Value{in})
req := res[0].Interface().(*request.Request)
req.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Waiter"))
err := req.Send()
for _, a := range w.Acceptors {
if err != nil && a.Matcher != "error" {
// Only matcher error is valid if there is a request error
continue
}
result := false
var vals []interface{}
switch a.Matcher {
case "pathAll", "path":
// Require all matches to be equal for result to match
vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
result = true
for _, val := range vals {
if !awsutil.DeepEqual(val, a.Expected) {
result = false
break
}
}
case "pathAny":
// Only a single match needs to equal for the result to match
vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
for _, val := range vals {
if awsutil.DeepEqual(val, a.Expected) {
result = true
break
}
}
case "status":
s := a.Expected.(int)
result = s == req.HTTPResponse.StatusCode
case "error":
if aerr, ok := err.(awserr.Error); ok {
result = aerr.Code() == a.Expected.(string)
}
case "pathList":
// ignored matcher
default:
logf(client, "WARNING: Waiter for %s encountered unexpected matcher: %s",
w.Config.Operation, a.Matcher)
}
if !result {
// If there was no matching result found there is nothing more to do
// for this response, retry the request.
continue
}
switch a.State {
case "success":
// waiter completed
return nil
case "failure":
// Waiter failure state triggered
return awserr.New("ResourceNotReady",
fmt.Sprintf("failed waiting for successful resource state"), err)
case "retry":
// clear the error and retry the operation
err = nil
default:
logf(client, "WARNING: Waiter for %s encountered unexpected state: %s",
w.Config.Operation, a.State)
}
}
if err != nil {
return err
}
time.Sleep(time.Second * time.Duration(w.Delay))
}
return awserr.New("ResourceNotReady",
fmt.Sprintf("exceeded %d wait attempts", w.MaxAttempts), nil)
}
func logf(client reflect.Value, msg string, args ...interface{}) {
cfgVal := client.FieldByName("Config")
if !cfgVal.IsValid() {
return
}
if cfg, ok := cfgVal.Interface().(*aws.Config); ok && cfg.Logger != nil {
cfg.Logger.Log(fmt.Sprintf(msg, args...))
}
}

View File

@ -519,6 +519,7 @@ func (c *AutoScaling) DescribeAutoScalingGroups(input *DescribeAutoScalingGroups
func (c *AutoScaling) DescribeAutoScalingGroupsPages(input *DescribeAutoScalingGroupsInput, fn func(p *DescribeAutoScalingGroupsOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribeAutoScalingGroupsRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribeAutoScalingGroupsOutput), lastPage)
})
@ -560,6 +561,7 @@ func (c *AutoScaling) DescribeAutoScalingInstances(input *DescribeAutoScalingIns
func (c *AutoScaling) DescribeAutoScalingInstancesPages(input *DescribeAutoScalingInstancesInput, fn func(p *DescribeAutoScalingInstancesOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribeAutoScalingInstancesRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribeAutoScalingInstancesOutput), lastPage)
})
@ -628,6 +630,7 @@ func (c *AutoScaling) DescribeLaunchConfigurations(input *DescribeLaunchConfigur
func (c *AutoScaling) DescribeLaunchConfigurationsPages(input *DescribeLaunchConfigurationsInput, fn func(p *DescribeLaunchConfigurationsOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribeLaunchConfigurationsRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribeLaunchConfigurationsOutput), lastPage)
})
@ -780,6 +783,7 @@ func (c *AutoScaling) DescribeNotificationConfigurations(input *DescribeNotifica
func (c *AutoScaling) DescribeNotificationConfigurationsPages(input *DescribeNotificationConfigurationsInput, fn func(p *DescribeNotificationConfigurationsOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribeNotificationConfigurationsRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribeNotificationConfigurationsOutput), lastPage)
})
@ -820,6 +824,7 @@ func (c *AutoScaling) DescribePolicies(input *DescribePoliciesInput) (*DescribeP
func (c *AutoScaling) DescribePoliciesPages(input *DescribePoliciesInput, fn func(p *DescribePoliciesOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribePoliciesRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribePoliciesOutput), lastPage)
})
@ -863,6 +868,7 @@ func (c *AutoScaling) DescribeScalingActivities(input *DescribeScalingActivities
func (c *AutoScaling) DescribeScalingActivitiesPages(input *DescribeScalingActivitiesInput, fn func(p *DescribeScalingActivitiesOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribeScalingActivitiesRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribeScalingActivitiesOutput), lastPage)
})
@ -931,6 +937,7 @@ func (c *AutoScaling) DescribeScheduledActions(input *DescribeScheduledActionsIn
func (c *AutoScaling) DescribeScheduledActionsPages(input *DescribeScheduledActionsInput, fn func(p *DescribeScheduledActionsOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribeScheduledActionsRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribeScheduledActionsOutput), lastPage)
})
@ -980,6 +987,7 @@ func (c *AutoScaling) DescribeTags(input *DescribeTagsInput) (*DescribeTagsOutpu
func (c *AutoScaling) DescribeTagsPages(input *DescribeTagsInput, fn func(p *DescribeTagsOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribeTagsRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribeTagsOutput), lastPage)
})

View File

@ -218,3 +218,5 @@ type AutoScalingAPI interface {
UpdateAutoScalingGroup(*autoscaling.UpdateAutoScalingGroupInput) (*autoscaling.UpdateAutoScalingGroupOutput, error)
}
var _ AutoScalingAPI = (*autoscaling.AutoScaling)(nil)

View File

@ -4,10 +4,9 @@ package autoscaling
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/service"
"github.com/aws/aws-sdk-go/aws/service/serviceinfo"
"github.com/aws/aws-sdk-go/private/protocol/query"
"github.com/aws/aws-sdk-go/private/signer/v4"
)
@ -15,40 +14,64 @@ import (
// Auto Scaling is designed to automatically launch or terminate EC2 instances
// based on user-defined policies, schedules, and health checks. Use this service
// in conjunction with the Amazon CloudWatch and Elastic Load Balancing services.
//The service client's operations are safe to be used concurrently.
// It is not safe to mutate any of the client's properties though.
type AutoScaling struct {
*service.Service
*client.Client
}
// Used for custom service initialization logic
var initService func(*service.Service)
// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
// New returns a new AutoScaling client.
func New(config *aws.Config) *AutoScaling {
service := &service.Service{
ServiceInfo: serviceinfo.ServiceInfo{
Config: defaults.DefaultConfig.Merge(config),
ServiceName: "autoscaling",
// A ServiceName is the name of the service the client will make API calls to.
const ServiceName = "autoscaling"
// New creates a new instance of the AutoScaling client with a session.
// If additional configuration is needed for the client instance use the optional
// aws.Config parameter to add your extra config.
//
// Example:
// // Create a AutoScaling client from just a session.
// svc := autoscaling.New(mySession)
//
// // Create a AutoScaling client with additional configuration
// svc := autoscaling.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *AutoScaling {
c := p.ClientConfig(ServiceName, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
}
// newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string) *AutoScaling {
svc := &AutoScaling{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "2011-01-01",
},
handlers,
),
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(query.Build)
service.Handlers.Unmarshal.PushBack(query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(query.UnmarshalError)
svc.Handlers.Sign.PushBack(v4.Sign)
svc.Handlers.Build.PushBack(query.Build)
svc.Handlers.Unmarshal.PushBack(query.Unmarshal)
svc.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta)
svc.Handlers.UnmarshalError.PushBack(query.UnmarshalError)
// Run custom service initialization if present
if initService != nil {
initService(service)
// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
return &AutoScaling{service}
return svc
}
// newRequest creates a new request for a AutoScaling operation and runs any

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,10 @@ package ec2
import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/endpoints"
)
func init() {
@ -20,38 +22,34 @@ func fillPresignedURL(r *request.Request) {
return
}
params := r.Params.(*CopySnapshotInput)
origParams := r.Params.(*CopySnapshotInput)
// Stop if PresignedURL/DestinationRegion is set
if params.PresignedUrl != nil || params.DestinationRegion != nil {
if origParams.PresignedUrl != nil || origParams.DestinationRegion != nil {
return
}
// First generate a copy of parameters
r.Params = awsutil.CopyOf(r.Params)
params = r.Params.(*CopySnapshotInput)
origParams.DestinationRegion = r.Config.Region
newParams := awsutil.CopyOf(r.Params).(*CopySnapshotInput)
// Set destination region. Avoids infinite handler loop.
// Also needed to sign sub-request.
params.DestinationRegion = r.Service.Config.Region
// Create a new client pointing at source region.
// We will use this to presign the CopySnapshot request against
// the source region
config := r.Service.Config.Copy().
// Create a new request based on the existing request. We will use this to
// presign the CopySnapshot request against the source region.
cfg := r.Config.Copy(aws.NewConfig().
WithEndpoint("").
WithRegion(*params.SourceRegion)
WithRegion(aws.StringValue(origParams.SourceRegion)))
client := New(config)
clientInfo := r.ClientInfo
clientInfo.Endpoint, clientInfo.SigningRegion = endpoints.EndpointForRegion(
clientInfo.ServiceName, aws.StringValue(cfg.Region), aws.BoolValue(cfg.DisableSSL))
// Presign a CopySnapshot request with modified params
req, _ := client.CopySnapshotRequest(params)
url, err := req.Presign(300 * time.Second) // 5 minutes should be enough.
req := request.New(*cfg, clientInfo, r.Handlers, r.Retryer, r.Operation, newParams, r.Data)
url, err := req.Presign(5 * time.Minute) // 5 minutes should be enough.
if err != nil { // bubble error back up to original request
r.Error = err
return
}
// We have our URL, set it on params
params.PresignedUrl = &url
origParams.PresignedUrl = &url
}

View File

@ -18,6 +18,10 @@ type EC2API interface {
AllocateAddress(*ec2.AllocateAddressInput) (*ec2.AllocateAddressOutput, error)
AllocateHostsRequest(*ec2.AllocateHostsInput) (*request.Request, *ec2.AllocateHostsOutput)
AllocateHosts(*ec2.AllocateHostsInput) (*ec2.AllocateHostsOutput, error)
AssignPrivateIpAddressesRequest(*ec2.AssignPrivateIpAddressesInput) (*request.Request, *ec2.AssignPrivateIpAddressesOutput)
AssignPrivateIpAddresses(*ec2.AssignPrivateIpAddressesInput) (*ec2.AssignPrivateIpAddressesOutput, error)
@ -346,6 +350,14 @@ type EC2API interface {
DescribeFlowLogs(*ec2.DescribeFlowLogsInput) (*ec2.DescribeFlowLogsOutput, error)
DescribeHostsRequest(*ec2.DescribeHostsInput) (*request.Request, *ec2.DescribeHostsOutput)
DescribeHosts(*ec2.DescribeHostsInput) (*ec2.DescribeHostsOutput, error)
DescribeIdFormatRequest(*ec2.DescribeIdFormatInput) (*request.Request, *ec2.DescribeIdFormatOutput)
DescribeIdFormat(*ec2.DescribeIdFormatInput) (*ec2.DescribeIdFormatOutput, error)
DescribeImageAttributeRequest(*ec2.DescribeImageAttributeInput) (*request.Request, *ec2.DescribeImageAttributeOutput)
DescribeImageAttribute(*ec2.DescribeImageAttributeInput) (*ec2.DescribeImageAttributeOutput, error)
@ -486,6 +498,8 @@ type EC2API interface {
DescribeTags(*ec2.DescribeTagsInput) (*ec2.DescribeTagsOutput, error)
DescribeTagsPages(*ec2.DescribeTagsInput, func(*ec2.DescribeTagsOutput, bool) bool) error
DescribeVolumeAttributeRequest(*ec2.DescribeVolumeAttributeInput) (*request.Request, *ec2.DescribeVolumeAttributeOutput)
DescribeVolumeAttribute(*ec2.DescribeVolumeAttributeInput) (*ec2.DescribeVolumeAttributeOutput, error)
@ -610,6 +624,14 @@ type EC2API interface {
ImportVolume(*ec2.ImportVolumeInput) (*ec2.ImportVolumeOutput, error)
ModifyHostsRequest(*ec2.ModifyHostsInput) (*request.Request, *ec2.ModifyHostsOutput)
ModifyHosts(*ec2.ModifyHostsInput) (*ec2.ModifyHostsOutput, error)
ModifyIdFormatRequest(*ec2.ModifyIdFormatInput) (*request.Request, *ec2.ModifyIdFormatOutput)
ModifyIdFormat(*ec2.ModifyIdFormatInput) (*ec2.ModifyIdFormatOutput, error)
ModifyImageAttributeRequest(*ec2.ModifyImageAttributeInput) (*request.Request, *ec2.ModifyImageAttributeOutput)
ModifyImageAttribute(*ec2.ModifyImageAttributeInput) (*ec2.ModifyImageAttributeOutput, error)
@ -618,6 +640,10 @@ type EC2API interface {
ModifyInstanceAttribute(*ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error)
ModifyInstancePlacementRequest(*ec2.ModifyInstancePlacementInput) (*request.Request, *ec2.ModifyInstancePlacementOutput)
ModifyInstancePlacement(*ec2.ModifyInstancePlacementInput) (*ec2.ModifyInstancePlacementOutput, error)
ModifyNetworkInterfaceAttributeRequest(*ec2.ModifyNetworkInterfaceAttributeInput) (*request.Request, *ec2.ModifyNetworkInterfaceAttributeOutput)
ModifyNetworkInterfaceAttribute(*ec2.ModifyNetworkInterfaceAttributeInput) (*ec2.ModifyNetworkInterfaceAttributeOutput, error)
@ -678,6 +704,10 @@ type EC2API interface {
ReleaseAddress(*ec2.ReleaseAddressInput) (*ec2.ReleaseAddressOutput, error)
ReleaseHostsRequest(*ec2.ReleaseHostsInput) (*request.Request, *ec2.ReleaseHostsOutput)
ReleaseHosts(*ec2.ReleaseHostsInput) (*ec2.ReleaseHostsOutput, error)
ReplaceNetworkAclAssociationRequest(*ec2.ReplaceNetworkAclAssociationInput) (*request.Request, *ec2.ReplaceNetworkAclAssociationOutput)
ReplaceNetworkAclAssociation(*ec2.ReplaceNetworkAclAssociationInput) (*ec2.ReplaceNetworkAclAssociationOutput, error)
@ -758,3 +788,5 @@ type EC2API interface {
UnmonitorInstances(*ec2.UnmonitorInstancesInput) (*ec2.UnmonitorInstancesOutput, error)
}
var _ EC2API = (*ec2.EC2)(nil)

View File

@ -4,10 +4,9 @@ package ec2
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/service"
"github.com/aws/aws-sdk-go/aws/service/serviceinfo"
"github.com/aws/aws-sdk-go/private/protocol/ec2query"
"github.com/aws/aws-sdk-go/private/signer/v4"
)
@ -16,40 +15,64 @@ import (
// in the Amazon Web Services (AWS) cloud. Using Amazon EC2 eliminates your
// need to invest in hardware up front, so you can develop and deploy applications
// faster.
//The service client's operations are safe to be used concurrently.
// It is not safe to mutate any of the client's properties though.
type EC2 struct {
*service.Service
*client.Client
}
// Used for custom service initialization logic
var initService func(*service.Service)
// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
// New returns a new EC2 client.
func New(config *aws.Config) *EC2 {
service := &service.Service{
ServiceInfo: serviceinfo.ServiceInfo{
Config: defaults.DefaultConfig.Merge(config),
ServiceName: "ec2",
// A ServiceName is the name of the service the client will make API calls to.
const ServiceName = "ec2"
// New creates a new instance of the EC2 client with a session.
// If additional configuration is needed for the client instance use the optional
// aws.Config parameter to add your extra config.
//
// Example:
// // Create a EC2 client from just a session.
// svc := ec2.New(mySession)
//
// // Create a EC2 client with additional configuration
// svc := ec2.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2 {
c := p.ClientConfig(ServiceName, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
}
// newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string) *EC2 {
svc := &EC2{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "2015-10-01",
},
handlers,
),
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
svc.Handlers.Sign.PushBack(v4.Sign)
svc.Handlers.Build.PushBack(ec2query.Build)
svc.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
svc.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
svc.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
// Run custom service initialization if present
if initService != nil {
initService(service)
// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
return &EC2{service}
return svc
}
// newRequest creates a new request for a EC2 operation and runs any

View File

@ -0,0 +1,761 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package ec2
import (
"github.com/aws/aws-sdk-go/private/waiter"
)
func (c *EC2) WaitUntilBundleTaskComplete(input *DescribeBundleTasksInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeBundleTasks",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "BundleTasks[].State",
Expected: "complete",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "BundleTasks[].State",
Expected: "failed",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilConversionTaskCancelled(input *DescribeConversionTasksInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeConversionTasks",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "ConversionTasks[].State",
Expected: "cancelled",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilConversionTaskCompleted(input *DescribeConversionTasksInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeConversionTasks",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "ConversionTasks[].State",
Expected: "completed",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "ConversionTasks[].State",
Expected: "cancelled",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "ConversionTasks[].State",
Expected: "cancelling",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilConversionTaskDeleted(input *DescribeConversionTasksInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeConversionTasks",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "ConversionTasks[].State",
Expected: "deleted",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilCustomerGatewayAvailable(input *DescribeCustomerGatewaysInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeCustomerGateways",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "CustomerGateways[].State",
Expected: "available",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "CustomerGateways[].State",
Expected: "deleted",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "CustomerGateways[].State",
Expected: "deleting",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilExportTaskCancelled(input *DescribeExportTasksInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeExportTasks",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "ExportTasks[].State",
Expected: "cancelled",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilExportTaskCompleted(input *DescribeExportTasksInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeExportTasks",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "ExportTasks[].State",
Expected: "completed",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilImageAvailable(input *DescribeImagesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeImages",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Images[].State",
Expected: "available",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Images[].State",
Expected: "failed",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilInstanceExists(input *DescribeInstancesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeInstances",
Delay: 5,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "status",
Argument: "",
Expected: 200,
},
{
State: "retry",
Matcher: "error",
Argument: "",
Expected: "InvalidInstanceIDNotFound",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilInstanceRunning(input *DescribeInstancesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeInstances",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Reservations[].Instances[].State.Name",
Expected: "running",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Reservations[].Instances[].State.Name",
Expected: "shutting-down",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Reservations[].Instances[].State.Name",
Expected: "terminated",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Reservations[].Instances[].State.Name",
Expected: "stopping",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilInstanceStatusOk(input *DescribeInstanceStatusInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeInstanceStatus",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "InstanceStatuses[].InstanceStatus.Status",
Expected: "ok",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilInstanceStopped(input *DescribeInstancesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeInstances",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Reservations[].Instances[].State.Name",
Expected: "stopped",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Reservations[].Instances[].State.Name",
Expected: "pending",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Reservations[].Instances[].State.Name",
Expected: "terminated",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilInstanceTerminated(input *DescribeInstancesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeInstances",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Reservations[].Instances[].State.Name",
Expected: "terminated",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Reservations[].Instances[].State.Name",
Expected: "pending",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Reservations[].Instances[].State.Name",
Expected: "stopping",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilKeyPairExists(input *DescribeKeyPairsInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeKeyPairs",
Delay: 5,
MaxAttempts: 6,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "length(KeyPairs[].KeyName) > `0`",
Expected: true,
},
{
State: "retry",
Matcher: "error",
Argument: "",
Expected: "InvalidKeyPairNotFound",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilNetworkInterfaceAvailable(input *DescribeNetworkInterfacesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeNetworkInterfaces",
Delay: 20,
MaxAttempts: 10,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "NetworkInterfaces[].Status",
Expected: "available",
},
{
State: "failure",
Matcher: "error",
Argument: "",
Expected: "InvalidNetworkInterfaceIDNotFound",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilPasswordDataAvailable(input *GetPasswordDataInput) error {
waiterCfg := waiter.Config{
Operation: "GetPasswordData",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "path",
Argument: "length(PasswordData) > `0`",
Expected: true,
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilSnapshotCompleted(input *DescribeSnapshotsInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeSnapshots",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Snapshots[].State",
Expected: "completed",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilSpotInstanceRequestFulfilled(input *DescribeSpotInstanceRequestsInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeSpotInstanceRequests",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "SpotInstanceRequests[].Status.Code",
Expected: "fulfilled",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "SpotInstanceRequests[].Status.Code",
Expected: "schedule-expired",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "SpotInstanceRequests[].Status.Code",
Expected: "canceled-before-fulfillment",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "SpotInstanceRequests[].Status.Code",
Expected: "bad-parameters",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "SpotInstanceRequests[].Status.Code",
Expected: "system-error",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilSubnetAvailable(input *DescribeSubnetsInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeSubnets",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Subnets[].State",
Expected: "available",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilSystemStatusOk(input *DescribeInstanceStatusInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeInstanceStatus",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "InstanceStatuses[].SystemStatus.Status",
Expected: "ok",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilVolumeAvailable(input *DescribeVolumesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeVolumes",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Volumes[].State",
Expected: "available",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Volumes[].State",
Expected: "deleted",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilVolumeDeleted(input *DescribeVolumesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeVolumes",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Volumes[].State",
Expected: "deleted",
},
{
State: "success",
Matcher: "error",
Argument: "",
Expected: "InvalidVolumeNotFound",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilVolumeInUse(input *DescribeVolumesInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeVolumes",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Volumes[].State",
Expected: "in-use",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "Volumes[].State",
Expected: "deleted",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilVpcAvailable(input *DescribeVpcsInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeVpcs",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "Vpcs[].State",
Expected: "available",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilVpnConnectionAvailable(input *DescribeVpnConnectionsInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeVpnConnections",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "VpnConnections[].State",
Expected: "available",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "VpnConnections[].State",
Expected: "deleting",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "VpnConnections[].State",
Expected: "deleted",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *EC2) WaitUntilVpnConnectionDeleted(input *DescribeVpnConnectionsInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeVpnConnections",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "VpnConnections[].State",
Expected: "deleted",
},
{
State: "failure",
Matcher: "pathAny",
Argument: "VpnConnections[].State",
Expected: "pending",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}

View File

@ -472,10 +472,12 @@ func (c *ELB) DescribeInstanceHealthRequest(input *DescribeInstanceHealthInput)
return
}
// Describes the state of the specified instances registered with the specified
// Describes the state of the specified instances with respect to the specified
// load balancer. If no instances are specified, the call describes the state
// of all instances registered with the load balancer, not including any terminated
// instances.
// of all instances that are currently registered with the load balancer. If
// instances are specified, their state is returned even if they are no longer
// registered with the load balancer. The state of terminated instances is not
// returned.
func (c *ELB) DescribeInstanceHealth(input *DescribeInstanceHealthInput) (*DescribeInstanceHealthOutput, error) {
req, out := c.DescribeInstanceHealthRequest(input)
err := req.Send()
@ -609,6 +611,7 @@ func (c *ELB) DescribeLoadBalancers(input *DescribeLoadBalancersInput) (*Describ
func (c *ELB) DescribeLoadBalancersPages(input *DescribeLoadBalancersInput, fn func(p *DescribeLoadBalancersOutput, lastPage bool) (shouldContinue bool)) error {
page, _ := c.DescribeLoadBalancersRequest(input)
page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
return page.EachPage(func(p interface{}, lastPage bool) bool {
return fn(p.(*DescribeLoadBalancersOutput), lastPage)
})
@ -814,8 +817,9 @@ func (c *ELB) RegisterInstancesWithLoadBalancerRequest(input *RegisterInstancesW
// with the load balancer in the VPC.
//
// Note that RegisterInstanceWithLoadBalancer completes when the request has
// been registered. Instance registration happens shortly afterwards. To check
// the state of the registered instances, use DescribeLoadBalancers or DescribeInstanceHealth.
// been registered. Instance registration takes a little time to complete. To
// check the state of the registered instances, use DescribeLoadBalancers or
// DescribeInstanceHealth.
//
// After the instance is registered, it starts receiving traffic and requests
// from the load balancer. Any instance that is not in one of the Availability
@ -1310,8 +1314,9 @@ type CreateAppCookieStickinessPolicyInput struct {
// The name of the load balancer.
LoadBalancerName *string `type:"string" required:"true"`
// The name of the policy being created. This name must be unique within the
// set of policies for this load balancer.
// The name of the policy being created. Policy names must consist of alphanumeric
// characters and dashes (-). This name must be unique within the set of policies
// for this load balancer.
PolicyName *string `type:"string" required:"true"`
metadataCreateAppCookieStickinessPolicyInput `json:"-" xml:"-"`
@ -1358,8 +1363,9 @@ type CreateLBCookieStickinessPolicyInput struct {
// The name of the load balancer.
LoadBalancerName *string `type:"string" required:"true"`
// The name of the policy being created. This name must be unique within the
// set of policies for this load balancer.
// The name of the policy being created. Policy names must consist of alphanumeric
// characters and dashes (-). This name must be unique within the set of policies
// for this load balancer.
PolicyName *string `type:"string" required:"true"`
metadataCreateLBCookieStickinessPolicyInput `json:"-" xml:"-"`
@ -1415,9 +1421,9 @@ type CreateLoadBalancerInput struct {
// The name of the load balancer.
//
// This name must be unique within your AWS account, must have a maximum of
// 32 characters, must contain only alphanumeric characters or hyphens, and
// cannot begin or end with a hyphen.
// This name must be unique within your set of load balancers for the region,
// must have a maximum of 32 characters, must contain only alphanumeric characters
// or hyphens, and cannot begin or end with a hyphen.
LoadBalancerName *string `type:"string" required:"true"`
// The type of a load balancer. Valid only for load balancers in a VPC.
@ -2364,8 +2370,9 @@ type Listener struct {
// is HTTP or TCP, the listener's InstanceProtocol must be HTTP or TCP.
InstanceProtocol *string `type:"string"`
// The port on which the load balancer is listening. The supported ports are:
// 25, 80, 443, 465, 587, and 1024-65535.
// The port on which the load balancer is listening. On EC2-VPC, you can specify
// any port from the range 1-65535. On EC2-Classic, you can specify any port
// from the following list: 25, 80, 443, 465, 587, 1024-65535.
LoadBalancerPort *int64 `type:"integer" required:"true"`
// The load balancer transport protocol to use for routing: HTTP, HTTPS, TCP,

View File

@ -124,3 +124,5 @@ type ELBAPI interface {
SetLoadBalancerPoliciesOfListener(*elb.SetLoadBalancerPoliciesOfListenerInput) (*elb.SetLoadBalancerPoliciesOfListenerOutput, error)
}
var _ ELBAPI = (*elb.ELB)(nil)

View File

@ -4,10 +4,9 @@ package elb
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/service"
"github.com/aws/aws-sdk-go/aws/service/serviceinfo"
"github.com/aws/aws-sdk-go/private/protocol/query"
"github.com/aws/aws-sdk-go/private/signer/v4"
)
@ -25,40 +24,64 @@ import (
// All Elastic Load Balancing operations are idempotent, which means that they
// complete at most one time. If you repeat an operation, it succeeds with a
// 200 OK response code.
//The service client's operations are safe to be used concurrently.
// It is not safe to mutate any of the client's properties though.
type ELB struct {
*service.Service
*client.Client
}
// Used for custom service initialization logic
var initService func(*service.Service)
// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
// New returns a new ELB client.
func New(config *aws.Config) *ELB {
service := &service.Service{
ServiceInfo: serviceinfo.ServiceInfo{
Config: defaults.DefaultConfig.Merge(config),
ServiceName: "elasticloadbalancing",
// A ServiceName is the name of the service the client will make API calls to.
const ServiceName = "elasticloadbalancing"
// New creates a new instance of the ELB client with a session.
// If additional configuration is needed for the client instance use the optional
// aws.Config parameter to add your extra config.
//
// Example:
// // Create a ELB client from just a session.
// svc := elb.New(mySession)
//
// // Create a ELB client with additional configuration
// svc := elb.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *ELB {
c := p.ClientConfig(ServiceName, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
}
// newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string) *ELB {
svc := &ELB{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "2012-06-01",
},
handlers,
),
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(query.Build)
service.Handlers.Unmarshal.PushBack(query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(query.UnmarshalError)
svc.Handlers.Sign.PushBack(v4.Sign)
svc.Handlers.Build.PushBack(query.Build)
svc.Handlers.Unmarshal.PushBack(query.Unmarshal)
svc.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta)
svc.Handlers.UnmarshalError.PushBack(query.UnmarshalError)
// Run custom service initialization if present
if initService != nil {
initService(service)
// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
return &ELB{service}
return svc
}
// newRequest creates a new request for a ELB operation and runs any

View File

@ -0,0 +1,53 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package elb
import (
"github.com/aws/aws-sdk-go/private/waiter"
)
func (c *ELB) WaitUntilAnyInstanceInService(input *DescribeInstanceHealthInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeInstanceHealth",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAny",
Argument: "InstanceStates[].State",
Expected: "InService",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
func (c *ELB) WaitUntilInstanceInService(input *DescribeInstanceHealthInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeInstanceHealth",
Delay: 15,
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "InstanceStates[].State",
Expected: "InService",
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}

View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,13 @@
Copyright 2015 James Saryerwinnie
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.

View File

@ -0,0 +1,4 @@
testdata/conf_out.ini
ini.sublime-project
ini.sublime-workspace
testdata/conf_reflect.ini

191
Godeps/_workspace/src/github.com/go-ini/ini/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

578
Godeps/_workspace/src/github.com/go-ini/ini/README.md generated vendored Normal file
View File

@ -0,0 +1,578 @@
ini [![Build Status](https://drone.io/github.com/go-ini/ini/status.png)](https://drone.io/github.com/go-ini/ini/latest) [![](http://gocover.io/_badge/github.com/go-ini/ini)](http://gocover.io/github.com/go-ini/ini)
===
![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
Package ini provides INI file read and write functionality in Go.
[简体中文](README_ZH.md)
## Feature
- Load multiple data sources(`[]byte` or file) with overwrites.
- Read with recursion values.
- Read with parent-child sections.
- Read with auto-increment key names.
- Read with multiple-line values.
- Read with tons of helper methods.
- Read and convert values to Go types.
- Read and **WRITE** comments of sections and keys.
- Manipulate sections, keys and comments with ease.
- Keep sections and keys in order as you parse and save.
## Installation
go get gopkg.in/ini.v1
## Getting Started
### Loading from data sources
A **Data Source** is either raw data in type `[]byte` or a file name with type `string` and you can load **as many as** data sources you want. Passing other types will simply return an error.
```go
cfg, err := ini.Load([]byte("raw data"), "filename")
```
Or start with an empty object:
```go
cfg := ini.Empty()
```
When you cannot decide how many data sources to load at the beginning, you still able to **Append()** them later.
```go
err := cfg.Append("other file", []byte("other raw data"))
```
### Working with sections
To get a section, you would need to:
```go
section, err := cfg.GetSection("section name")
```
For a shortcut for default section, just give an empty string as name:
```go
section, err := cfg.GetSection("")
```
When you're pretty sure the section exists, following code could make your life easier:
```go
section := cfg.Section("")
```
What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
To create a new section:
```go
err := cfg.NewSection("new section")
```
To get a list of sections or section names:
```go
sections := cfg.Sections()
names := cfg.SectionStrings()
```
### Working with keys
To get a key under a section:
```go
key, err := cfg.Section("").GetKey("key name")
```
Same rule applies to key operations:
```go
key := cfg.Section("").Key("key name")
```
To check if a key exists:
```go
yes := cfg.Section("").HasKey("key name")
```
To create a new key:
```go
err := cfg.Section("").NewKey("name", "value")
```
To get a list of keys or key names:
```go
keys := cfg.Section("").Keys()
names := cfg.Section("").KeyStrings()
```
To get a clone hash of keys and corresponding values:
```go
hash := cfg.GetSection("").KeysHash()
```
### Working with values
To get a string value:
```go
val := cfg.Section("").Key("key name").String()
```
To validate key value on the fly:
```go
val := cfg.Section("").Key("key name").Validate(func(in string) string {
if len(in) == 0 {
return "default"
}
return in
})
```
If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance):
```go
val := cfg.Section("").Key("key name").Value()
```
To check if raw value exists:
```go
yes := cfg.Section("").HasValue("test value")
```
To get value with types:
```go
// For boolean values:
// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, ON, on, On
// false when value is: 0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off
v, err = cfg.Section("").Key("BOOL").Bool()
v, err = cfg.Section("").Key("FLOAT64").Float64()
v, err = cfg.Section("").Key("INT").Int()
v, err = cfg.Section("").Key("INT64").Int64()
v, err = cfg.Section("").Key("UINT").Uint()
v, err = cfg.Section("").Key("UINT64").Uint64()
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
v, err = cfg.Section("").Key("TIME").Time() // RFC3339
v = cfg.Section("").Key("BOOL").MustBool()
v = cfg.Section("").Key("FLOAT64").MustFloat64()
v = cfg.Section("").Key("INT").MustInt()
v = cfg.Section("").Key("INT64").MustInt64()
v = cfg.Section("").Key("UINT").MustUint()
v = cfg.Section("").Key("UINT64").MustUint64()
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
v = cfg.Section("").Key("TIME").MustTime() // RFC3339
// Methods start with Must also accept one argument for default value
// when key not found or fail to parse value to given type.
// Except method MustString, which you have to pass a default value.
v = cfg.Section("").Key("String").MustString("default")
v = cfg.Section("").Key("BOOL").MustBool(true)
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
v = cfg.Section("").Key("INT").MustInt(10)
v = cfg.Section("").Key("INT64").MustInt64(99)
v = cfg.Section("").Key("UINT").MustUint(3)
v = cfg.Section("").Key("UINT64").MustUint64(6)
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
```
What if my value is three-line long?
```ini
[advance]
ADDRESS = """404 road,
NotFound, State, 5000
Earth"""
```
Not a problem!
```go
cfg.Section("advance").Key("ADDRESS").String()
/* --- start ---
404 road,
NotFound, State, 5000
Earth
------ end --- */
```
That's cool, how about continuation lines?
```ini
[advance]
two_lines = how about \
continuation lines?
lots_of_lines = 1 \
2 \
3 \
4
```
Piece of cake!
```go
cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
```
Note that single quotes around values will be stripped:
```ini
foo = "some value" // foo: some value
bar = 'some value' // bar: some value
```
That's all? Hmm, no.
#### Helper methods of working with values
To get value with given candidates:
```go
v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
```
Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
To validate value in a given range:
```go
vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
```
To auto-split value into slice:
```go
vals = cfg.Section("").Key("STRINGS").Strings(",")
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
vals = cfg.Section("").Key("INTS").Ints(",")
vals = cfg.Section("").Key("INT64S").Int64s(",")
vals = cfg.Section("").Key("UINTS").Uints(",")
vals = cfg.Section("").Key("UINT64S").Uint64s(",")
vals = cfg.Section("").Key("TIMES").Times(",")
```
### Save your configuration
Finally, it's time to save your configuration to somewhere.
A typical way to save configuration is writing it to a file:
```go
// ...
err = cfg.SaveTo("my.ini")
err = cfg.SaveToIndent("my.ini", "\t")
```
Another way to save is writing to a `io.Writer` interface:
```go
// ...
cfg.WriteTo(writer)
cfg.WriteToIndent(writer, "\t")
```
## Advanced Usage
### Recursive Values
For all value of keys, there is a special syntax `%(<name>)s`, where `<name>` is the key name in same section or default section, and `%(<name>)s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.
```ini
NAME = ini
[author]
NAME = Unknwon
GITHUB = https://github.com/%(NAME)s
[package]
FULL_NAME = github.com/go-ini/%(NAME)s
```
```go
cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
```
### Parent-child Sections
You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.
```ini
NAME = ini
VERSION = v1
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
[package]
CLONE_URL = https://%(IMPORT_PATH)s
[package.sub]
```
```go
cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
```
### Auto-increment Key Names
If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
```ini
[features]
-: Support read/write comments of keys and sections
-: Support auto-increment of key names
-: Support load multiple files to overwrite key values
```
```go
cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
```
### Map To Struct
Want more objective way to play with INI? Cool.
```ini
Name = Unknwon
age = 21
Male = true
Born = 1993-01-01T20:17:05Z
[Note]
Content = Hi is a good man!
Cities = HangZhou, Boston
```
```go
type Note struct {
Content string
Cities []string
}
type Person struct {
Name string
Age int `ini:"age"`
Male bool
Born time.Time
Note
Created time.Time `ini:"-"`
}
func main() {
cfg, err := ini.Load("path/to/ini")
// ...
p := new(Person)
err = cfg.MapTo(p)
// ...
// Things can be simpler.
err = ini.MapTo(p, "path/to/ini")
// ...
// Just map a section? Fine.
n := new(Note)
err = cfg.Section("Note").MapTo(n)
// ...
}
```
Can I have default value for field? Absolutely.
Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
```go
// ...
p := &Person{
Name: "Joe",
}
// ...
```
It's really cool, but what's the point if you can't give me my file back from struct?
### Reflect From Struct
Why not?
```go
type Embeded struct {
Dates []time.Time `delim:"|"`
Places []string
None []int
}
type Author struct {
Name string `ini:"NAME"`
Male bool
Age int
GPA float64
NeverMind string `ini:"-"`
*Embeded
}
func main() {
a := &Author{"Unknwon", true, 21, 2.8, "",
&Embeded{
[]time.Time{time.Now(), time.Now()},
[]string{"HangZhou", "Boston"},
[]int{},
}}
cfg := ini.Empty()
err = ini.ReflectFrom(cfg, a)
// ...
}
```
So, what do I get?
```ini
NAME = Unknwon
Male = true
Age = 21
GPA = 2.8
[Embeded]
Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
Places = HangZhou,Boston
None =
```
#### Name Mapper
To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual section and key name.
There are 2 built-in name mappers:
- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
To use them:
```go
type Info struct {
PackageName string
}
func main() {
err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("packag_name=ini"))
// ...
cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
// ...
info := new(Info)
cfg.NameMapper = ini.AllCapsUnderscore
err = cfg.MapTo(info)
// ...
}
```
Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
#### Other Notes On Map/Reflect
Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
```go
type Child struct {
Age string
}
type Parent struct {
Name string
Child
}
type Config struct {
City string
Parent
}
```
Example configuration:
```ini
City = Boston
[Parent]
Name = Unknwon
[Child]
Age = 21
```
What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
```go
type Child struct {
Age string
}
type Parent struct {
Name string
Child `ini:"Parent"`
}
type Config struct {
City string
Parent
}
```
Example configuration:
```ini
City = Boston
[Parent]
Name = Unknwon
Age = 21
```
## Getting Help
- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
- [File An Issue](https://github.com/go-ini/ini/issues/new)
## FAQs
### What does `BlockMode` field do?
By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set `cfg.BlockMode = false` to speed up read operations about **50-70%** faster.
### Why another INI library?
Many people are using my another INI library [goconfig](https://github.com/Unknwon/goconfig), so the reason for this one is I would like to make more Go style code. Also when you set `cfg.BlockMode = false`, this one is about **10-30%** faster.
To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using `gopkg.in` to version my package at this time.(PS: shorter import path)
## License
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.

View File

@ -0,0 +1,565 @@
本包提供了 Go 语言中读写 INI 文件的功能。
## 功能特性
- 支持覆盖加载多个数据源(`[]byte` 或文件)
- 支持递归读取键值
- 支持读取父子分区
- 支持读取自增键名
- 支持读取多行的键值
- 支持大量辅助方法
- 支持在读取时直接转换为 Go 语言类型
- 支持读取和 **写入** 分区和键的注释
- 轻松操作分区、键值和注释
- 在保存文件时分区和键值会保持原有的顺序
## 下载安装
go get gopkg.in/ini.v1
## 开始使用
### 从数据源加载
一个 **数据源** 可以是 `[]byte` 类型的原始数据,或 `string` 类型的文件路径。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
```go
cfg, err := ini.Load([]byte("raw data"), "filename")
```
或者从一个空白的文件开始:
```go
cfg := ini.Empty()
```
当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
```go
err := cfg.Append("other file", []byte("other raw data"))
```
### 操作分区Section
获取指定分区:
```go
section, err := cfg.GetSection("section name")
```
如果您想要获取默认分区,则可以用空字符串代替分区名:
```go
section, err := cfg.GetSection("")
```
当您非常确定某个分区是存在的,可以使用以下简便方法:
```go
section := cfg.Section("")
```
如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
创建一个分区:
```go
err := cfg.NewSection("new section")
```
获取所有分区对象或名称:
```go
sections := cfg.Sections()
names := cfg.SectionStrings()
```
### 操作键Key
获取某个分区下的键:
```go
key, err := cfg.Section("").GetKey("key name")
```
和分区一样,您也可以直接获取键而忽略错误处理:
```go
key := cfg.Section("").Key("key name")
```
判断某个键是否存在:
```go
yes := cfg.Section("").HasKey("key name")
```
创建一个新的键:
```go
err := cfg.Section("").NewKey("name", "value")
```
获取分区下的所有键或键名:
```go
keys := cfg.Section("").Keys()
names := cfg.Section("").KeyStrings()
```
获取分区下的所有键值对的克隆:
```go
hash := cfg.GetSection("").KeysHash()
```
### 操作键值Value
获取一个类型为字符串string的值
```go
val := cfg.Section("").Key("key name").String()
```
获取值的同时通过自定义函数进行处理验证:
```go
val := cfg.Section("").Key("key name").Validate(func(in string) string {
if len(in) == 0 {
return "default"
}
return in
})
```
如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
```go
val := cfg.Section("").Key("key name").Value()
```
判断某个原值是否存在:
```go
yes := cfg.Section("").HasValue("test value")
```
获取其它类型的值:
```go
// 布尔值的规则:
// true 当值为1, t, T, TRUE, true, True, YES, yes, Yes, ON, on, On
// false 当值为0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off
v, err = cfg.Section("").Key("BOOL").Bool()
v, err = cfg.Section("").Key("FLOAT64").Float64()
v, err = cfg.Section("").Key("INT").Int()
v, err = cfg.Section("").Key("INT64").Int64()
v, err = cfg.Section("").Key("UINT").Uint()
v, err = cfg.Section("").Key("UINT64").Uint64()
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
v, err = cfg.Section("").Key("TIME").Time() // RFC3339
v = cfg.Section("").Key("BOOL").MustBool()
v = cfg.Section("").Key("FLOAT64").MustFloat64()
v = cfg.Section("").Key("INT").MustInt()
v = cfg.Section("").Key("INT64").MustInt64()
v = cfg.Section("").Key("UINT").MustUint()
v = cfg.Section("").Key("UINT64").MustUint64()
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
v = cfg.Section("").Key("TIME").MustTime() // RFC3339
// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
// 当键不存在或者转换失败时,则会直接返回该默认值。
// 但是MustString 方法必须传递一个默认值。
v = cfg.Seciont("").Key("String").MustString("default")
v = cfg.Section("").Key("BOOL").MustBool(true)
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
v = cfg.Section("").Key("INT").MustInt(10)
v = cfg.Section("").Key("INT64").MustInt64(99)
v = cfg.Section("").Key("UINT").MustUint(3)
v = cfg.Section("").Key("UINT64").MustUint64(6)
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
```
如果我的值有好多行怎么办?
```ini
[advance]
ADDRESS = """404 road,
NotFound, State, 5000
Earth"""
```
嗯哼?小 case
```go
cfg.Section("advance").Key("ADDRESS").String()
/* --- start ---
404 road,
NotFound, State, 5000
Earth
------ end --- */
```
赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
```ini
[advance]
two_lines = how about \
continuation lines?
lots_of_lines = 1 \
2 \
3 \
4
```
简直是小菜一碟!
```go
cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
```
需要注意的是,值两侧的单引号会被自动剔除:
```ini
foo = "some value" // foo: some value
bar = 'some value' // bar: some value
```
这就是全部了?哈哈,当然不是。
#### 操作键值的辅助方法
获取键值时设定候选值:
```go
v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
```
如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
验证获取的值是否在指定范围内:
```go
vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
```
自动分割键值为切片slice
```go
vals = cfg.Section("").Key("STRINGS").Strings(",")
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
vals = cfg.Section("").Key("INTS").Ints(",")
vals = cfg.Section("").Key("INT64S").Int64s(",")
vals = cfg.Section("").Key("UINTS").Uints(",")
vals = cfg.Section("").Key("UINT64S").Uint64s(",")
vals = cfg.Section("").Key("TIMES").Times(",")
```
### 保存配置
终于到了这个时刻,是时候保存一下配置了。
比较原始的做法是输出配置到某个文件:
```go
// ...
err = cfg.SaveTo("my.ini")
err = cfg.SaveToIndent("my.ini", "\t")
```
另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
```go
// ...
cfg.WriteTo(writer)
cfg.WriteToIndent(writer, "\t")
```
### 高级用法
#### 递归读取键值
在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
```ini
NAME = ini
[author]
NAME = Unknwon
GITHUB = https://github.com/%(NAME)s
[package]
FULL_NAME = github.com/go-ini/%(NAME)s
```
```go
cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
```
#### 读取父子分区
您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
```ini
NAME = ini
VERSION = v1
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
[package]
CLONE_URL = https://%(IMPORT_PATH)s
[package.sub]
```
```go
cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
```
#### 读取自增键名
如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
```ini
[features]
-: Support read/write comments of keys and sections
-: Support auto-increment of key names
-: Support load multiple files to overwrite key values
```
```go
cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
```
### 映射到结构
想要使用更加面向对象的方式玩转 INI 吗?好主意。
```ini
Name = Unknwon
age = 21
Male = true
Born = 1993-01-01T20:17:05Z
[Note]
Content = Hi is a good man!
Cities = HangZhou, Boston
```
```go
type Note struct {
Content string
Cities []string
}
type Person struct {
Name string
Age int `ini:"age"`
Male bool
Born time.Time
Note
Created time.Time `ini:"-"`
}
func main() {
cfg, err := ini.Load("path/to/ini")
// ...
p := new(Person)
err = cfg.MapTo(p)
// ...
// 一切竟可以如此的简单。
err = ini.MapTo(p, "path/to/ini")
// ...
// 嗯哼?只需要映射一个分区吗?
n := new(Note)
err = cfg.Section("Note").MapTo(n)
// ...
}
```
结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
```go
// ...
p := &Person{
Name: "Joe",
}
// ...
```
这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
### 从结构反射
可是,我有说不能吗?
```go
type Embeded struct {
Dates []time.Time `delim:"|"`
Places []string
None []int
}
type Author struct {
Name string `ini:"NAME"`
Male bool
Age int
GPA float64
NeverMind string `ini:"-"`
*Embeded
}
func main() {
a := &Author{"Unknwon", true, 21, 2.8, "",
&Embeded{
[]time.Time{time.Now(), time.Now()},
[]string{"HangZhou", "Boston"},
[]int{},
}}
cfg := ini.Empty()
err = ini.ReflectFrom(cfg, a)
// ...
}
```
瞧瞧,奇迹发生了。
```ini
NAME = Unknwon
Male = true
Age = 21
GPA = 2.8
[Embeded]
Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
Places = HangZhou,Boston
None =
```
#### 名称映射器Name Mapper
为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
目前有 2 款内置的映射器:
- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
使用方法:
```go
type Info struct{
PackageName string
}
func main() {
err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("packag_name=ini"))
// ...
cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
// ...
info := new(Info)
cfg.NameMapper = ini.AllCapsUnderscore
err = cfg.MapTo(info)
// ...
}
```
使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
#### 映射/反射的其它说明
任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
```go
type Child struct {
Age string
}
type Parent struct {
Name string
Child
}
type Config struct {
City string
Parent
}
```
示例配置文件:
```ini
City = Boston
[Parent]
Name = Unknwon
[Child]
Age = 21
```
很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
```go
type Child struct {
Age string
}
type Parent struct {
Name string
Child `ini:"Parent"`
}
type Config struct {
City string
Parent
}
```
示例配置文件:
```ini
City = Boston
[Parent]
Name = Unknwon
Age = 21
```
## 获取帮助
- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
- [创建工单](https://github.com/go-ini/ini/issues/new)
## 常见问题
### 字段 `BlockMode` 是什么?
默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
### 为什么要写另一个 INI 解析库?
许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)

1247
Godeps/_workspace/src/github.com/go-ini/ini/ini.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

350
Godeps/_workspace/src/github.com/go-ini/ini/struct.go generated vendored Normal file
View File

@ -0,0 +1,350 @@
// Copyright 2014 Unknwon
//
// 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 ini
import (
"bytes"
"errors"
"fmt"
"reflect"
"time"
"unicode"
)
// NameMapper represents a ini tag name mapper.
type NameMapper func(string) string
// Built-in name getters.
var (
// AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
AllCapsUnderscore NameMapper = func(raw string) string {
newstr := make([]rune, 0, len(raw))
for i, chr := range raw {
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
if i > 0 {
newstr = append(newstr, '_')
}
}
newstr = append(newstr, unicode.ToUpper(chr))
}
return string(newstr)
}
// TitleUnderscore converts to format title_underscore.
TitleUnderscore NameMapper = func(raw string) string {
newstr := make([]rune, 0, len(raw))
for i, chr := range raw {
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
if i > 0 {
newstr = append(newstr, '_')
}
chr -= ('A' - 'a')
}
newstr = append(newstr, chr)
}
return string(newstr)
}
)
func (s *Section) parseFieldName(raw, actual string) string {
if len(actual) > 0 {
return actual
}
if s.f.NameMapper != nil {
return s.f.NameMapper(raw)
}
return raw
}
func parseDelim(actual string) string {
if len(actual) > 0 {
return actual
}
return ","
}
var reflectTime = reflect.TypeOf(time.Now()).Kind()
// setWithProperType sets proper value to field based on its type,
// but it does not return error for failing parsing,
// because we want to use default value that is already assigned to strcut.
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
switch t.Kind() {
case reflect.String:
if len(key.String()) == 0 {
return nil
}
field.SetString(key.String())
case reflect.Bool:
boolVal, err := key.Bool()
if err != nil {
return nil
}
field.SetBool(boolVal)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
durationVal, err := key.Duration()
if err == nil {
field.Set(reflect.ValueOf(durationVal))
return nil
}
intVal, err := key.Int64()
if err != nil {
return nil
}
field.SetInt(intVal)
// byte is an alias for uint8, so supporting uint8 breaks support for byte
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
durationVal, err := key.Duration()
if err == nil {
field.Set(reflect.ValueOf(durationVal))
return nil
}
uintVal, err := key.Uint64()
if err != nil {
return nil
}
field.SetUint(uintVal)
case reflect.Float64:
floatVal, err := key.Float64()
if err != nil {
return nil
}
field.SetFloat(floatVal)
case reflectTime:
timeVal, err := key.Time()
if err != nil {
return nil
}
field.Set(reflect.ValueOf(timeVal))
case reflect.Slice:
vals := key.Strings(delim)
numVals := len(vals)
if numVals == 0 {
return nil
}
sliceOf := field.Type().Elem().Kind()
var times []time.Time
if sliceOf == reflectTime {
times = key.Times(delim)
}
slice := reflect.MakeSlice(field.Type(), numVals, numVals)
for i := 0; i < numVals; i++ {
switch sliceOf {
case reflectTime:
slice.Index(i).Set(reflect.ValueOf(times[i]))
default:
slice.Index(i).Set(reflect.ValueOf(vals[i]))
}
}
field.Set(slice)
default:
return fmt.Errorf("unsupported type '%s'", t)
}
return nil
}
func (s *Section) mapTo(val reflect.Value) error {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
field := val.Field(i)
tpField := typ.Field(i)
tag := tpField.Tag.Get("ini")
if tag == "-" {
continue
}
fieldName := s.parseFieldName(tpField.Name, tag)
if len(fieldName) == 0 || !field.CanSet() {
continue
}
isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
isStruct := tpField.Type.Kind() == reflect.Struct
if isAnonymous {
field.Set(reflect.New(tpField.Type.Elem()))
}
if isAnonymous || isStruct {
if sec, err := s.f.GetSection(fieldName); err == nil {
if err = sec.mapTo(field); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
}
continue
}
}
if key, err := s.GetKey(fieldName); err == nil {
if err = setWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
}
}
}
return nil
}
// MapTo maps section to given struct.
func (s *Section) MapTo(v interface{}) error {
typ := reflect.TypeOf(v)
val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
} else {
return errors.New("cannot map to non-pointer struct")
}
return s.mapTo(val)
}
// MapTo maps file to given struct.
func (f *File) MapTo(v interface{}) error {
return f.Section("").MapTo(v)
}
// MapTo maps data sources to given struct with name mapper.
func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
cfg, err := Load(source, others...)
if err != nil {
return err
}
cfg.NameMapper = mapper
return cfg.MapTo(v)
}
// MapTo maps data sources to given struct.
func MapTo(v, source interface{}, others ...interface{}) error {
return MapToWithMapper(v, nil, source, others...)
}
// reflectWithProperType does the opposite thing with setWithProperType.
func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
switch t.Kind() {
case reflect.String:
key.SetValue(field.String())
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float64,
reflectTime:
key.SetValue(fmt.Sprint(field))
case reflect.Slice:
vals := field.Slice(0, field.Len())
if field.Len() == 0 {
return nil
}
var buf bytes.Buffer
isTime := fmt.Sprint(field.Type()) == "[]time.Time"
for i := 0; i < field.Len(); i++ {
if isTime {
buf.WriteString(vals.Index(i).Interface().(time.Time).Format(time.RFC3339))
} else {
buf.WriteString(fmt.Sprint(vals.Index(i)))
}
buf.WriteString(delim)
}
key.SetValue(buf.String()[:buf.Len()-1])
default:
return fmt.Errorf("unsupported type '%s'", t)
}
return nil
}
func (s *Section) reflectFrom(val reflect.Value) error {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
field := val.Field(i)
tpField := typ.Field(i)
tag := tpField.Tag.Get("ini")
if tag == "-" {
continue
}
fieldName := s.parseFieldName(tpField.Name, tag)
if len(fieldName) == 0 || !field.CanSet() {
continue
}
if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
(tpField.Type.Kind() == reflect.Struct) {
// Note: The only error here is section doesn't exist.
sec, err := s.f.GetSection(fieldName)
if err != nil {
// Note: fieldName can never be empty here, ignore error.
sec, _ = s.f.NewSection(fieldName)
}
if err = sec.reflectFrom(field); err != nil {
return fmt.Errorf("error reflecting field(%s): %v", fieldName, err)
}
continue
}
// Note: Same reason as secion.
key, err := s.GetKey(fieldName)
if err != nil {
key, _ = s.NewKey(fieldName, "")
}
if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
return fmt.Errorf("error reflecting field(%s): %v", fieldName, err)
}
}
return nil
}
// ReflectFrom reflects secion from given struct.
func (s *Section) ReflectFrom(v interface{}) error {
typ := reflect.TypeOf(v)
val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
} else {
return errors.New("cannot reflect from non-pointer struct")
}
return s.reflectFrom(val)
}
// ReflectFrom reflects file from given struct.
func (f *File) ReflectFrom(v interface{}) error {
return f.Section("").ReflectFrom(v)
}
// ReflectFrom reflects data sources from given struct with name mapper.
func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
cfg.NameMapper = mapper
return cfg.ReflectFrom(v)
}
// ReflectFrom reflects data sources from given struct.
func ReflectFrom(cfg *File, v interface{}) error {
return ReflectFromWithMapper(cfg, v, nil)
}

View File

@ -0,0 +1,4 @@
jpgo
jmespath-fuzz.zip
cpu.out
go-jmespath.test

View File

@ -0,0 +1,9 @@
language: go
sudo: false
go:
- 1.4
install: go get -v -t ./...
script: make test

View File

@ -0,0 +1,13 @@
Copyright 2015 James Saryerwinnie
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.

View File

@ -0,0 +1,44 @@
CMD = jpgo
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " test to run all the tests"
@echo " build to build the library and jp executable"
@echo " generate to run codegen"
generate:
go generate ./...
build:
rm -f $(CMD)
go build ./...
rm -f cmd/$(CMD)/$(CMD) && cd cmd/$(CMD)/ && go build ./...
mv cmd/$(CMD)/$(CMD) .
test:
go test -v ./...
check:
go vet ./...
@echo "golint ./..."
@lint=`golint ./...`; \
lint=`echo "$$lint" | grep -v "astnodetype_string.go" | grep -v "toktype_string.go"`; \
echo "$$lint"; \
if [ "$$lint" != "" ]; then exit 1; fi
htmlc:
go test -coverprofile="/tmp/jpcov" && go tool cover -html="/tmp/jpcov" && unlink /tmp/jpcov
buildfuzz:
go-fuzz-build github.com/jmespath/go-jmespath/fuzz
fuzz: buildfuzz
go-fuzz -bin=./jmespath-fuzz.zip -workdir=fuzz/corpus
bench:
go test -bench . -cpuprofile cpu.out
pprof-cpu:
go tool pprof ./go-jmespath.test ./cpu.out

View File

@ -0,0 +1,7 @@
# go-jmespath - A JMESPath implementation in Go
[![Build Status](https://img.shields.io/travis/jmespath/go-jmespath.svg)](https://travis-ci.org/jmespath/go-jmespath)
See http://jmespath.org for more info.

View File

@ -0,0 +1,12 @@
package jmespath
// Search evaluates a JMESPath expression against input data and returns the result.
func Search(expression string, data interface{}) (interface{}, error) {
intr := newInterpreter()
parser := NewParser()
ast, err := parser.Parse(expression)
if err != nil {
return nil, err
}
return intr.Execute(ast, data)
}

View File

@ -0,0 +1,16 @@
// generated by stringer -type astNodeType; DO NOT EDIT
package jmespath
import "fmt"
const _astNodeType_name = "ASTEmptyASTComparatorASTCurrentNodeASTExpRefASTFunctionExpressionASTFieldASTFilterProjectionASTFlattenASTIdentityASTIndexASTIndexExpressionASTKeyValPairASTLiteralASTMultiSelectHashASTMultiSelectListASTOrExpressionASTAndExpressionASTNotExpressionASTPipeASTProjectionASTSubexpressionASTSliceASTValueProjection"
var _astNodeType_index = [...]uint16{0, 8, 21, 35, 44, 65, 73, 92, 102, 113, 121, 139, 152, 162, 180, 198, 213, 229, 245, 252, 265, 281, 289, 307}
func (i astNodeType) String() string {
if i < 0 || i >= astNodeType(len(_astNodeType_index)-1) {
return fmt.Sprintf("astNodeType(%d)", i)
}
return _astNodeType_name[_astNodeType_index[i]:_astNodeType_index[i+1]]
}

View File

@ -0,0 +1,96 @@
/*Basic command line interface for debug and testing purposes.
Examples:
Only print the AST for the expression:
jp.go -ast "foo.bar.baz"
Evaluate the JMESPath expression against JSON data from a file:
jp.go -input /tmp/data.json "foo.bar.baz"
This program can also be used as an executable to the jp-compliance
runner (github.com/jmespath/jmespath.test).
*/
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
)
import (
"encoding/json"
"github.com/jmespath/go-jmespath"
)
func errMsg(msg string, a ...interface{}) int {
fmt.Fprintf(os.Stderr, msg, a...)
fmt.Fprintln(os.Stderr)
return 1
}
func run() int {
astOnly := flag.Bool("ast", false, "Print the AST for the input expression and exit.")
inputFile := flag.String("input", "", "Filename containing JSON data to search. If not provided, data is read from stdin.")
flag.Parse()
args := flag.Args()
if len(args) != 1 {
fmt.Fprintf(os.Stderr, "Usage:\n\n")
flag.PrintDefaults()
return errMsg("\nError: expected a single argument (the JMESPath expression).")
}
expression := args[0]
parser := jmespath.NewParser()
parsed, err := parser.Parse(expression)
if err != nil {
if syntaxError, ok := err.(jmespath.SyntaxError); ok {
return errMsg("%s\n%s\n", syntaxError, syntaxError.HighlightLocation())
}
return errMsg("%s", err)
}
if *astOnly {
fmt.Println("")
fmt.Printf("%s\n", parsed)
return 0
}
var inputData []byte
if *inputFile != "" {
inputData, err = ioutil.ReadFile(*inputFile)
if err != nil {
return errMsg("Error loading file %s: %s", *inputFile, err)
}
} else {
// If an input data file is not provided then we read the
// data from stdin.
inputData, err = ioutil.ReadAll(os.Stdin)
if err != nil {
return errMsg("Error reading from stdin: %s", err)
}
}
var data interface{}
json.Unmarshal(inputData, &data)
result, err := jmespath.Search(expression, data)
if err != nil {
return errMsg("Error executing expression: %s", err)
}
toJSON, err := json.MarshalIndent(result, "", " ")
if err != nil {
return errMsg("Error serializing result to JSON: %s", err)
}
fmt.Println(string(toJSON))
return 0
}
func main() {
os.Exit(run())
}

View File

@ -0,0 +1,96 @@
[{
"given":
{"foo": {"bar": {"baz": "correct"}}},
"cases": [
{
"expression": "foo",
"result": {"bar": {"baz": "correct"}}
},
{
"expression": "foo.bar",
"result": {"baz": "correct"}
},
{
"expression": "foo.bar.baz",
"result": "correct"
},
{
"expression": "foo\n.\nbar\n.baz",
"result": "correct"
},
{
"expression": "foo.bar.baz.bad",
"result": null
},
{
"expression": "foo.bar.bad",
"result": null
},
{
"expression": "foo.bad",
"result": null
},
{
"expression": "bad",
"result": null
},
{
"expression": "bad.morebad.morebad",
"result": null
}
]
},
{
"given":
{"foo": {"bar": ["one", "two", "three"]}},
"cases": [
{
"expression": "foo",
"result": {"bar": ["one", "two", "three"]}
},
{
"expression": "foo.bar",
"result": ["one", "two", "three"]
}
]
},
{
"given": ["one", "two", "three"],
"cases": [
{
"expression": "one",
"result": null
},
{
"expression": "two",
"result": null
},
{
"expression": "three",
"result": null
},
{
"expression": "one.two",
"result": null
}
]
},
{
"given":
{"foo": {"1": ["one", "two", "three"], "-1": "bar"}},
"cases": [
{
"expression": "foo.\"1\"",
"result": ["one", "two", "three"]
},
{
"expression": "foo.\"1\"[0]",
"result": "one"
},
{
"expression": "foo.\"-1\"",
"result": "bar"
}
]
}
]

View File

@ -0,0 +1,257 @@
[
{
"given": {
"outer": {
"foo": "foo",
"bar": "bar",
"baz": "baz"
}
},
"cases": [
{
"expression": "outer.foo || outer.bar",
"result": "foo"
},
{
"expression": "outer.foo||outer.bar",
"result": "foo"
},
{
"expression": "outer.bar || outer.baz",
"result": "bar"
},
{
"expression": "outer.bar||outer.baz",
"result": "bar"
},
{
"expression": "outer.bad || outer.foo",
"result": "foo"
},
{
"expression": "outer.bad||outer.foo",
"result": "foo"
},
{
"expression": "outer.foo || outer.bad",
"result": "foo"
},
{
"expression": "outer.foo||outer.bad",
"result": "foo"
},
{
"expression": "outer.bad || outer.alsobad",
"result": null
},
{
"expression": "outer.bad||outer.alsobad",
"result": null
}
]
},
{
"given": {
"outer": {
"foo": "foo",
"bool": false,
"empty_list": [],
"empty_string": ""
}
},
"cases": [
{
"expression": "outer.empty_string || outer.foo",
"result": "foo"
},
{
"expression": "outer.nokey || outer.bool || outer.empty_list || outer.empty_string || outer.foo",
"result": "foo"
}
]
},
{
"given": {
"True": true,
"False": false,
"Number": 5,
"EmptyList": [],
"Zero": 0
},
"cases": [
{
"expression": "True && False",
"result": false
},
{
"expression": "False && True",
"result": false
},
{
"expression": "True && True",
"result": true
},
{
"expression": "False && False",
"result": false
},
{
"expression": "True && Number",
"result": 5
},
{
"expression": "Number && True",
"result": true
},
{
"expression": "Number && False",
"result": false
},
{
"expression": "Number && EmptyList",
"result": []
},
{
"expression": "Number && True",
"result": true
},
{
"expression": "EmptyList && True",
"result": []
},
{
"expression": "EmptyList && False",
"result": []
},
{
"expression": "True || False",
"result": true
},
{
"expression": "True || True",
"result": true
},
{
"expression": "False || True",
"result": true
},
{
"expression": "False || False",
"result": false
},
{
"expression": "Number || EmptyList",
"result": 5
},
{
"expression": "Number || True",
"result": 5
},
{
"expression": "Number || True && False",
"result": 5
},
{
"expression": "(Number || True) && False",
"result": false
},
{
"expression": "Number || (True && False)",
"result": 5
},
{
"expression": "!True",
"result": false
},
{
"expression": "!False",
"result": true
},
{
"expression": "!Number",
"result": false
},
{
"expression": "!EmptyList",
"result": true
},
{
"expression": "True && !False",
"result": true
},
{
"expression": "True && !EmptyList",
"result": true
},
{
"expression": "!False && !EmptyList",
"result": true
},
{
"expression": "!(True && False)",
"result": true
},
{
"expression": "!Zero",
"result": false
},
{
"expression": "!!Zero",
"result": true
}
]
},
{
"given": {
"one": 1,
"two": 2,
"three": 3
},
"cases": [
{
"expression": "one < two",
"result": true
},
{
"expression": "one <= two",
"result": true
},
{
"expression": "one == one",
"result": true
},
{
"expression": "one == two",
"result": false
},
{
"expression": "one > two",
"result": false
},
{
"expression": "one >= two",
"result": false
},
{
"expression": "one != two",
"result": true
},
{
"expression": "one < two && three > one",
"result": true
},
{
"expression": "one < two || three > one",
"result": true
},
{
"expression": "one < two || three < one",
"result": true
},
{
"expression": "two < one || three < one",
"result": false
}
]
}
]

View File

@ -0,0 +1,25 @@
[
{
"given": {
"foo": [{"name": "a"}, {"name": "b"}],
"bar": {"baz": "qux"}
},
"cases": [
{
"expression": "@",
"result": {
"foo": [{"name": "a"}, {"name": "b"}],
"bar": {"baz": "qux"}
}
},
{
"expression": "@.bar",
"result": {"baz": "qux"}
},
{
"expression": "@.foo[0]",
"result": {"name": "a"}
}
]
}
]

View File

@ -0,0 +1,46 @@
[{
"given": {
"foo.bar": "dot",
"foo bar": "space",
"foo\nbar": "newline",
"foo\"bar": "doublequote",
"c:\\\\windows\\path": "windows",
"/unix/path": "unix",
"\"\"\"": "threequotes",
"bar": {"baz": "qux"}
},
"cases": [
{
"expression": "\"foo.bar\"",
"result": "dot"
},
{
"expression": "\"foo bar\"",
"result": "space"
},
{
"expression": "\"foo\\nbar\"",
"result": "newline"
},
{
"expression": "\"foo\\\"bar\"",
"result": "doublequote"
},
{
"expression": "\"c:\\\\\\\\windows\\\\path\"",
"result": "windows"
},
{
"expression": "\"/unix/path\"",
"result": "unix"
},
{
"expression": "\"\\\"\\\"\\\"\"",
"result": "threequotes"
},
{
"expression": "\"bar\".\"baz\"",
"result": "qux"
}
]
}]

View File

@ -0,0 +1,468 @@
[
{
"given": {"foo": [{"name": "a"}, {"name": "b"}]},
"cases": [
{
"comment": "Matching a literal",
"expression": "foo[?name == 'a']",
"result": [{"name": "a"}]
}
]
},
{
"given": {"foo": [0, 1], "bar": [2, 3]},
"cases": [
{
"comment": "Matching a literal",
"expression": "*[?[0] == `0`]",
"result": [[], []]
}
]
},
{
"given": {"foo": [{"first": "foo", "last": "bar"},
{"first": "foo", "last": "foo"},
{"first": "foo", "last": "baz"}]},
"cases": [
{
"comment": "Matching an expression",
"expression": "foo[?first == last]",
"result": [{"first": "foo", "last": "foo"}]
},
{
"comment": "Verify projection created from filter",
"expression": "foo[?first == last].first",
"result": ["foo"]
}
]
},
{
"given": {"foo": [{"age": 20},
{"age": 25},
{"age": 30}]},
"cases": [
{
"comment": "Greater than with a number",
"expression": "foo[?age > `25`]",
"result": [{"age": 30}]
},
{
"expression": "foo[?age >= `25`]",
"result": [{"age": 25}, {"age": 30}]
},
{
"comment": "Greater than with a number",
"expression": "foo[?age > `30`]",
"result": []
},
{
"comment": "Greater than with a number",
"expression": "foo[?age < `25`]",
"result": [{"age": 20}]
},
{
"comment": "Greater than with a number",
"expression": "foo[?age <= `25`]",
"result": [{"age": 20}, {"age": 25}]
},
{
"comment": "Greater than with a number",
"expression": "foo[?age < `20`]",
"result": []
},
{
"expression": "foo[?age == `20`]",
"result": [{"age": 20}]
},
{
"expression": "foo[?age != `20`]",
"result": [{"age": 25}, {"age": 30}]
}
]
},
{
"given": {"foo": [{"top": {"name": "a"}},
{"top": {"name": "b"}}]},
"cases": [
{
"comment": "Filter with subexpression",
"expression": "foo[?top.name == 'a']",
"result": [{"top": {"name": "a"}}]
}
]
},
{
"given": {"foo": [{"top": {"first": "foo", "last": "bar"}},
{"top": {"first": "foo", "last": "foo"}},
{"top": {"first": "foo", "last": "baz"}}]},
"cases": [
{
"comment": "Matching an expression",
"expression": "foo[?top.first == top.last]",
"result": [{"top": {"first": "foo", "last": "foo"}}]
},
{
"comment": "Matching a JSON array",
"expression": "foo[?top == `{\"first\": \"foo\", \"last\": \"bar\"}`]",
"result": [{"top": {"first": "foo", "last": "bar"}}]
}
]
},
{
"given": {"foo": [
{"key": true},
{"key": false},
{"key": 0},
{"key": 1},
{"key": [0]},
{"key": {"bar": [0]}},
{"key": null},
{"key": [1]},
{"key": {"a":2}}
]},
"cases": [
{
"expression": "foo[?key == `true`]",
"result": [{"key": true}]
},
{
"expression": "foo[?key == `false`]",
"result": [{"key": false}]
},
{
"expression": "foo[?key == `0`]",
"result": [{"key": 0}]
},
{
"expression": "foo[?key == `1`]",
"result": [{"key": 1}]
},
{
"expression": "foo[?key == `[0]`]",
"result": [{"key": [0]}]
},
{
"expression": "foo[?key == `{\"bar\": [0]}`]",
"result": [{"key": {"bar": [0]}}]
},
{
"expression": "foo[?key == `null`]",
"result": [{"key": null}]
},
{
"expression": "foo[?key == `[1]`]",
"result": [{"key": [1]}]
},
{
"expression": "foo[?key == `{\"a\":2}`]",
"result": [{"key": {"a":2}}]
},
{
"expression": "foo[?`true` == key]",
"result": [{"key": true}]
},
{
"expression": "foo[?`false` == key]",
"result": [{"key": false}]
},
{
"expression": "foo[?`0` == key]",
"result": [{"key": 0}]
},
{
"expression": "foo[?`1` == key]",
"result": [{"key": 1}]
},
{
"expression": "foo[?`[0]` == key]",
"result": [{"key": [0]}]
},
{
"expression": "foo[?`{\"bar\": [0]}` == key]",
"result": [{"key": {"bar": [0]}}]
},
{
"expression": "foo[?`null` == key]",
"result": [{"key": null}]
},
{
"expression": "foo[?`[1]` == key]",
"result": [{"key": [1]}]
},
{
"expression": "foo[?`{\"a\":2}` == key]",
"result": [{"key": {"a":2}}]
},
{
"expression": "foo[?key != `true`]",
"result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?key != `false`]",
"result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?key != `0`]",
"result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?key != `1`]",
"result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?key != `null`]",
"result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?key != `[1]`]",
"result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}]
},
{
"expression": "foo[?key != `{\"a\":2}`]",
"result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}]
},
{
"expression": "foo[?`true` != key]",
"result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?`false` != key]",
"result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?`0` != key]",
"result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?`1` != key]",
"result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?`null` != key]",
"result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}]
},
{
"expression": "foo[?`[1]` != key]",
"result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}]
},
{
"expression": "foo[?`{\"a\":2}` != key]",
"result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
{"key": {"bar": [0]}}, {"key": null}, {"key": [1]}]
}
]
},
{
"given": {"reservations": [
{"instances": [
{"foo": 1, "bar": 2}, {"foo": 1, "bar": 3},
{"foo": 1, "bar": 2}, {"foo": 2, "bar": 1}]}]},
"cases": [
{
"expression": "reservations[].instances[?bar==`1`]",
"result": [[{"foo": 2, "bar": 1}]]
},
{
"expression": "reservations[*].instances[?bar==`1`]",
"result": [[{"foo": 2, "bar": 1}]]
},
{
"expression": "reservations[].instances[?bar==`1`][]",
"result": [{"foo": 2, "bar": 1}]
}
]
},
{
"given": {
"baz": "other",
"foo": [
{"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 1, "baz": 2}
]
},
"cases": [
{
"expression": "foo[?bar==`1`].bar[0]",
"result": []
}
]
},
{
"given": {
"foo": [
{"a": 1, "b": {"c": "x"}},
{"a": 1, "b": {"c": "y"}},
{"a": 1, "b": {"c": "z"}},
{"a": 2, "b": {"c": "z"}},
{"a": 1, "baz": 2}
]
},
"cases": [
{
"expression": "foo[?a==`1`].b.c",
"result": ["x", "y", "z"]
}
]
},
{
"given": {"foo": [{"name": "a"}, {"name": "b"}, {"name": "c"}]},
"cases": [
{
"comment": "Filter with or expression",
"expression": "foo[?name == 'a' || name == 'b']",
"result": [{"name": "a"}, {"name": "b"}]
},
{
"expression": "foo[?name == 'a' || name == 'e']",
"result": [{"name": "a"}]
},
{
"expression": "foo[?name == 'a' || name == 'b' || name == 'c']",
"result": [{"name": "a"}, {"name": "b"}, {"name": "c"}]
}
]
},
{
"given": {"foo": [{"a": 1, "b": 2}, {"a": 1, "b": 3}]},
"cases": [
{
"comment": "Filter with and expression",
"expression": "foo[?a == `1` && b == `2`]",
"result": [{"a": 1, "b": 2}]
},
{
"expression": "foo[?a == `1` && b == `4`]",
"result": []
}
]
},
{
"given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]},
"cases": [
{
"comment": "Filter with Or and And expressions",
"expression": "foo[?c == `3` || a == `1` && b == `4`]",
"result": [{"a": 1, "b": 2, "c": 3}]
},
{
"expression": "foo[?b == `2` || a == `3` && b == `4`]",
"result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]
},
{
"expression": "foo[?a == `3` && b == `4` || b == `2`]",
"result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]
},
{
"expression": "foo[?(a == `3` && b == `4`) || b == `2`]",
"result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]
},
{
"expression": "foo[?((a == `3` && b == `4`)) || b == `2`]",
"result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]
},
{
"expression": "foo[?a == `3` && (b == `4` || b == `2`)]",
"result": [{"a": 3, "b": 4}]
},
{
"expression": "foo[?a == `3` && ((b == `4` || b == `2`))]",
"result": [{"a": 3, "b": 4}]
}
]
},
{
"given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]},
"cases": [
{
"comment": "Verify precedence of or/and expressions",
"expression": "foo[?a == `1` || b ==`2` && c == `5`]",
"result": [{"a": 1, "b": 2, "c": 3}]
},
{
"comment": "Parentheses can alter precedence",
"expression": "foo[?(a == `1` || b ==`2`) && c == `5`]",
"result": []
},
{
"comment": "Not expressions combined with and/or",
"expression": "foo[?!(a == `1` || b ==`2`)]",
"result": [{"a": 3, "b": 4}]
}
]
},
{
"given": {
"foo": [
{"key": true},
{"key": false},
{"key": []},
{"key": {}},
{"key": [0]},
{"key": {"a": "b"}},
{"key": 0},
{"key": 1},
{"key": null},
{"notkey": true}
]
},
"cases": [
{
"comment": "Unary filter expression",
"expression": "foo[?key]",
"result": [
{"key": true}, {"key": [0]}, {"key": {"a": "b"}},
{"key": 0}, {"key": 1}
]
},
{
"comment": "Unary not filter expression",
"expression": "foo[?!key]",
"result": [
{"key": false}, {"key": []}, {"key": {}},
{"key": null}, {"notkey": true}
]
},
{
"comment": "Equality with null RHS",
"expression": "foo[?key == `null`]",
"result": [
{"key": null}, {"notkey": true}
]
}
]
},
{
"given": {
"foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
"cases": [
{
"comment": "Using @ in a filter expression",
"expression": "foo[?@ < `5`]",
"result": [0, 1, 2, 3, 4]
},
{
"comment": "Using @ in a filter expression",
"expression": "foo[?`5` > @]",
"result": [0, 1, 2, 3, 4]
},
{
"comment": "Using @ in a filter expression",
"expression": "foo[?@ == @]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
]
}
]

View File

@ -0,0 +1,825 @@
[{
"given":
{
"foo": -1,
"zero": 0,
"numbers": [-1, 3, 4, 5],
"array": [-1, 3, 4, 5, "a", "100"],
"strings": ["a", "b", "c"],
"decimals": [1.01, 1.2, -1.5],
"str": "Str",
"false": false,
"empty_list": [],
"empty_hash": {},
"objects": {"foo": "bar", "bar": "baz"},
"null_key": null
},
"cases": [
{
"expression": "abs(foo)",
"result": 1
},
{
"expression": "abs(foo)",
"result": 1
},
{
"expression": "abs(str)",
"error": "invalid-type"
},
{
"expression": "abs(array[1])",
"result": 3
},
{
"expression": "abs(array[1])",
"result": 3
},
{
"expression": "abs(`false`)",
"error": "invalid-type"
},
{
"expression": "abs(`-24`)",
"result": 24
},
{
"expression": "abs(`-24`)",
"result": 24
},
{
"expression": "abs(`1`, `2`)",
"error": "invalid-arity"
},
{
"expression": "abs()",
"error": "invalid-arity"
},
{
"expression": "unknown_function(`1`, `2`)",
"error": "unknown-function"
},
{
"expression": "avg(numbers)",
"result": 2.75
},
{
"expression": "avg(array)",
"error": "invalid-type"
},
{
"expression": "avg('abc')",
"error": "invalid-type"
},
{
"expression": "avg(foo)",
"error": "invalid-type"
},
{
"expression": "avg(@)",
"error": "invalid-type"
},
{
"expression": "avg(strings)",
"error": "invalid-type"
},
{
"expression": "ceil(`1.2`)",
"result": 2
},
{
"expression": "ceil(decimals[0])",
"result": 2
},
{
"expression": "ceil(decimals[1])",
"result": 2
},
{
"expression": "ceil(decimals[2])",
"result": -1
},
{
"expression": "ceil('string')",
"error": "invalid-type"
},
{
"expression": "contains('abc', 'a')",
"result": true
},
{
"expression": "contains('abc', 'd')",
"result": false
},
{
"expression": "contains(`false`, 'd')",
"error": "invalid-type"
},
{
"expression": "contains(strings, 'a')",
"result": true
},
{
"expression": "contains(decimals, `1.2`)",
"result": true
},
{
"expression": "contains(decimals, `false`)",
"result": false
},
{
"expression": "ends_with(str, 'r')",
"result": true
},
{
"expression": "ends_with(str, 'tr')",
"result": true
},
{
"expression": "ends_with(str, 'Str')",
"result": true
},
{
"expression": "ends_with(str, 'SStr')",
"result": false
},
{
"expression": "ends_with(str, 'foo')",
"result": false
},
{
"expression": "ends_with(str, `0`)",
"error": "invalid-type"
},
{
"expression": "floor(`1.2`)",
"result": 1
},
{
"expression": "floor('string')",
"error": "invalid-type"
},
{
"expression": "floor(decimals[0])",
"result": 1
},
{
"expression": "floor(foo)",
"result": -1
},
{
"expression": "floor(str)",
"error": "invalid-type"
},
{
"expression": "length('abc')",
"result": 3
},
{
"expression": "length('✓foo')",
"result": 4
},
{
"expression": "length('')",
"result": 0
},
{
"expression": "length(@)",
"result": 12
},
{
"expression": "length(strings[0])",
"result": 1
},
{
"expression": "length(str)",
"result": 3
},
{
"expression": "length(array)",
"result": 6
},
{
"expression": "length(objects)",
"result": 2
},
{
"expression": "length(`false`)",
"error": "invalid-type"
},
{
"expression": "length(foo)",
"error": "invalid-type"
},
{
"expression": "length(strings[0])",
"result": 1
},
{
"expression": "max(numbers)",
"result": 5
},
{
"expression": "max(decimals)",
"result": 1.2
},
{
"expression": "max(strings)",
"result": "c"
},
{
"expression": "max(abc)",
"error": "invalid-type"
},
{
"expression": "max(array)",
"error": "invalid-type"
},
{
"expression": "max(decimals)",
"result": 1.2
},
{
"expression": "max(empty_list)",
"result": null
},
{
"expression": "merge(`{}`)",
"result": {}
},
{
"expression": "merge(`{}`, `{}`)",
"result": {}
},
{
"expression": "merge(`{\"a\": 1}`, `{\"b\": 2}`)",
"result": {"a": 1, "b": 2}
},
{
"expression": "merge(`{\"a\": 1}`, `{\"a\": 2}`)",
"result": {"a": 2}
},
{
"expression": "merge(`{\"a\": 1, \"b\": 2}`, `{\"a\": 2, \"c\": 3}`, `{\"d\": 4}`)",
"result": {"a": 2, "b": 2, "c": 3, "d": 4}
},
{
"expression": "min(numbers)",
"result": -1
},
{
"expression": "min(decimals)",
"result": -1.5
},
{
"expression": "min(abc)",
"error": "invalid-type"
},
{
"expression": "min(array)",
"error": "invalid-type"
},
{
"expression": "min(empty_list)",
"result": null
},
{
"expression": "min(decimals)",
"result": -1.5
},
{
"expression": "min(strings)",
"result": "a"
},
{
"expression": "type('abc')",
"result": "string"
},
{
"expression": "type(`1.0`)",
"result": "number"
},
{
"expression": "type(`2`)",
"result": "number"
},
{
"expression": "type(`true`)",
"result": "boolean"
},
{
"expression": "type(`false`)",
"result": "boolean"
},
{
"expression": "type(`null`)",
"result": "null"
},
{
"expression": "type(`[0]`)",
"result": "array"
},
{
"expression": "type(`{\"a\": \"b\"}`)",
"result": "object"
},
{
"expression": "type(@)",
"result": "object"
},
{
"expression": "sort(keys(objects))",
"result": ["bar", "foo"]
},
{
"expression": "keys(foo)",
"error": "invalid-type"
},
{
"expression": "keys(strings)",
"error": "invalid-type"
},
{
"expression": "keys(`false`)",
"error": "invalid-type"
},
{
"expression": "sort(values(objects))",
"result": ["bar", "baz"]
},
{
"expression": "keys(empty_hash)",
"result": []
},
{
"expression": "values(foo)",
"error": "invalid-type"
},
{
"expression": "join(', ', strings)",
"result": "a, b, c"
},
{
"expression": "join(', ', strings)",
"result": "a, b, c"
},
{
"expression": "join(',', `[\"a\", \"b\"]`)",
"result": "a,b"
},
{
"expression": "join(',', `[\"a\", 0]`)",
"error": "invalid-type"
},
{
"expression": "join(', ', str)",
"error": "invalid-type"
},
{
"expression": "join('|', strings)",
"result": "a|b|c"
},
{
"expression": "join(`2`, strings)",
"error": "invalid-type"
},
{
"expression": "join('|', decimals)",
"error": "invalid-type"
},
{
"expression": "join('|', decimals[].to_string(@))",
"result": "1.01|1.2|-1.5"
},
{
"expression": "join('|', empty_list)",
"result": ""
},
{
"expression": "reverse(numbers)",
"result": [5, 4, 3, -1]
},
{
"expression": "reverse(array)",
"result": ["100", "a", 5, 4, 3, -1]
},
{
"expression": "reverse(`[]`)",
"result": []
},
{
"expression": "reverse('')",
"result": ""
},
{
"expression": "reverse('hello world')",
"result": "dlrow olleh"
},
{
"expression": "starts_with(str, 'S')",
"result": true
},
{
"expression": "starts_with(str, 'St')",
"result": true
},
{
"expression": "starts_with(str, 'Str')",
"result": true
},
{
"expression": "starts_with(str, 'String')",
"result": false
},
{
"expression": "starts_with(str, `0`)",
"error": "invalid-type"
},
{
"expression": "sum(numbers)",
"result": 11
},
{
"expression": "sum(decimals)",
"result": 0.71
},
{
"expression": "sum(array)",
"error": "invalid-type"
},
{
"expression": "sum(array[].to_number(@))",
"result": 111
},
{
"expression": "sum(`[]`)",
"result": 0
},
{
"expression": "to_array('foo')",
"result": ["foo"]
},
{
"expression": "to_array(`0`)",
"result": [0]
},
{
"expression": "to_array(objects)",
"result": [{"foo": "bar", "bar": "baz"}]
},
{
"expression": "to_array(`[1, 2, 3]`)",
"result": [1, 2, 3]
},
{
"expression": "to_array(false)",
"result": [false]
},
{
"expression": "to_string('foo')",
"result": "foo"
},
{
"expression": "to_string(`1.2`)",
"result": "1.2"
},
{
"expression": "to_string(`[0, 1]`)",
"result": "[0,1]"
},
{
"expression": "to_number('1.0')",
"result": 1.0
},
{
"expression": "to_number('1.1')",
"result": 1.1
},
{
"expression": "to_number('4')",
"result": 4
},
{
"expression": "to_number('notanumber')",
"result": null
},
{
"expression": "to_number(`false`)",
"result": null
},
{
"expression": "to_number(`null`)",
"result": null
},
{
"expression": "to_number(`[0]`)",
"result": null
},
{
"expression": "to_number(`{\"foo\": 0}`)",
"result": null
},
{
"expression": "\"to_string\"(`1.0`)",
"error": "syntax"
},
{
"expression": "sort(numbers)",
"result": [-1, 3, 4, 5]
},
{
"expression": "sort(strings)",
"result": ["a", "b", "c"]
},
{
"expression": "sort(decimals)",
"result": [-1.5, 1.01, 1.2]
},
{
"expression": "sort(array)",
"error": "invalid-type"
},
{
"expression": "sort(abc)",
"error": "invalid-type"
},
{
"expression": "sort(empty_list)",
"result": []
},
{
"expression": "sort(@)",
"error": "invalid-type"
},
{
"expression": "not_null(unknown_key, str)",
"result": "Str"
},
{
"expression": "not_null(unknown_key, foo.bar, empty_list, str)",
"result": []
},
{
"expression": "not_null(unknown_key, null_key, empty_list, str)",
"result": []
},
{
"expression": "not_null(all, expressions, are_null)",
"result": null
},
{
"expression": "not_null()",
"error": "invalid-arity"
},
{
"description": "function projection on single arg function",
"expression": "numbers[].to_string(@)",
"result": ["-1", "3", "4", "5"]
},
{
"description": "function projection on single arg function",
"expression": "array[].to_number(@)",
"result": [-1, 3, 4, 5, 100]
}
]
}, {
"given":
{
"foo": [
{"b": "b", "a": "a"},
{"c": "c", "b": "b"},
{"d": "d", "c": "c"},
{"e": "e", "d": "d"},
{"f": "f", "e": "e"}
]
},
"cases": [
{
"description": "function projection on variadic function",
"expression": "foo[].not_null(f, e, d, c, b, a)",
"result": ["b", "c", "d", "e", "f"]
}
]
}, {
"given":
{
"people": [
{"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
{"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
{"age": 30, "age_str": "30", "bool": true, "name": "c"},
{"age": 50, "age_str": "50", "bool": false, "name": "d"},
{"age": 10, "age_str": "10", "bool": true, "name": 3}
]
},
"cases": [
{
"description": "sort by field expression",
"expression": "sort_by(people, &age)",
"result": [
{"age": 10, "age_str": "10", "bool": true, "name": 3},
{"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
{"age": 30, "age_str": "30", "bool": true, "name": "c"},
{"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
{"age": 50, "age_str": "50", "bool": false, "name": "d"}
]
},
{
"expression": "sort_by(people, &age_str)",
"result": [
{"age": 10, "age_str": "10", "bool": true, "name": 3},
{"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
{"age": 30, "age_str": "30", "bool": true, "name": "c"},
{"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
{"age": 50, "age_str": "50", "bool": false, "name": "d"}
]
},
{
"description": "sort by function expression",
"expression": "sort_by(people, &to_number(age_str))",
"result": [
{"age": 10, "age_str": "10", "bool": true, "name": 3},
{"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
{"age": 30, "age_str": "30", "bool": true, "name": "c"},
{"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
{"age": 50, "age_str": "50", "bool": false, "name": "d"}
]
},
{
"description": "function projection on sort_by function",
"expression": "sort_by(people, &age)[].name",
"result": [3, "a", "c", "b", "d"]
},
{
"expression": "sort_by(people, &extra)",
"error": "invalid-type"
},
{
"expression": "sort_by(people, &bool)",
"error": "invalid-type"
},
{
"expression": "sort_by(people, &name)",
"error": "invalid-type"
},
{
"expression": "sort_by(people, name)",
"error": "invalid-type"
},
{
"expression": "sort_by(people, &age)[].extra",
"result": ["foo", "bar"]
},
{
"expression": "sort_by(`[]`, &age)",
"result": []
},
{
"expression": "max_by(people, &age)",
"result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
},
{
"expression": "max_by(people, &age_str)",
"result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
},
{
"expression": "max_by(people, &bool)",
"error": "invalid-type"
},
{
"expression": "max_by(people, &extra)",
"error": "invalid-type"
},
{
"expression": "max_by(people, &to_number(age_str))",
"result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
},
{
"expression": "min_by(people, &age)",
"result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
},
{
"expression": "min_by(people, &age_str)",
"result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
},
{
"expression": "min_by(people, &bool)",
"error": "invalid-type"
},
{
"expression": "min_by(people, &extra)",
"error": "invalid-type"
},
{
"expression": "min_by(people, &to_number(age_str))",
"result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
}
]
}, {
"given":
{
"people": [
{"age": 10, "order": "1"},
{"age": 10, "order": "2"},
{"age": 10, "order": "3"},
{"age": 10, "order": "4"},
{"age": 10, "order": "5"},
{"age": 10, "order": "6"},
{"age": 10, "order": "7"},
{"age": 10, "order": "8"},
{"age": 10, "order": "9"},
{"age": 10, "order": "10"},
{"age": 10, "order": "11"}
]
},
"cases": [
{
"description": "stable sort order",
"expression": "sort_by(people, &age)",
"result": [
{"age": 10, "order": "1"},
{"age": 10, "order": "2"},
{"age": 10, "order": "3"},
{"age": 10, "order": "4"},
{"age": 10, "order": "5"},
{"age": 10, "order": "6"},
{"age": 10, "order": "7"},
{"age": 10, "order": "8"},
{"age": 10, "order": "9"},
{"age": 10, "order": "10"},
{"age": 10, "order": "11"}
]
}
]
}, {
"given":
{
"people": [
{"a": 10, "b": 1, "c": "z"},
{"a": 10, "b": 2, "c": null},
{"a": 10, "b": 3},
{"a": 10, "b": 4, "c": "z"},
{"a": 10, "b": 5, "c": null},
{"a": 10, "b": 6},
{"a": 10, "b": 7, "c": "z"},
{"a": 10, "b": 8, "c": null},
{"a": 10, "b": 9}
],
"empty": []
},
"cases": [
{
"expression": "map(&a, people)",
"result": [10, 10, 10, 10, 10, 10, 10, 10, 10]
},
{
"expression": "map(&c, people)",
"result": ["z", null, null, "z", null, null, "z", null, null]
},
{
"expression": "map(&a, badkey)",
"error": "invalid-type"
},
{
"expression": "map(&foo, empty)",
"result": []
}
]
}, {
"given": {
"array": [
{
"foo": {"bar": "yes1"}
},
{
"foo": {"bar": "yes2"}
},
{
"foo1": {"bar": "no"}
}
]},
"cases": [
{
"expression": "map(&foo.bar, array)",
"result": ["yes1", "yes2", null]
},
{
"expression": "map(&foo1.bar, array)",
"result": [null, null, "no"]
},
{
"expression": "map(&foo.bar.baz, array)",
"result": [null, null, null]
}
]
}, {
"given": {
"array": [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]]
},
"cases": [
{
"expression": "map(&[], array)",
"result": [[1, 2, 3, 4], [5, 6, 7, 8, 9]]
}
]
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,346 @@
[{
"given":
{"foo": {"bar": ["zero", "one", "two"]}},
"cases": [
{
"expression": "foo.bar[0]",
"result": "zero"
},
{
"expression": "foo.bar[1]",
"result": "one"
},
{
"expression": "foo.bar[2]",
"result": "two"
},
{
"expression": "foo.bar[3]",
"result": null
},
{
"expression": "foo.bar[-1]",
"result": "two"
},
{
"expression": "foo.bar[-2]",
"result": "one"
},
{
"expression": "foo.bar[-3]",
"result": "zero"
},
{
"expression": "foo.bar[-4]",
"result": null
}
]
},
{
"given":
{"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]},
"cases": [
{
"expression": "foo.bar",
"result": null
},
{
"expression": "foo[0].bar",
"result": "one"
},
{
"expression": "foo[1].bar",
"result": "two"
},
{
"expression": "foo[2].bar",
"result": "three"
},
{
"expression": "foo[3].notbar",
"result": "four"
},
{
"expression": "foo[3].bar",
"result": null
},
{
"expression": "foo[0]",
"result": {"bar": "one"}
},
{
"expression": "foo[1]",
"result": {"bar": "two"}
},
{
"expression": "foo[2]",
"result": {"bar": "three"}
},
{
"expression": "foo[3]",
"result": {"notbar": "four"}
},
{
"expression": "foo[4]",
"result": null
}
]
},
{
"given": [
"one", "two", "three"
],
"cases": [
{
"expression": "[0]",
"result": "one"
},
{
"expression": "[1]",
"result": "two"
},
{
"expression": "[2]",
"result": "three"
},
{
"expression": "[-1]",
"result": "three"
},
{
"expression": "[-2]",
"result": "two"
},
{
"expression": "[-3]",
"result": "one"
}
]
},
{
"given": {"reservations": [
{"instances": [{"foo": 1}, {"foo": 2}]}
]},
"cases": [
{
"expression": "reservations[].instances[].foo",
"result": [1, 2]
},
{
"expression": "reservations[].instances[].bar",
"result": []
},
{
"expression": "reservations[].notinstances[].foo",
"result": []
},
{
"expression": "reservations[].notinstances[].foo",
"result": []
}
]
},
{
"given": {"reservations": [{
"instances": [
{"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]},
{"foo": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]},
{"foo": "bar"},
{"notfoo": [{"bar": 20}, {"bar": 21}, {"notbar": [7]}, {"bar": 22}]},
{"bar": [{"baz": [1]}, {"baz": [2]}, {"baz": [3]}, {"baz": [4]}]},
{"baz": [{"baz": [1, 2]}, {"baz": []}, {"baz": []}, {"baz": [3, 4]}]},
{"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]}
],
"otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}
}, {
"instances": [
{"a": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]},
{"b": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]},
{"c": "bar"},
{"notfoo": [{"bar": 23}, {"bar": 24}, {"notbar": [7]}, {"bar": 25}]},
{"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]}
],
"otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}
}
]},
"cases": [
{
"expression": "reservations[].instances[].foo[].bar",
"result": [1, 2, 4, 5, 6, 8]
},
{
"expression": "reservations[].instances[].foo[].baz",
"result": []
},
{
"expression": "reservations[].instances[].notfoo[].bar",
"result": [20, 21, 22, 23, 24, 25]
},
{
"expression": "reservations[].instances[].notfoo[].notbar",
"result": [[7], [7]]
},
{
"expression": "reservations[].notinstances[].foo",
"result": []
},
{
"expression": "reservations[].instances[].foo[].notbar",
"result": [3, [7]]
},
{
"expression": "reservations[].instances[].bar[].baz",
"result": [[1], [2], [3], [4]]
},
{
"expression": "reservations[].instances[].baz[].baz",
"result": [[1, 2], [], [], [3, 4]]
},
{
"expression": "reservations[].instances[].qux[].baz",
"result": [[], [1, 2, 3], [4], [], [], [1, 2, 3], [4], []]
},
{
"expression": "reservations[].instances[].qux[].baz[]",
"result": [1, 2, 3, 4, 1, 2, 3, 4]
}
]
},
{
"given": {
"foo": [
[["one", "two"], ["three", "four"]],
[["five", "six"], ["seven", "eight"]],
[["nine"], ["ten"]]
]
},
"cases": [
{
"expression": "foo[]",
"result": [["one", "two"], ["three", "four"], ["five", "six"],
["seven", "eight"], ["nine"], ["ten"]]
},
{
"expression": "foo[][0]",
"result": ["one", "three", "five", "seven", "nine", "ten"]
},
{
"expression": "foo[][1]",
"result": ["two", "four", "six", "eight"]
},
{
"expression": "foo[][0][0]",
"result": []
},
{
"expression": "foo[][2][2]",
"result": []
},
{
"expression": "foo[][0][0][100]",
"result": []
}
]
},
{
"given": {
"foo": [{
"bar": [
{
"qux": 2,
"baz": 1
},
{
"qux": 4,
"baz": 3
}
]
},
{
"bar": [
{
"qux": 6,
"baz": 5
},
{
"qux": 8,
"baz": 7
}
]
}
]
},
"cases": [
{
"expression": "foo",
"result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]},
{"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}]
},
{
"expression": "foo[]",
"result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]},
{"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}]
},
{
"expression": "foo[].bar",
"result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}],
[{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]]
},
{
"expression": "foo[].bar[]",
"result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3},
{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]
},
{
"expression": "foo[].bar[].baz",
"result": [1, 3, 5, 7]
}
]
},
{
"given": {
"string": "string",
"hash": {"foo": "bar", "bar": "baz"},
"number": 23,
"nullvalue": null
},
"cases": [
{
"expression": "string[]",
"result": null
},
{
"expression": "hash[]",
"result": null
},
{
"expression": "number[]",
"result": null
},
{
"expression": "nullvalue[]",
"result": null
},
{
"expression": "string[].foo",
"result": null
},
{
"expression": "hash[].foo",
"result": null
},
{
"expression": "number[].foo",
"result": null
},
{
"expression": "nullvalue[].foo",
"result": null
},
{
"expression": "nullvalue[].foo[].bar",
"result": null
}
]
}
]

View File

@ -0,0 +1,185 @@
[
{
"given": {
"foo": [{"name": "a"}, {"name": "b"}],
"bar": {"baz": "qux"}
},
"cases": [
{
"expression": "`\"foo\"`",
"result": "foo"
},
{
"comment": "Interpret escaped unicode.",
"expression": "`\"\\u03a6\"`",
"result": "Φ"
},
{
"expression": "`\"✓\"`",
"result": "✓"
},
{
"expression": "`[1, 2, 3]`",
"result": [1, 2, 3]
},
{
"expression": "`{\"a\": \"b\"}`",
"result": {"a": "b"}
},
{
"expression": "`true`",
"result": true
},
{
"expression": "`false`",
"result": false
},
{
"expression": "`null`",
"result": null
},
{
"expression": "`0`",
"result": 0
},
{
"expression": "`1`",
"result": 1
},
{
"expression": "`2`",
"result": 2
},
{
"expression": "`3`",
"result": 3
},
{
"expression": "`4`",
"result": 4
},
{
"expression": "`5`",
"result": 5
},
{
"expression": "`6`",
"result": 6
},
{
"expression": "`7`",
"result": 7
},
{
"expression": "`8`",
"result": 8
},
{
"expression": "`9`",
"result": 9
},
{
"comment": "Escaping a backtick in quotes",
"expression": "`\"foo\\`bar\"`",
"result": "foo`bar"
},
{
"comment": "Double quote in literal",
"expression": "`\"foo\\\"bar\"`",
"result": "foo\"bar"
},
{
"expression": "`\"1\\`\"`",
"result": "1`"
},
{
"comment": "Multiple literal expressions with escapes",
"expression": "`\"\\\\\"`.{a:`\"b\"`}",
"result": {"a": "b"}
},
{
"comment": "literal . identifier",
"expression": "`{\"a\": \"b\"}`.a",
"result": "b"
},
{
"comment": "literal . identifier . identifier",
"expression": "`{\"a\": {\"b\": \"c\"}}`.a.b",
"result": "c"
},
{
"comment": "literal . identifier bracket-expr",
"expression": "`[0, 1, 2]`[1]",
"result": 1
}
]
},
{
"comment": "Literals",
"given": {"type": "object"},
"cases": [
{
"comment": "Literal with leading whitespace",
"expression": "` {\"foo\": true}`",
"result": {"foo": true}
},
{
"comment": "Literal with trailing whitespace",
"expression": "`{\"foo\": true} `",
"result": {"foo": true}
},
{
"comment": "Literal on RHS of subexpr not allowed",
"expression": "foo.`\"bar\"`",
"error": "syntax"
}
]
},
{
"comment": "Raw String Literals",
"given": {},
"cases": [
{
"expression": "'foo'",
"result": "foo"
},
{
"expression": "' foo '",
"result": " foo "
},
{
"expression": "'0'",
"result": "0"
},
{
"expression": "'newline\n'",
"result": "newline\n"
},
{
"expression": "'\n'",
"result": "\n"
},
{
"expression": "'✓'",
"result": "✓"
},
{
"expression": "'𝄞'",
"result": "𝄞"
},
{
"expression": "' [foo] '",
"result": " [foo] "
},
{
"expression": "'[foo]'",
"result": "[foo]"
},
{
"comment": "Do not interpret escaped unicode.",
"expression": "'\\u03a6'",
"result": "\\u03a6"
}
]
}
]

View File

@ -0,0 +1,393 @@
[{
"given": {
"foo": {
"bar": "bar",
"baz": "baz",
"qux": "qux",
"nested": {
"one": {
"a": "first",
"b": "second",
"c": "third"
},
"two": {
"a": "first",
"b": "second",
"c": "third"
},
"three": {
"a": "first",
"b": "second",
"c": {"inner": "third"}
}
}
},
"bar": 1,
"baz": 2,
"qux\"": 3
},
"cases": [
{
"expression": "foo.{bar: bar}",
"result": {"bar": "bar"}
},
{
"expression": "foo.{\"bar\": bar}",
"result": {"bar": "bar"}
},
{
"expression": "foo.{\"foo.bar\": bar}",
"result": {"foo.bar": "bar"}
},
{
"expression": "foo.{bar: bar, baz: baz}",
"result": {"bar": "bar", "baz": "baz"}
},
{
"expression": "foo.{\"bar\": bar, \"baz\": baz}",
"result": {"bar": "bar", "baz": "baz"}
},
{
"expression": "{\"baz\": baz, \"qux\\\"\": \"qux\\\"\"}",
"result": {"baz": 2, "qux\"": 3}
},
{
"expression": "foo.{bar:bar,baz:baz}",
"result": {"bar": "bar", "baz": "baz"}
},
{
"expression": "foo.{bar: bar,qux: qux}",
"result": {"bar": "bar", "qux": "qux"}
},
{
"expression": "foo.{bar: bar, noexist: noexist}",
"result": {"bar": "bar", "noexist": null}
},
{
"expression": "foo.{noexist: noexist, alsonoexist: alsonoexist}",
"result": {"noexist": null, "alsonoexist": null}
},
{
"expression": "foo.badkey.{nokey: nokey, alsonokey: alsonokey}",
"result": null
},
{
"expression": "foo.nested.*.{a: a,b: b}",
"result": [{"a": "first", "b": "second"},
{"a": "first", "b": "second"},
{"a": "first", "b": "second"}]
},
{
"expression": "foo.nested.three.{a: a, cinner: c.inner}",
"result": {"a": "first", "cinner": "third"}
},
{
"expression": "foo.nested.three.{a: a, c: c.inner.bad.key}",
"result": {"a": "first", "c": null}
},
{
"expression": "foo.{a: nested.one.a, b: nested.two.b}",
"result": {"a": "first", "b": "second"}
},
{
"expression": "{bar: bar, baz: baz}",
"result": {"bar": 1, "baz": 2}
},
{
"expression": "{bar: bar}",
"result": {"bar": 1}
},
{
"expression": "{otherkey: bar}",
"result": {"otherkey": 1}
},
{
"expression": "{no: no, exist: exist}",
"result": {"no": null, "exist": null}
},
{
"expression": "foo.[bar]",
"result": ["bar"]
},
{
"expression": "foo.[bar,baz]",
"result": ["bar", "baz"]
},
{
"expression": "foo.[bar,qux]",
"result": ["bar", "qux"]
},
{
"expression": "foo.[bar,noexist]",
"result": ["bar", null]
},
{
"expression": "foo.[noexist,alsonoexist]",
"result": [null, null]
}
]
}, {
"given": {
"foo": {"bar": 1, "baz": [2, 3, 4]}
},
"cases": [
{
"expression": "foo.{bar:bar,baz:baz}",
"result": {"bar": 1, "baz": [2, 3, 4]}
},
{
"expression": "foo.[bar,baz[0]]",
"result": [1, 2]
},
{
"expression": "foo.[bar,baz[1]]",
"result": [1, 3]
},
{
"expression": "foo.[bar,baz[2]]",
"result": [1, 4]
},
{
"expression": "foo.[bar,baz[3]]",
"result": [1, null]
},
{
"expression": "foo.[bar[0],baz[3]]",
"result": [null, null]
}
]
}, {
"given": {
"foo": {"bar": 1, "baz": 2}
},
"cases": [
{
"expression": "foo.{bar: bar, baz: baz}",
"result": {"bar": 1, "baz": 2}
},
{
"expression": "foo.[bar,baz]",
"result": [1, 2]
}
]
}, {
"given": {
"foo": {
"bar": {"baz": [{"common": "first", "one": 1},
{"common": "second", "two": 2}]},
"ignoreme": 1,
"includeme": true
}
},
"cases": [
{
"expression": "foo.{bar: bar.baz[1],includeme: includeme}",
"result": {"bar": {"common": "second", "two": 2}, "includeme": true}
},
{
"expression": "foo.{\"bar.baz.two\": bar.baz[1].two, includeme: includeme}",
"result": {"bar.baz.two": 2, "includeme": true}
},
{
"expression": "foo.[includeme, bar.baz[*].common]",
"result": [true, ["first", "second"]]
},
{
"expression": "foo.[includeme, bar.baz[*].none]",
"result": [true, []]
},
{
"expression": "foo.[includeme, bar.baz[].common]",
"result": [true, ["first", "second"]]
}
]
}, {
"given": {
"reservations": [{
"instances": [
{"id": "id1",
"name": "first"},
{"id": "id2",
"name": "second"}
]}, {
"instances": [
{"id": "id3",
"name": "third"},
{"id": "id4",
"name": "fourth"}
]}
]},
"cases": [
{
"expression": "reservations[*].instances[*].{id: id, name: name}",
"result": [[{"id": "id1", "name": "first"}, {"id": "id2", "name": "second"}],
[{"id": "id3", "name": "third"}, {"id": "id4", "name": "fourth"}]]
},
{
"expression": "reservations[].instances[].{id: id, name: name}",
"result": [{"id": "id1", "name": "first"},
{"id": "id2", "name": "second"},
{"id": "id3", "name": "third"},
{"id": "id4", "name": "fourth"}]
},
{
"expression": "reservations[].instances[].[id, name]",
"result": [["id1", "first"],
["id2", "second"],
["id3", "third"],
["id4", "fourth"]]
}
]
},
{
"given": {
"foo": [{
"bar": [
{
"qux": 2,
"baz": 1
},
{
"qux": 4,
"baz": 3
}
]
},
{
"bar": [
{
"qux": 6,
"baz": 5
},
{
"qux": 8,
"baz": 7
}
]
}
]
},
"cases": [
{
"expression": "foo",
"result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]},
{"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}]
},
{
"expression": "foo[]",
"result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]},
{"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}]
},
{
"expression": "foo[].bar",
"result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}],
[{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]]
},
{
"expression": "foo[].bar[]",
"result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3},
{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]
},
{
"expression": "foo[].bar[].[baz, qux]",
"result": [[1, 2], [3, 4], [5, 6], [7, 8]]
},
{
"expression": "foo[].bar[].[baz]",
"result": [[1], [3], [5], [7]]
},
{
"expression": "foo[].bar[].[baz, qux][]",
"result": [1, 2, 3, 4, 5, 6, 7, 8]
}
]
},
{
"given": {
"foo": {
"baz": [
{
"bar": "abc"
}, {
"bar": "def"
}
],
"qux": ["zero"]
}
},
"cases": [
{
"expression": "foo.[baz[*].bar, qux[0]]",
"result": [["abc", "def"], "zero"]
}
]
},
{
"given": {
"foo": {
"baz": [
{
"bar": "a",
"bam": "b",
"boo": "c"
}, {
"bar": "d",
"bam": "e",
"boo": "f"
}
],
"qux": ["zero"]
}
},
"cases": [
{
"expression": "foo.[baz[*].[bar, boo], qux[0]]",
"result": [[["a", "c" ], ["d", "f" ]], "zero"]
}
]
},
{
"given": {
"foo": {
"baz": [
{
"bar": "a",
"bam": "b",
"boo": "c"
}, {
"bar": "d",
"bam": "e",
"boo": "f"
}
],
"qux": ["zero"]
}
},
"cases": [
{
"expression": "foo.[baz[*].not_there || baz[*].bar, qux[0]]",
"result": [["a", "d"], "zero"]
}
]
},
{
"given": {"type": "object"},
"cases": [
{
"comment": "Nested multiselect",
"expression": "[[*],*]",
"result": [null, ["object"]]
}
]
},
{
"given": [],
"cases": [
{
"comment": "Nested multiselect",
"expression": "[[*]]",
"result": [[]]
}
]
}
]

View File

@ -0,0 +1,59 @@
[{
"given":
{"outer": {"foo": "foo", "bar": "bar", "baz": "baz"}},
"cases": [
{
"expression": "outer.foo || outer.bar",
"result": "foo"
},
{
"expression": "outer.foo||outer.bar",
"result": "foo"
},
{
"expression": "outer.bar || outer.baz",
"result": "bar"
},
{
"expression": "outer.bar||outer.baz",
"result": "bar"
},
{
"expression": "outer.bad || outer.foo",
"result": "foo"
},
{
"expression": "outer.bad||outer.foo",
"result": "foo"
},
{
"expression": "outer.foo || outer.bad",
"result": "foo"
},
{
"expression": "outer.foo||outer.bad",
"result": "foo"
},
{
"expression": "outer.bad || outer.alsobad",
"result": null
},
{
"expression": "outer.bad||outer.alsobad",
"result": null
}
]
}, {
"given":
{"outer": {"foo": "foo", "bool": false, "empty_list": [], "empty_string": ""}},
"cases": [
{
"expression": "outer.empty_string || outer.foo",
"result": "foo"
},
{
"expression": "outer.nokey || outer.bool || outer.empty_list || outer.empty_string || outer.foo",
"result": "foo"
}
]
}]

View File

@ -0,0 +1,131 @@
[{
"given": {
"foo": {
"bar": {
"baz": "subkey"
},
"other": {
"baz": "subkey"
},
"other2": {
"baz": "subkey"
},
"other3": {
"notbaz": ["a", "b", "c"]
},
"other4": {
"notbaz": ["a", "b", "c"]
}
}
},
"cases": [
{
"expression": "foo.*.baz | [0]",
"result": "subkey"
},
{
"expression": "foo.*.baz | [1]",
"result": "subkey"
},
{
"expression": "foo.*.baz | [2]",
"result": "subkey"
},
{
"expression": "foo.bar.* | [0]",
"result": "subkey"
},
{
"expression": "foo.*.notbaz | [*]",
"result": [["a", "b", "c"], ["a", "b", "c"]]
},
{
"expression": "{\"a\": foo.bar, \"b\": foo.other} | *.baz",
"result": ["subkey", "subkey"]
}
]
}, {
"given": {
"foo": {
"bar": {
"baz": "one"
},
"other": {
"baz": "two"
},
"other2": {
"baz": "three"
},
"other3": {
"notbaz": ["a", "b", "c"]
},
"other4": {
"notbaz": ["d", "e", "f"]
}
}
},
"cases": [
{
"expression": "foo | bar",
"result": {"baz": "one"}
},
{
"expression": "foo | bar | baz",
"result": "one"
},
{
"expression": "foo|bar| baz",
"result": "one"
},
{
"expression": "not_there | [0]",
"result": null
},
{
"expression": "not_there | [0]",
"result": null
},
{
"expression": "[foo.bar, foo.other] | [0]",
"result": {"baz": "one"}
},
{
"expression": "{\"a\": foo.bar, \"b\": foo.other} | a",
"result": {"baz": "one"}
},
{
"expression": "{\"a\": foo.bar, \"b\": foo.other} | b",
"result": {"baz": "two"}
},
{
"expression": "foo.bam || foo.bar | baz",
"result": "one"
},
{
"expression": "foo | not_there || bar",
"result": {"baz": "one"}
}
]
}, {
"given": {
"foo": [{
"bar": [{
"baz": "one"
}, {
"baz": "two"
}]
}, {
"bar": [{
"baz": "three"
}, {
"baz": "four"
}]
}]
},
"cases": [
{
"expression": "foo[*].bar[*] | [0][0]",
"result": {"baz": "one"}
}
]
}]

View File

@ -0,0 +1,187 @@
[{
"given": {
"foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"bar": {
"baz": 1
}
},
"cases": [
{
"expression": "bar[0:10]",
"result": null
},
{
"expression": "foo[0:10:1]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[0:10]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[0:10:]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[0::1]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[0::]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[0:]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[:10:1]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[::1]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[:10:]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[::]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[:]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[1:9]",
"result": [1, 2, 3, 4, 5, 6, 7, 8]
},
{
"expression": "foo[0:10:2]",
"result": [0, 2, 4, 6, 8]
},
{
"expression": "foo[5:]",
"result": [5, 6, 7, 8, 9]
},
{
"expression": "foo[5::2]",
"result": [5, 7, 9]
},
{
"expression": "foo[::2]",
"result": [0, 2, 4, 6, 8]
},
{
"expression": "foo[::-1]",
"result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
},
{
"expression": "foo[1::2]",
"result": [1, 3, 5, 7, 9]
},
{
"expression": "foo[10:0:-1]",
"result": [9, 8, 7, 6, 5, 4, 3, 2, 1]
},
{
"expression": "foo[10:5:-1]",
"result": [9, 8, 7, 6]
},
{
"expression": "foo[8:2:-2]",
"result": [8, 6, 4]
},
{
"expression": "foo[0:20]",
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
{
"expression": "foo[10:-20:-1]",
"result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
},
{
"expression": "foo[10:-20]",
"result": []
},
{
"expression": "foo[-4:-1]",
"result": [6, 7, 8]
},
{
"expression": "foo[:-5:-1]",
"result": [9, 8, 7, 6]
},
{
"expression": "foo[8:2:0]",
"error": "invalid-value"
},
{
"expression": "foo[8:2:0:1]",
"error": "syntax"
},
{
"expression": "foo[8:2&]",
"error": "syntax"
},
{
"expression": "foo[2:a:3]",
"error": "syntax"
}
]
}, {
"given": {
"foo": [{"a": 1}, {"a": 2}, {"a": 3}],
"bar": [{"a": {"b": 1}}, {"a": {"b": 2}},
{"a": {"b": 3}}],
"baz": 50
},
"cases": [
{
"expression": "foo[:2].a",
"result": [1, 2]
},
{
"expression": "foo[:2].b",
"result": []
},
{
"expression": "foo[:2].a.b",
"result": []
},
{
"expression": "bar[::-1].a.b",
"result": [3, 2, 1]
},
{
"expression": "bar[:2].a.b",
"result": [1, 2]
},
{
"expression": "baz[:2].a",
"result": null
}
]
}, {
"given": [{"a": 1}, {"a": 2}, {"a": 3}],
"cases": [
{
"expression": "[:]",
"result": [{"a": 1}, {"a": 2}, {"a": 3}]
},
{
"expression": "[:2].a",
"result": [1, 2]
},
{
"expression": "[::-1].a",
"result": [3, 2, 1]
},
{
"expression": "[:2].b",
"result": []
}
]
}]

View File

@ -0,0 +1,616 @@
[{
"comment": "Dot syntax",
"given": {"type": "object"},
"cases": [
{
"expression": "foo.bar",
"result": null
},
{
"expression": "foo.1",
"error": "syntax"
},
{
"expression": "foo.-11",
"error": "syntax"
},
{
"expression": "foo",
"result": null
},
{
"expression": "foo.",
"error": "syntax"
},
{
"expression": "foo.",
"error": "syntax"
},
{
"expression": ".foo",
"error": "syntax"
},
{
"expression": "foo..bar",
"error": "syntax"
},
{
"expression": "foo.bar.",
"error": "syntax"
},
{
"expression": "foo[.]",
"error": "syntax"
}
]
},
{
"comment": "Simple token errors",
"given": {"type": "object"},
"cases": [
{
"expression": ".",
"error": "syntax"
},
{
"expression": ":",
"error": "syntax"
},
{
"expression": ",",
"error": "syntax"
},
{
"expression": "]",
"error": "syntax"
},
{
"expression": "[",
"error": "syntax"
},
{
"expression": "}",
"error": "syntax"
},
{
"expression": "{",
"error": "syntax"
},
{
"expression": ")",
"error": "syntax"
},
{
"expression": "(",
"error": "syntax"
},
{
"expression": "((&",
"error": "syntax"
},
{
"expression": "a[",
"error": "syntax"
},
{
"expression": "a]",
"error": "syntax"
},
{
"expression": "a][",
"error": "syntax"
},
{
"expression": "!",
"error": "syntax"
}
]
},
{
"comment": "Boolean syntax errors",
"given": {"type": "object"},
"cases": [
{
"expression": "![!(!",
"error": "syntax"
}
]
},
{
"comment": "Wildcard syntax",
"given": {"type": "object"},
"cases": [
{
"expression": "*",
"result": ["object"]
},
{
"expression": "*.*",
"result": []
},
{
"expression": "*.foo",
"result": []
},
{
"expression": "*[0]",
"result": []
},
{
"expression": ".*",
"error": "syntax"
},
{
"expression": "*foo",
"error": "syntax"
},
{
"expression": "*0",
"error": "syntax"
},
{
"expression": "foo[*]bar",
"error": "syntax"
},
{
"expression": "foo[*]*",
"error": "syntax"
}
]
},
{
"comment": "Flatten syntax",
"given": {"type": "object"},
"cases": [
{
"expression": "[]",
"result": null
}
]
},
{
"comment": "Simple bracket syntax",
"given": {"type": "object"},
"cases": [
{
"expression": "[0]",
"result": null
},
{
"expression": "[*]",
"result": null
},
{
"expression": "*.[0]",
"error": "syntax"
},
{
"expression": "*.[\"0\"]",
"result": [[null]]
},
{
"expression": "[*].bar",
"result": null
},
{
"expression": "[*][0]",
"result": null
},
{
"expression": "foo[#]",
"error": "syntax"
}
]
},
{
"comment": "Multi-select list syntax",
"given": {"type": "object"},
"cases": [
{
"expression": "foo[0]",
"result": null
},
{
"comment": "Valid multi-select of a list",
"expression": "foo[0, 1]",
"error": "syntax"
},
{
"expression": "foo.[0]",
"error": "syntax"
},
{
"expression": "foo.[*]",
"result": null
},
{
"comment": "Multi-select of a list with trailing comma",
"expression": "foo[0, ]",
"error": "syntax"
},
{
"comment": "Multi-select of a list with trailing comma and no close",
"expression": "foo[0,",
"error": "syntax"
},
{
"comment": "Multi-select of a list with trailing comma and no close",
"expression": "foo.[a",
"error": "syntax"
},
{
"comment": "Multi-select of a list with extra comma",
"expression": "foo[0,, 1]",
"error": "syntax"
},
{
"comment": "Multi-select of a list using an identifier index",
"expression": "foo[abc]",
"error": "syntax"
},
{
"comment": "Multi-select of a list using identifier indices",
"expression": "foo[abc, def]",
"error": "syntax"
},
{
"comment": "Multi-select of a list using an identifier index",
"expression": "foo[abc, 1]",
"error": "syntax"
},
{
"comment": "Multi-select of a list using an identifier index with trailing comma",
"expression": "foo[abc, ]",
"error": "syntax"
},
{
"comment": "Valid multi-select of a hash using an identifier index",
"expression": "foo.[abc]",
"result": null
},
{
"comment": "Valid multi-select of a hash",
"expression": "foo.[abc, def]",
"result": null
},
{
"comment": "Multi-select of a hash using a numeric index",
"expression": "foo.[abc, 1]",
"error": "syntax"
},
{
"comment": "Multi-select of a hash with a trailing comma",
"expression": "foo.[abc, ]",
"error": "syntax"
},
{
"comment": "Multi-select of a hash with extra commas",
"expression": "foo.[abc,, def]",
"error": "syntax"
},
{
"comment": "Multi-select of a hash using number indices",
"expression": "foo.[0, 1]",
"error": "syntax"
}
]
},
{
"comment": "Multi-select hash syntax",
"given": {"type": "object"},
"cases": [
{
"comment": "No key or value",
"expression": "a{}",
"error": "syntax"
},
{
"comment": "No closing token",
"expression": "a{",
"error": "syntax"
},
{
"comment": "Not a key value pair",
"expression": "a{foo}",
"error": "syntax"
},
{
"comment": "Missing value and closing character",
"expression": "a{foo:",
"error": "syntax"
},
{
"comment": "Missing closing character",
"expression": "a{foo: 0",
"error": "syntax"
},
{
"comment": "Missing value",
"expression": "a{foo:}",
"error": "syntax"
},
{
"comment": "Trailing comma and no closing character",
"expression": "a{foo: 0, ",
"error": "syntax"
},
{
"comment": "Missing value with trailing comma",
"expression": "a{foo: ,}",
"error": "syntax"
},
{
"comment": "Accessing Array using an identifier",
"expression": "a{foo: bar}",
"error": "syntax"
},
{
"expression": "a{foo: 0}",
"error": "syntax"
},
{
"comment": "Missing key-value pair",
"expression": "a.{}",
"error": "syntax"
},
{
"comment": "Not a key-value pair",
"expression": "a.{foo}",
"error": "syntax"
},
{
"comment": "Missing value",
"expression": "a.{foo:}",
"error": "syntax"
},
{
"comment": "Missing value with trailing comma",
"expression": "a.{foo: ,}",
"error": "syntax"
},
{
"comment": "Valid multi-select hash extraction",
"expression": "a.{foo: bar}",
"result": null
},
{
"comment": "Valid multi-select hash extraction",
"expression": "a.{foo: bar, baz: bam}",
"result": null
},
{
"comment": "Trailing comma",
"expression": "a.{foo: bar, }",
"error": "syntax"
},
{
"comment": "Missing key in second key-value pair",
"expression": "a.{foo: bar, baz}",
"error": "syntax"
},
{
"comment": "Missing value in second key-value pair",
"expression": "a.{foo: bar, baz:}",
"error": "syntax"
},
{
"comment": "Trailing comma",
"expression": "a.{foo: bar, baz: bam, }",
"error": "syntax"
},
{
"comment": "Nested multi select",
"expression": "{\"\\\\\":{\" \":*}}",
"result": {"\\": {" ": ["object"]}}
}
]
},
{
"comment": "Or expressions",
"given": {"type": "object"},
"cases": [
{
"expression": "foo || bar",
"result": null
},
{
"expression": "foo ||",
"error": "syntax"
},
{
"expression": "foo.|| bar",
"error": "syntax"
},
{
"expression": " || foo",
"error": "syntax"
},
{
"expression": "foo || || foo",
"error": "syntax"
},
{
"expression": "foo.[a || b]",
"result": null
},
{
"expression": "foo.[a ||]",
"error": "syntax"
},
{
"expression": "\"foo",
"error": "syntax"
}
]
},
{
"comment": "Filter expressions",
"given": {"type": "object"},
"cases": [
{
"expression": "foo[?bar==`\"baz\"`]",
"result": null
},
{
"expression": "foo[? bar == `\"baz\"` ]",
"result": null
},
{
"expression": "foo[ ?bar==`\"baz\"`]",
"error": "syntax"
},
{
"expression": "foo[?bar==]",
"error": "syntax"
},
{
"expression": "foo[?==]",
"error": "syntax"
},
{
"expression": "foo[?==bar]",
"error": "syntax"
},
{
"expression": "foo[?bar==baz?]",
"error": "syntax"
},
{
"expression": "foo[?a.b.c==d.e.f]",
"result": null
},
{
"expression": "foo[?bar==`[0, 1, 2]`]",
"result": null
},
{
"expression": "foo[?bar==`[\"a\", \"b\", \"c\"]`]",
"result": null
},
{
"comment": "Literal char not escaped",
"expression": "foo[?bar==`[\"foo`bar\"]`]",
"error": "syntax"
},
{
"comment": "Literal char escaped",
"expression": "foo[?bar==`[\"foo\\`bar\"]`]",
"result": null
},
{
"comment": "Unknown comparator",
"expression": "foo[?bar<>baz]",
"error": "syntax"
},
{
"comment": "Unknown comparator",
"expression": "foo[?bar^baz]",
"error": "syntax"
},
{
"expression": "foo[bar==baz]",
"error": "syntax"
},
{
"comment": "Quoted identifier in filter expression no spaces",
"expression": "[?\"\\\\\">`\"foo\"`]",
"result": null
},
{
"comment": "Quoted identifier in filter expression with spaces",
"expression": "[?\"\\\\\" > `\"foo\"`]",
"result": null
}
]
},
{
"comment": "Filter expression errors",
"given": {"type": "object"},
"cases": [
{
"expression": "bar.`\"anything\"`",
"error": "syntax"
},
{
"expression": "bar.baz.noexists.`\"literal\"`",
"error": "syntax"
},
{
"comment": "Literal wildcard projection",
"expression": "foo[*].`\"literal\"`",
"error": "syntax"
},
{
"expression": "foo[*].name.`\"literal\"`",
"error": "syntax"
},
{
"expression": "foo[].name.`\"literal\"`",
"error": "syntax"
},
{
"expression": "foo[].name.`\"literal\"`.`\"subliteral\"`",
"error": "syntax"
},
{
"comment": "Projecting a literal onto an empty list",
"expression": "foo[*].name.noexist.`\"literal\"`",
"error": "syntax"
},
{
"expression": "foo[].name.noexist.`\"literal\"`",
"error": "syntax"
},
{
"expression": "twolen[*].`\"foo\"`",
"error": "syntax"
},
{
"comment": "Two level projection of a literal",
"expression": "twolen[*].threelen[*].`\"bar\"`",
"error": "syntax"
},
{
"comment": "Two level flattened projection of a literal",
"expression": "twolen[].threelen[].`\"bar\"`",
"error": "syntax"
}
]
},
{
"comment": "Identifiers",
"given": {"type": "object"},
"cases": [
{
"expression": "foo",
"result": null
},
{
"expression": "\"foo\"",
"result": null
},
{
"expression": "\"\\\\\"",
"result": null
}
]
},
{
"comment": "Combined syntax",
"given": [],
"cases": [
{
"expression": "*||*|*|*",
"result": null
},
{
"expression": "*[]||[*]",
"result": []
},
{
"expression": "[*.*]",
"result": [null]
}
]
}
]

View File

@ -0,0 +1,38 @@
[
{
"given": {"foo": [{"✓": "✓"}, {"✓": "✗"}]},
"cases": [
{
"expression": "foo[].\"✓\"",
"result": ["✓", "✗"]
}
]
},
{
"given": {"☯": true},
"cases": [
{
"expression": "\"☯\"",
"result": true
}
]
},
{
"given": {"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪": true},
"cases": [
{
"expression": "\"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪\"",
"result": true
}
]
},
{
"given": {"☃": true},
"cases": [
{
"expression": "\"☃\"",
"result": true
}
]
}
]

View File

@ -0,0 +1,460 @@
[{
"given": {
"foo": {
"bar": {
"baz": "val"
},
"other": {
"baz": "val"
},
"other2": {
"baz": "val"
},
"other3": {
"notbaz": ["a", "b", "c"]
},
"other4": {
"notbaz": ["a", "b", "c"]
},
"other5": {
"other": {
"a": 1,
"b": 1,
"c": 1
}
}
}
},
"cases": [
{
"expression": "foo.*.baz",
"result": ["val", "val", "val"]
},
{
"expression": "foo.bar.*",
"result": ["val"]
},
{
"expression": "foo.*.notbaz",
"result": [["a", "b", "c"], ["a", "b", "c"]]
},
{
"expression": "foo.*.notbaz[0]",
"result": ["a", "a"]
},
{
"expression": "foo.*.notbaz[-1]",
"result": ["c", "c"]
}
]
}, {
"given": {
"foo": {
"first-1": {
"second-1": "val"
},
"first-2": {
"second-1": "val"
},
"first-3": {
"second-1": "val"
}
}
},
"cases": [
{
"expression": "foo.*",
"result": [{"second-1": "val"}, {"second-1": "val"},
{"second-1": "val"}]
},
{
"expression": "foo.*.*",
"result": [["val"], ["val"], ["val"]]
},
{
"expression": "foo.*.*.*",
"result": [[], [], []]
},
{
"expression": "foo.*.*.*.*",
"result": [[], [], []]
}
]
}, {
"given": {
"foo": {
"bar": "one"
},
"other": {
"bar": "one"
},
"nomatch": {
"notbar": "three"
}
},
"cases": [
{
"expression": "*.bar",
"result": ["one", "one"]
}
]
}, {
"given": {
"top1": {
"sub1": {"foo": "one"}
},
"top2": {
"sub1": {"foo": "one"}
}
},
"cases": [
{
"expression": "*",
"result": [{"sub1": {"foo": "one"}},
{"sub1": {"foo": "one"}}]
},
{
"expression": "*.sub1",
"result": [{"foo": "one"},
{"foo": "one"}]
},
{
"expression": "*.*",
"result": [[{"foo": "one"}],
[{"foo": "one"}]]
},
{
"expression": "*.*.foo[]",
"result": ["one", "one"]
},
{
"expression": "*.sub1.foo",
"result": ["one", "one"]
}
]
},
{
"given":
{"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]},
"cases": [
{
"expression": "foo[*].bar",
"result": ["one", "two", "three"]
},
{
"expression": "foo[*].notbar",
"result": ["four"]
}
]
},
{
"given":
[{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}],
"cases": [
{
"expression": "[*]",
"result": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]
},
{
"expression": "[*].bar",
"result": ["one", "two", "three"]
},
{
"expression": "[*].notbar",
"result": ["four"]
}
]
},
{
"given": {
"foo": {
"bar": [
{"baz": ["one", "two", "three"]},
{"baz": ["four", "five", "six"]},
{"baz": ["seven", "eight", "nine"]}
]
}
},
"cases": [
{
"expression": "foo.bar[*].baz",
"result": [["one", "two", "three"], ["four", "five", "six"], ["seven", "eight", "nine"]]
},
{
"expression": "foo.bar[*].baz[0]",
"result": ["one", "four", "seven"]
},
{
"expression": "foo.bar[*].baz[1]",
"result": ["two", "five", "eight"]
},
{
"expression": "foo.bar[*].baz[2]",
"result": ["three", "six", "nine"]
},
{
"expression": "foo.bar[*].baz[3]",
"result": []
}
]
},
{
"given": {
"foo": {
"bar": [["one", "two"], ["three", "four"]]
}
},
"cases": [
{
"expression": "foo.bar[*]",
"result": [["one", "two"], ["three", "four"]]
},
{
"expression": "foo.bar[0]",
"result": ["one", "two"]
},
{
"expression": "foo.bar[0][0]",
"result": "one"
},
{
"expression": "foo.bar[0][0][0]",
"result": null
},
{
"expression": "foo.bar[0][0][0][0]",
"result": null
},
{
"expression": "foo[0][0]",
"result": null
}
]
},
{
"given": {
"foo": [
{"bar": [{"kind": "basic"}, {"kind": "intermediate"}]},
{"bar": [{"kind": "advanced"}, {"kind": "expert"}]},
{"bar": "string"}
]
},
"cases": [
{
"expression": "foo[*].bar[*].kind",
"result": [["basic", "intermediate"], ["advanced", "expert"]]
},
{
"expression": "foo[*].bar[0].kind",
"result": ["basic", "advanced"]
}
]
},
{
"given": {
"foo": [
{"bar": {"kind": "basic"}},
{"bar": {"kind": "intermediate"}},
{"bar": {"kind": "advanced"}},
{"bar": {"kind": "expert"}},
{"bar": "string"}
]
},
"cases": [
{
"expression": "foo[*].bar.kind",
"result": ["basic", "intermediate", "advanced", "expert"]
}
]
},
{
"given": {
"foo": [{"bar": ["one", "two"]}, {"bar": ["three", "four"]}, {"bar": ["five"]}]
},
"cases": [
{
"expression": "foo[*].bar[0]",
"result": ["one", "three", "five"]
},
{
"expression": "foo[*].bar[1]",
"result": ["two", "four"]
},
{
"expression": "foo[*].bar[2]",
"result": []
}
]
},
{
"given": {
"foo": [{"bar": []}, {"bar": []}, {"bar": []}]
},
"cases": [
{
"expression": "foo[*].bar[0]",
"result": []
}
]
},
{
"given": {
"foo": [["one", "two"], ["three", "four"], ["five"]]
},
"cases": [
{
"expression": "foo[*][0]",
"result": ["one", "three", "five"]
},
{
"expression": "foo[*][1]",
"result": ["two", "four"]
}
]
},
{
"given": {
"foo": [
[
["one", "two"], ["three", "four"]
], [
["five", "six"], ["seven", "eight"]
], [
["nine"], ["ten"]
]
]
},
"cases": [
{
"expression": "foo[*][0]",
"result": [["one", "two"], ["five", "six"], ["nine"]]
},
{
"expression": "foo[*][1]",
"result": [["three", "four"], ["seven", "eight"], ["ten"]]
},
{
"expression": "foo[*][0][0]",
"result": ["one", "five", "nine"]
},
{
"expression": "foo[*][1][0]",
"result": ["three", "seven", "ten"]
},
{
"expression": "foo[*][0][1]",
"result": ["two", "six"]
},
{
"expression": "foo[*][1][1]",
"result": ["four", "eight"]
},
{
"expression": "foo[*][2]",
"result": []
},
{
"expression": "foo[*][2][2]",
"result": []
},
{
"expression": "bar[*]",
"result": null
},
{
"expression": "bar[*].baz[*]",
"result": null
}
]
},
{
"given": {
"string": "string",
"hash": {"foo": "bar", "bar": "baz"},
"number": 23,
"nullvalue": null
},
"cases": [
{
"expression": "string[*]",
"result": null
},
{
"expression": "hash[*]",
"result": null
},
{
"expression": "number[*]",
"result": null
},
{
"expression": "nullvalue[*]",
"result": null
},
{
"expression": "string[*].foo",
"result": null
},
{
"expression": "hash[*].foo",
"result": null
},
{
"expression": "number[*].foo",
"result": null
},
{
"expression": "nullvalue[*].foo",
"result": null
},
{
"expression": "nullvalue[*].foo[*].bar",
"result": null
}
]
},
{
"given": {
"string": "string",
"hash": {"foo": "val", "bar": "val"},
"number": 23,
"array": [1, 2, 3],
"nullvalue": null
},
"cases": [
{
"expression": "string.*",
"result": null
},
{
"expression": "hash.*",
"result": ["val", "val"]
},
{
"expression": "number.*",
"result": null
},
{
"expression": "array.*",
"result": null
},
{
"expression": "nullvalue.*",
"result": null
}
]
},
{
"given": {
"a": [0, 1, 2],
"b": [0, 1, 2]
},
"cases": [
{
"expression": "*[0]",
"result": [0, 0]
}
]
}
]

View File

@ -0,0 +1,840 @@
package jmespath
import (
"encoding/json"
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
"unicode/utf8"
)
type jpFunction func(arguments []interface{}) (interface{}, error)
type jpType string
const (
jpUnknown jpType = "unknown"
jpNumber jpType = "number"
jpString jpType = "string"
jpArray jpType = "array"
jpObject jpType = "object"
jpArrayNumber jpType = "array[number]"
jpArrayString jpType = "array[string]"
jpExpref jpType = "expref"
jpAny jpType = "any"
)
type functionEntry struct {
name string
arguments []argSpec
handler jpFunction
hasExpRef bool
}
type argSpec struct {
types []jpType
variadic bool
}
type byExprString struct {
intr *treeInterpreter
node ASTNode
items []interface{}
hasError bool
}
func (a *byExprString) Len() int {
return len(a.items)
}
func (a *byExprString) Swap(i, j int) {
a.items[i], a.items[j] = a.items[j], a.items[i]
}
func (a *byExprString) Less(i, j int) bool {
first, err := a.intr.Execute(a.node, a.items[i])
if err != nil {
a.hasError = true
// Return a dummy value.
return true
}
ith, ok := first.(string)
if !ok {
a.hasError = true
return true
}
second, err := a.intr.Execute(a.node, a.items[j])
if err != nil {
a.hasError = true
// Return a dummy value.
return true
}
jth, ok := second.(string)
if !ok {
a.hasError = true
return true
}
return ith < jth
}
type byExprFloat struct {
intr *treeInterpreter
node ASTNode
items []interface{}
hasError bool
}
func (a *byExprFloat) Len() int {
return len(a.items)
}
func (a *byExprFloat) Swap(i, j int) {
a.items[i], a.items[j] = a.items[j], a.items[i]
}
func (a *byExprFloat) Less(i, j int) bool {
first, err := a.intr.Execute(a.node, a.items[i])
if err != nil {
a.hasError = true
// Return a dummy value.
return true
}
ith, ok := first.(float64)
if !ok {
a.hasError = true
return true
}
second, err := a.intr.Execute(a.node, a.items[j])
if err != nil {
a.hasError = true
// Return a dummy value.
return true
}
jth, ok := second.(float64)
if !ok {
a.hasError = true
return true
}
return ith < jth
}
type functionCaller struct {
functionTable map[string]functionEntry
}
func newFunctionCaller() *functionCaller {
caller := &functionCaller{}
caller.functionTable = map[string]functionEntry{
"length": functionEntry{
name: "length",
arguments: []argSpec{
argSpec{types: []jpType{jpString, jpArray, jpObject}},
},
handler: jpfLength,
},
"starts_with": functionEntry{
name: "starts_with",
arguments: []argSpec{
argSpec{types: []jpType{jpString}},
argSpec{types: []jpType{jpString}},
},
handler: jpfStartsWith,
},
"abs": functionEntry{
name: "abs",
arguments: []argSpec{
argSpec{types: []jpType{jpNumber}},
},
handler: jpfAbs,
},
"avg": functionEntry{
name: "avg",
arguments: []argSpec{
argSpec{types: []jpType{jpArrayNumber}},
},
handler: jpfAvg,
},
"ceil": functionEntry{
name: "ceil",
arguments: []argSpec{
argSpec{types: []jpType{jpNumber}},
},
handler: jpfCeil,
},
"contains": functionEntry{
name: "contains",
arguments: []argSpec{
argSpec{types: []jpType{jpArray, jpString}},
argSpec{types: []jpType{jpAny}},
},
handler: jpfContains,
},
"ends_with": functionEntry{
name: "ends_with",
arguments: []argSpec{
argSpec{types: []jpType{jpString}},
argSpec{types: []jpType{jpString}},
},
handler: jpfEndsWith,
},
"floor": functionEntry{
name: "floor",
arguments: []argSpec{
argSpec{types: []jpType{jpNumber}},
},
handler: jpfFloor,
},
"map": functionEntry{
name: "amp",
arguments: []argSpec{
argSpec{types: []jpType{jpExpref}},
argSpec{types: []jpType{jpArray}},
},
handler: jpfMap,
hasExpRef: true,
},
"max": functionEntry{
name: "max",
arguments: []argSpec{
argSpec{types: []jpType{jpArrayNumber, jpArrayString}},
},
handler: jpfMax,
},
"merge": functionEntry{
name: "merge",
arguments: []argSpec{
argSpec{types: []jpType{jpObject}, variadic: true},
},
handler: jpfMerge,
},
"max_by": functionEntry{
name: "max_by",
arguments: []argSpec{
argSpec{types: []jpType{jpArray}},
argSpec{types: []jpType{jpExpref}},
},
handler: jpfMaxBy,
hasExpRef: true,
},
"sum": functionEntry{
name: "sum",
arguments: []argSpec{
argSpec{types: []jpType{jpArrayNumber}},
},
handler: jpfSum,
},
"min": functionEntry{
name: "min",
arguments: []argSpec{
argSpec{types: []jpType{jpArrayNumber, jpArrayString}},
},
handler: jpfMin,
},
"min_by": functionEntry{
name: "min_by",
arguments: []argSpec{
argSpec{types: []jpType{jpArray}},
argSpec{types: []jpType{jpExpref}},
},
handler: jpfMinBy,
hasExpRef: true,
},
"type": functionEntry{
name: "type",
arguments: []argSpec{
argSpec{types: []jpType{jpAny}},
},
handler: jpfType,
},
"keys": functionEntry{
name: "keys",
arguments: []argSpec{
argSpec{types: []jpType{jpObject}},
},
handler: jpfKeys,
},
"values": functionEntry{
name: "values",
arguments: []argSpec{
argSpec{types: []jpType{jpObject}},
},
handler: jpfValues,
},
"sort": functionEntry{
name: "sort",
arguments: []argSpec{
argSpec{types: []jpType{jpArrayString, jpArrayNumber}},
},
handler: jpfSort,
},
"sort_by": functionEntry{
name: "sort_by",
arguments: []argSpec{
argSpec{types: []jpType{jpArray}},
argSpec{types: []jpType{jpExpref}},
},
handler: jpfSortBy,
hasExpRef: true,
},
"join": functionEntry{
name: "join",
arguments: []argSpec{
argSpec{types: []jpType{jpString}},
argSpec{types: []jpType{jpArrayString}},
},
handler: jpfJoin,
},
"reverse": functionEntry{
name: "reverse",
arguments: []argSpec{
argSpec{types: []jpType{jpArray, jpString}},
},
handler: jpfReverse,
},
"to_array": functionEntry{
name: "to_array",
arguments: []argSpec{
argSpec{types: []jpType{jpAny}},
},
handler: jpfToArray,
},
"to_string": functionEntry{
name: "to_string",
arguments: []argSpec{
argSpec{types: []jpType{jpAny}},
},
handler: jpfToString,
},
"to_number": functionEntry{
name: "to_number",
arguments: []argSpec{
argSpec{types: []jpType{jpAny}},
},
handler: jpfToNumber,
},
"not_null": functionEntry{
name: "not_null",
arguments: []argSpec{
argSpec{types: []jpType{jpAny}, variadic: true},
},
handler: jpfNotNull,
},
}
return caller
}
func (e *functionEntry) resolveArgs(arguments []interface{}) ([]interface{}, error) {
if len(e.arguments) == 0 {
return arguments, nil
}
if !e.arguments[len(e.arguments)-1].variadic {
if len(e.arguments) != len(arguments) {
return nil, errors.New("incorrect number of args")
}
for i, spec := range e.arguments {
userArg := arguments[i]
err := spec.typeCheck(userArg)
if err != nil {
return nil, err
}
}
return arguments, nil
}
if len(arguments) < len(e.arguments) {
return nil, errors.New("Invalid arity.")
}
return arguments, nil
}
func (a *argSpec) typeCheck(arg interface{}) error {
for _, t := range a.types {
switch t {
case jpNumber:
if _, ok := arg.(float64); ok {
return nil
}
case jpString:
if _, ok := arg.(string); ok {
return nil
}
case jpArray:
if _, ok := arg.([]interface{}); ok {
return nil
}
case jpObject:
if _, ok := arg.(map[string]interface{}); ok {
return nil
}
case jpArrayNumber:
if _, ok := toArrayNum(arg); ok {
return nil
}
case jpArrayString:
if _, ok := toArrayStr(arg); ok {
return nil
}
case jpAny:
return nil
case jpExpref:
if _, ok := arg.(expRef); ok {
return nil
}
}
}
return fmt.Errorf("Invalid type for: %v, expected: %#v", arg, a.types)
}
func (f *functionCaller) CallFunction(name string, arguments []interface{}, intr *treeInterpreter) (interface{}, error) {
entry, ok := f.functionTable[name]
if !ok {
return nil, errors.New("unknown function: " + name)
}
resolvedArgs, err := entry.resolveArgs(arguments)
if err != nil {
return nil, err
}
if entry.hasExpRef {
var extra []interface{}
extra = append(extra, intr)
resolvedArgs = append(extra, resolvedArgs...)
}
return entry.handler(resolvedArgs)
}
func jpfAbs(arguments []interface{}) (interface{}, error) {
num := arguments[0].(float64)
return math.Abs(num), nil
}
func jpfLength(arguments []interface{}) (interface{}, error) {
arg := arguments[0]
if c, ok := arg.(string); ok {
return float64(utf8.RuneCountInString(c)), nil
} else if c, ok := arg.([]interface{}); ok {
return float64(len(c)), nil
} else if c, ok := arg.(map[string]interface{}); ok {
return float64(len(c)), nil
}
return nil, errors.New("could not compute length()")
}
func jpfStartsWith(arguments []interface{}) (interface{}, error) {
search := arguments[0].(string)
prefix := arguments[1].(string)
return strings.HasPrefix(search, prefix), nil
}
func jpfAvg(arguments []interface{}) (interface{}, error) {
// We've already type checked the value so we can safely use
// type assertions.
args := arguments[0].([]interface{})
length := float64(len(args))
numerator := 0.0
for _, n := range args {
numerator += n.(float64)
}
return numerator / length, nil
}
func jpfCeil(arguments []interface{}) (interface{}, error) {
val := arguments[0].(float64)
return math.Ceil(val), nil
}
func jpfContains(arguments []interface{}) (interface{}, error) {
search := arguments[0]
el := arguments[1]
if searchStr, ok := search.(string); ok {
if elStr, ok := el.(string); ok {
return strings.Index(searchStr, elStr) != -1, nil
}
return false, nil
}
// Otherwise this is a generic contains for []interface{}
general := search.([]interface{})
for _, item := range general {
if item == el {
return true, nil
}
}
return false, nil
}
func jpfEndsWith(arguments []interface{}) (interface{}, error) {
search := arguments[0].(string)
suffix := arguments[1].(string)
return strings.HasSuffix(search, suffix), nil
}
func jpfFloor(arguments []interface{}) (interface{}, error) {
val := arguments[0].(float64)
return math.Floor(val), nil
}
func jpfMap(arguments []interface{}) (interface{}, error) {
intr := arguments[0].(*treeInterpreter)
exp := arguments[1].(expRef)
node := exp.ref
arr := arguments[2].([]interface{})
mapped := make([]interface{}, 0, len(arr))
for _, value := range arr {
current, err := intr.Execute(node, value)
if err != nil {
return nil, err
}
mapped = append(mapped, current)
}
return mapped, nil
}
func jpfMax(arguments []interface{}) (interface{}, error) {
if items, ok := toArrayNum(arguments[0]); ok {
if len(items) == 0 {
return nil, nil
}
if len(items) == 1 {
return items[0], nil
}
best := items[0]
for _, item := range items[1:] {
if item > best {
best = item
}
}
return best, nil
}
// Otherwise we're dealing with a max() of strings.
items, _ := toArrayStr(arguments[0])
if len(items) == 0 {
return nil, nil
}
if len(items) == 1 {
return items[0], nil
}
best := items[0]
for _, item := range items[1:] {
if item > best {
best = item
}
}
return best, nil
}
func jpfMerge(arguments []interface{}) (interface{}, error) {
final := make(map[string]interface{})
for _, m := range arguments {
mapped := m.(map[string]interface{})
for key, value := range mapped {
final[key] = value
}
}
return final, nil
}
func jpfMaxBy(arguments []interface{}) (interface{}, error) {
intr := arguments[0].(*treeInterpreter)
arr := arguments[1].([]interface{})
exp := arguments[2].(expRef)
node := exp.ref
if len(arr) == 0 {
return nil, nil
} else if len(arr) == 1 {
return arr[0], nil
}
start, err := intr.Execute(node, arr[0])
if err != nil {
return nil, err
}
switch t := start.(type) {
case float64:
bestVal := t
bestItem := arr[0]
for _, item := range arr[1:] {
result, err := intr.Execute(node, item)
if err != nil {
return nil, err
}
current, ok := result.(float64)
if !ok {
return nil, errors.New("invalid type, must be number")
}
if current > bestVal {
bestVal = current
bestItem = item
}
}
return bestItem, nil
case string:
bestVal := t
bestItem := arr[0]
for _, item := range arr[1:] {
result, err := intr.Execute(node, item)
if err != nil {
return nil, err
}
current, ok := result.(string)
if !ok {
return nil, errors.New("invalid type, must be string")
}
if current > bestVal {
bestVal = current
bestItem = item
}
}
return bestItem, nil
default:
return nil, errors.New("invalid type, must be number of string")
}
}
func jpfSum(arguments []interface{}) (interface{}, error) {
items, _ := toArrayNum(arguments[0])
sum := 0.0
for _, item := range items {
sum += item
}
return sum, nil
}
func jpfMin(arguments []interface{}) (interface{}, error) {
if items, ok := toArrayNum(arguments[0]); ok {
if len(items) == 0 {
return nil, nil
}
if len(items) == 1 {
return items[0], nil
}
best := items[0]
for _, item := range items[1:] {
if item < best {
best = item
}
}
return best, nil
}
items, _ := toArrayStr(arguments[0])
if len(items) == 0 {
return nil, nil
}
if len(items) == 1 {
return items[0], nil
}
best := items[0]
for _, item := range items[1:] {
if item < best {
best = item
}
}
return best, nil
}
func jpfMinBy(arguments []interface{}) (interface{}, error) {
intr := arguments[0].(*treeInterpreter)
arr := arguments[1].([]interface{})
exp := arguments[2].(expRef)
node := exp.ref
if len(arr) == 0 {
return nil, nil
} else if len(arr) == 1 {
return arr[0], nil
}
start, err := intr.Execute(node, arr[0])
if err != nil {
return nil, err
}
if t, ok := start.(float64); ok {
bestVal := t
bestItem := arr[0]
for _, item := range arr[1:] {
result, err := intr.Execute(node, item)
if err != nil {
return nil, err
}
current, ok := result.(float64)
if !ok {
return nil, errors.New("invalid type, must be number")
}
if current < bestVal {
bestVal = current
bestItem = item
}
}
return bestItem, nil
} else if t, ok := start.(string); ok {
bestVal := t
bestItem := arr[0]
for _, item := range arr[1:] {
result, err := intr.Execute(node, item)
if err != nil {
return nil, err
}
current, ok := result.(string)
if !ok {
return nil, errors.New("invalid type, must be string")
}
if current < bestVal {
bestVal = current
bestItem = item
}
}
return bestItem, nil
} else {
return nil, errors.New("invalid type, must be number of string")
}
}
func jpfType(arguments []interface{}) (interface{}, error) {
arg := arguments[0]
if _, ok := arg.(float64); ok {
return "number", nil
}
if _, ok := arg.(string); ok {
return "string", nil
}
if _, ok := arg.([]interface{}); ok {
return "array", nil
}
if _, ok := arg.(map[string]interface{}); ok {
return "object", nil
}
if arg == nil {
return "null", nil
}
if arg == true || arg == false {
return "boolean", nil
}
return nil, errors.New("unknown type")
}
func jpfKeys(arguments []interface{}) (interface{}, error) {
arg := arguments[0].(map[string]interface{})
collected := make([]interface{}, 0, len(arg))
for key := range arg {
collected = append(collected, key)
}
return collected, nil
}
func jpfValues(arguments []interface{}) (interface{}, error) {
arg := arguments[0].(map[string]interface{})
collected := make([]interface{}, 0, len(arg))
for _, value := range arg {
collected = append(collected, value)
}
return collected, nil
}
func jpfSort(arguments []interface{}) (interface{}, error) {
if items, ok := toArrayNum(arguments[0]); ok {
d := sort.Float64Slice(items)
sort.Stable(d)
final := make([]interface{}, len(d))
for i, val := range d {
final[i] = val
}
return final, nil
}
// Otherwise we're dealing with sort()'ing strings.
items, _ := toArrayStr(arguments[0])
d := sort.StringSlice(items)
sort.Stable(d)
final := make([]interface{}, len(d))
for i, val := range d {
final[i] = val
}
return final, nil
}
func jpfSortBy(arguments []interface{}) (interface{}, error) {
intr := arguments[0].(*treeInterpreter)
arr := arguments[1].([]interface{})
exp := arguments[2].(expRef)
node := exp.ref
if len(arr) == 0 {
return arr, nil
} else if len(arr) == 1 {
return arr, nil
}
start, err := intr.Execute(node, arr[0])
if err != nil {
return nil, err
}
if _, ok := start.(float64); ok {
sortable := &byExprFloat{intr, node, arr, false}
sort.Stable(sortable)
if sortable.hasError {
return nil, errors.New("error in sort_by comparison")
}
return arr, nil
} else if _, ok := start.(string); ok {
sortable := &byExprString{intr, node, arr, false}
sort.Stable(sortable)
if sortable.hasError {
return nil, errors.New("error in sort_by comparison")
}
return arr, nil
} else {
return nil, errors.New("invalid type, must be number of string")
}
}
func jpfJoin(arguments []interface{}) (interface{}, error) {
sep := arguments[0].(string)
// We can't just do arguments[1].([]string), we have to
// manually convert each item to a string.
arrayStr := []string{}
for _, item := range arguments[1].([]interface{}) {
arrayStr = append(arrayStr, item.(string))
}
return strings.Join(arrayStr, sep), nil
}
func jpfReverse(arguments []interface{}) (interface{}, error) {
if s, ok := arguments[0].(string); ok {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r), nil
}
items := arguments[0].([]interface{})
length := len(items)
reversed := make([]interface{}, length)
for i, item := range items {
reversed[length-(i+1)] = item
}
return reversed, nil
}
func jpfToArray(arguments []interface{}) (interface{}, error) {
if _, ok := arguments[0].([]interface{}); ok {
return arguments[0], nil
}
return arguments[:1:1], nil
}
func jpfToString(arguments []interface{}) (interface{}, error) {
if v, ok := arguments[0].(string); ok {
return v, nil
}
result, err := json.Marshal(arguments[0])
if err != nil {
return nil, err
}
return string(result), nil
}
func jpfToNumber(arguments []interface{}) (interface{}, error) {
arg := arguments[0]
if v, ok := arg.(float64); ok {
return v, nil
}
if v, ok := arg.(string); ok {
conv, err := strconv.ParseFloat(v, 64)
if err != nil {
return nil, nil
}
return conv, nil
}
if _, ok := arg.([]interface{}); ok {
return nil, nil
}
if _, ok := arg.(map[string]interface{}); ok {
return nil, nil
}
if arg == nil {
return nil, nil
}
if arg == true || arg == false {
return nil, nil
}
return nil, errors.New("unknown type")
}
func jpfNotNull(arguments []interface{}) (interface{}, error) {
for _, arg := range arguments {
if arg != nil {
return arg, nil
}
}
return nil, nil
}

View File

@ -0,0 +1 @@
foo

View File

@ -0,0 +1 @@
foo.bar

View File

@ -0,0 +1 @@
ends_with(str, 'SStr')

View File

@ -0,0 +1 @@
ends_with(str, 'foo')

View File

@ -0,0 +1 @@
floor(`1.2`)

View File

@ -0,0 +1 @@
floor(decimals[0])

View File

@ -0,0 +1 @@
floor(foo)

View File

@ -0,0 +1 @@
length('abc')

View File

@ -0,0 +1 @@
length('')

View File

@ -0,0 +1 @@
length(@)

View File

@ -0,0 +1 @@
length(strings[0])

View File

@ -0,0 +1 @@
length(str)

View File

@ -0,0 +1 @@
length(array)

View File

@ -0,0 +1 @@
length(strings[0])

View File

@ -0,0 +1 @@
max(strings)

View File

@ -0,0 +1 @@
merge(`{}`)

View File

@ -0,0 +1 @@
merge(`{}`, `{}`)

View File

@ -0,0 +1 @@
two

Some files were not shown because too many files have changed in this diff Show More