Merge branch 'master' into websocket

This commit is contained in:
Torkel Ödegaard
2017-01-31 08:56:49 +01:00
283 changed files with 29756 additions and 7727 deletions
+14368
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
package s3
import (
"io/ioutil"
"regexp"
"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"
)
var reBucketLocation = regexp.MustCompile(`>([^<>]+)<\/Location`)
func buildGetBucketLocation(r *request.Request) {
if r.DataFilled() {
out := r.Data.(*GetBucketLocationOutput)
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError", "failed reading response body", err)
return
}
match := reBucketLocation.FindSubmatch(b)
if len(match) > 1 {
loc := string(match[1])
out.LocationConstraint = &loc
}
}
}
func populateLocationConstraint(r *request.Request) {
if r.ParamsFilled() && aws.StringValue(r.Config.Region) != "us-east-1" {
in := r.Params.(*CreateBucketInput)
if in.CreateBucketConfiguration == nil {
r.Params = awsutil.CopyOf(r.Params)
in = r.Params.(*CreateBucketInput)
in.CreateBucketConfiguration = &CreateBucketConfiguration{
LocationConstraint: r.Config.Region,
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
package s3
import (
"crypto/md5"
"encoding/base64"
"io"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
// contentMD5 computes and sets the HTTP Content-MD5 header for requests that
// require it.
func contentMD5(r *request.Request) {
h := md5.New()
// hash the body. seek back to the first position after reading to reset
// the body for transmission. copy errors may be assumed to be from the
// body.
_, err := io.Copy(h, r.Body)
if err != nil {
r.Error = awserr.New("ContentMD5", "failed to read body", err)
return
}
_, err = r.Body.Seek(0, 0)
if err != nil {
r.Error = awserr.New("ContentMD5", "failed to seek body", err)
return
}
// encode the md5 checksum in base64 and set the request header.
sum := h.Sum(nil)
sum64 := make([]byte, base64.StdEncoding.EncodedLen(len(sum)))
base64.StdEncoding.Encode(sum64, sum)
r.HTTPRequest.Header.Set("Content-MD5", string(sum64))
}
+46
View File
@@ -0,0 +1,46 @@
package s3
import (
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/request"
)
func init() {
initClient = defaultInitClientFn
initRequest = defaultInitRequestFn
}
func defaultInitClientFn(c *client.Client) {
// Support building custom endpoints based on config
c.Handlers.Build.PushFront(updateEndpointForS3Config)
// Require SSL when using SSE keys
c.Handlers.Validate.PushBack(validateSSERequiresSSL)
c.Handlers.Build.PushBack(computeSSEKeys)
// S3 uses custom error unmarshaling logic
c.Handlers.UnmarshalError.Clear()
c.Handlers.UnmarshalError.PushBack(unmarshalError)
}
func defaultInitRequestFn(r *request.Request) {
// Add reuest handlers for specific platforms.
// e.g. 100-continue support for PUT requests using Go 1.6
platformRequestHandlers(r)
switch r.Operation.Name {
case opPutBucketCors, opPutBucketLifecycle, opPutBucketPolicy,
opPutBucketTagging, opDeleteObjects, opPutBucketLifecycleConfiguration,
opPutBucketReplication:
// These S3 operations require Content-MD5 to be set
r.Handlers.Build.PushBack(contentMD5)
case opGetBucketLocation:
// GetBucketLocation has custom parsing logic
r.Handlers.Unmarshal.PushFront(buildGetBucketLocation)
case opCreateBucket:
// Auto-populate LocationConstraint with current region
r.Handlers.Validate.PushFront(populateLocationConstraint)
case opCopyObject, opUploadPartCopy, opCompleteMultipartUpload:
r.Handlers.Unmarshal.PushFront(copyMultipartStatusOKUnmarhsalError)
}
}
+186
View File
@@ -0,0 +1,186 @@
package s3
import (
"bytes"
"fmt"
"net/url"
"regexp"
"strings"
"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"
)
// an operationBlacklist is a list of operation names that should a
// request handler should not be executed with.
type operationBlacklist []string
// Continue will return true of the Request's operation name is not
// in the blacklist. False otherwise.
func (b operationBlacklist) Continue(r *request.Request) bool {
for i := 0; i < len(b); i++ {
if b[i] == r.Operation.Name {
return false
}
}
return true
}
var accelerateOpBlacklist = operationBlacklist{
opListBuckets, opCreateBucket, opDeleteBucket,
}
// Request handler to automatically add the bucket name to the endpoint domain
// if possible. This style of bucket is valid for all bucket names which are
// DNS compatible and do not contain "."
func updateEndpointForS3Config(r *request.Request) {
forceHostStyle := aws.BoolValue(r.Config.S3ForcePathStyle)
accelerate := aws.BoolValue(r.Config.S3UseAccelerate)
if accelerate && accelerateOpBlacklist.Continue(r) {
if forceHostStyle {
if r.Config.Logger != nil {
r.Config.Logger.Log("ERROR: aws.Config.S3UseAccelerate is not compatible with aws.Config.S3ForcePathStyle, ignoring S3ForcePathStyle.")
}
}
updateEndpointForAccelerate(r)
} else if !forceHostStyle && r.Operation.Name != opGetBucketLocation {
updateEndpointForHostStyle(r)
}
}
func updateEndpointForHostStyle(r *request.Request) {
bucket, ok := bucketNameFromReqParams(r.Params)
if !ok {
// Ignore operation requests if the bucketname was not provided
// if this is an input validation error the validation handler
// will report it.
return
}
if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
// bucket name must be valid to put into the host
return
}
moveBucketToHost(r.HTTPRequest.URL, bucket)
}
var (
accelElem = []byte("s3-accelerate.dualstack.")
)
func updateEndpointForAccelerate(r *request.Request) {
bucket, ok := bucketNameFromReqParams(r.Params)
if !ok {
// Ignore operation requests if the bucketname was not provided
// if this is an input validation error the validation handler
// will report it.
return
}
if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
r.Error = awserr.New("InvalidParameterException",
fmt.Sprintf("bucket name %s is not compatibile with S3 Accelerate", bucket),
nil)
return
}
// Change endpoint from s3(-[a-z0-1-])?.amazonaws.com to s3-accelerate.amazonaws.com
r.HTTPRequest.URL.Host = replaceHostRegion(r.HTTPRequest.URL.Host, "accelerate")
if aws.BoolValue(r.Config.UseDualStack) {
host := []byte(r.HTTPRequest.URL.Host)
// Strip region from hostname
if idx := bytes.Index(host, accelElem); idx >= 0 {
start := idx + len(accelElem)
if end := bytes.IndexByte(host[start:], '.'); end >= 0 {
end += start + 1
copy(host[start:], host[end:])
host = host[:len(host)-(end-start)]
r.HTTPRequest.URL.Host = string(host)
}
}
}
moveBucketToHost(r.HTTPRequest.URL, bucket)
}
// Attempts to retrieve the bucket name from the request input parameters.
// If no bucket is found, or the field is empty "", false will be returned.
func bucketNameFromReqParams(params interface{}) (string, bool) {
b, _ := awsutil.ValuesAtPath(params, "Bucket")
if len(b) == 0 {
return "", false
}
if bucket, ok := b[0].(*string); ok {
if bucketStr := aws.StringValue(bucket); bucketStr != "" {
return bucketStr, true
}
}
return "", false
}
// hostCompatibleBucketName returns true if the request should
// put the bucket in the host. This is false if S3ForcePathStyle is
// explicitly set or if the bucket is not DNS compatible.
func hostCompatibleBucketName(u *url.URL, bucket string) bool {
// Bucket might be DNS compatible but dots in the hostname will fail
// certificate validation, so do not use host-style.
if u.Scheme == "https" && strings.Contains(bucket, ".") {
return false
}
// if the bucket is DNS compatible
return dnsCompatibleBucketName(bucket)
}
var reDomain = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`)
var reIPAddress = regexp.MustCompile(`^(\d+\.){3}\d+$`)
// dnsCompatibleBucketName returns true if the bucket name is DNS compatible.
// Buckets created outside of the classic region MUST be DNS compatible.
func dnsCompatibleBucketName(bucket string) bool {
return reDomain.MatchString(bucket) &&
!reIPAddress.MatchString(bucket) &&
!strings.Contains(bucket, "..")
}
// moveBucketToHost moves the bucket name from the URI path to URL host.
func moveBucketToHost(u *url.URL, bucket string) {
u.Host = bucket + "." + u.Host
u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1)
if u.Path == "" {
u.Path = "/"
}
}
const s3HostPrefix = "s3"
// replaceHostRegion replaces the S3 region string in the host with the
// value provided. If v is empty the host prefix returned will be s3.
func replaceHostRegion(host, v string) string {
if !strings.HasPrefix(host, s3HostPrefix) {
return host
}
suffix := host[len(s3HostPrefix):]
for i := len(s3HostPrefix); i < len(host); i++ {
if host[i] == '.' {
// Trim until '.' leave the it in place.
suffix = host[i:]
break
}
}
if len(v) == 0 {
return fmt.Sprintf("s3%s", suffix)
}
return fmt.Sprintf("s3-%s%s", v, suffix)
}
+8
View File
@@ -0,0 +1,8 @@
// +build !go1.6
package s3
import "github.com/aws/aws-sdk-go/aws/request"
func platformRequestHandlers(r *request.Request) {
}
+28
View File
@@ -0,0 +1,28 @@
// +build go1.6
package s3
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
)
func platformRequestHandlers(r *request.Request) {
if r.Operation.HTTPMethod == "PUT" {
// 100-Continue should only be used on put requests.
r.Handlers.Sign.PushBack(add100Continue)
}
}
func add100Continue(r *request.Request) {
if aws.BoolValue(r.Config.S3Disable100Continue) {
return
}
if r.HTTPRequest.ContentLength < 1024*1024*2 {
// Ignore requests smaller than 2MB. This helps prevent delaying
// requests unnecessarily.
return
}
r.HTTPRequest.Header.Set("Expect", "100-Continue")
}
+86
View File
@@ -0,0 +1,86 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package s3
import (
"github.com/aws/aws-sdk-go/aws"
"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/signer/v4"
"github.com/aws/aws-sdk-go/private/protocol/restxml"
)
// S3 is a client for Amazon S3.
//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 S3 struct {
*client.Client
}
// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
// A ServiceName is the name of the service the client will make API calls to.
const ServiceName = "s3"
// New creates a new instance of the S3 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 S3 client from just a session.
// svc := s3.New(mySession)
//
// // Create a S3 client with additional configuration
// svc := s3.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *S3 {
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) *S3 {
svc := &S3{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "2006-03-01",
},
handlers,
),
}
// Handlers
svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
svc.Handlers.Build.PushBackNamed(restxml.BuildHandler)
svc.Handlers.Unmarshal.PushBackNamed(restxml.UnmarshalHandler)
svc.Handlers.UnmarshalMeta.PushBackNamed(restxml.UnmarshalMetaHandler)
svc.Handlers.UnmarshalError.PushBackNamed(restxml.UnmarshalErrorHandler)
// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
return svc
}
// newRequest creates a new request for a S3 operation and runs any
// custom request initialization.
func (c *S3) newRequest(op *request.Operation, params, data interface{}) *request.Request {
req := c.NewRequest(op, params, data)
// Run custom request initialization if present
if initRequest != nil {
initRequest(req)
}
return req
}
+44
View File
@@ -0,0 +1,44 @@
package s3
import (
"crypto/md5"
"encoding/base64"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/aws/aws-sdk-go/aws/request"
)
var errSSERequiresSSL = awserr.New("ConfigError", "cannot send SSE keys over HTTP.", nil)
func validateSSERequiresSSL(r *request.Request) {
if r.HTTPRequest.URL.Scheme != "https" {
p, _ := awsutil.ValuesAtPath(r.Params, "SSECustomerKey||CopySourceSSECustomerKey")
if len(p) > 0 {
r.Error = errSSERequiresSSL
}
}
}
func computeSSEKeys(r *request.Request) {
headers := []string{
"x-amz-server-side-encryption-customer-key",
"x-amz-copy-source-server-side-encryption-customer-key",
}
for _, h := range headers {
md5h := h + "-md5"
if key := r.HTTPRequest.Header.Get(h); key != "" {
// Base64-encode the value
b64v := base64.StdEncoding.EncodeToString([]byte(key))
r.HTTPRequest.Header.Set(h, b64v)
// Add MD5 if it wasn't computed
if r.HTTPRequest.Header.Get(md5h) == "" {
sum := md5.Sum([]byte(key))
b64sum := base64.StdEncoding.EncodeToString(sum[:])
r.HTTPRequest.Header.Set(md5h, b64sum)
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
package s3
import (
"bytes"
"io/ioutil"
"net/http"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
func copyMultipartStatusOKUnmarhsalError(r *request.Request) {
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError", "unable to read response body", err)
return
}
body := bytes.NewReader(b)
r.HTTPResponse.Body = aws.ReadSeekCloser(body)
defer r.HTTPResponse.Body.(aws.ReaderSeekerCloser).Seek(0, 0)
if body.Len() == 0 {
// If there is no body don't attempt to parse the body.
return
}
unmarshalError(r)
if err, ok := r.Error.(awserr.Error); ok && err != nil {
if err.Code() == "SerializationError" {
r.Error = nil
return
}
r.HTTPResponse.StatusCode = http.StatusServiceUnavailable
}
}
+65
View File
@@ -0,0 +1,65 @@
package s3
import (
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
type xmlErrorResponse struct {
XMLName xml.Name `xml:"Error"`
Code string `xml:"Code"`
Message string `xml:"Message"`
}
func unmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close()
defer io.Copy(ioutil.Discard, r.HTTPResponse.Body)
// Bucket exists in a different region, and request needs
// to be made to the correct region.
if r.HTTPResponse.StatusCode == http.StatusMovedPermanently {
r.Error = awserr.NewRequestFailure(
awserr.New("BucketRegionError",
fmt.Sprintf("incorrect region, the bucket is not in '%s' region",
aws.StringValue(r.Config.Region)),
nil),
r.HTTPResponse.StatusCode,
r.RequestID,
)
return
}
var errCode, errMsg string
// Attempt to parse error from body if it is known
resp := &xmlErrorResponse{}
err := xml.NewDecoder(r.HTTPResponse.Body).Decode(resp)
if err != nil && err != io.EOF {
errCode = "SerializationError"
errMsg = "failed to decode S3 XML error response"
} else {
errCode = resp.Code
errMsg = resp.Message
}
// Fallback to status code converted to message if still no error code
if len(errCode) == 0 {
statusText := http.StatusText(r.HTTPResponse.StatusCode)
errCode = strings.Replace(statusText, " ", "", -1)
errMsg = statusText
}
r.Error = awserr.NewRequestFailure(
awserr.New(errCode, errMsg, nil),
r.HTTPResponse.StatusCode,
r.RequestID,
)
}
+139
View File
@@ -0,0 +1,139 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package s3
import (
"github.com/aws/aws-sdk-go/private/waiter"
)
// WaitUntilBucketExists uses the Amazon S3 API operation
// HeadBucket to wait for a condition to be met before returning.
// If the condition is not meet within the max attempt window an error will
// be returned.
func (c *S3) WaitUntilBucketExists(input *HeadBucketInput) error {
waiterCfg := waiter.Config{
Operation: "HeadBucket",
Delay: 5,
MaxAttempts: 20,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "status",
Argument: "",
Expected: 200,
},
{
State: "success",
Matcher: "status",
Argument: "",
Expected: 301,
},
{
State: "success",
Matcher: "status",
Argument: "",
Expected: 403,
},
{
State: "retry",
Matcher: "status",
Argument: "",
Expected: 404,
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
// WaitUntilBucketNotExists uses the Amazon S3 API operation
// HeadBucket to wait for a condition to be met before returning.
// If the condition is not meet within the max attempt window an error will
// be returned.
func (c *S3) WaitUntilBucketNotExists(input *HeadBucketInput) error {
waiterCfg := waiter.Config{
Operation: "HeadBucket",
Delay: 5,
MaxAttempts: 20,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "status",
Argument: "",
Expected: 404,
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
// WaitUntilObjectExists uses the Amazon S3 API operation
// HeadObject to wait for a condition to be met before returning.
// If the condition is not meet within the max attempt window an error will
// be returned.
func (c *S3) WaitUntilObjectExists(input *HeadObjectInput) error {
waiterCfg := waiter.Config{
Operation: "HeadObject",
Delay: 5,
MaxAttempts: 20,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "status",
Argument: "",
Expected: 200,
},
{
State: "retry",
Matcher: "status",
Argument: "",
Expected: 404,
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
// WaitUntilObjectNotExists uses the Amazon S3 API operation
// HeadObject to wait for a condition to be met before returning.
// If the condition is not meet within the max attempt window an error will
// be returned.
func (c *S3) WaitUntilObjectNotExists(input *HeadObjectInput) error {
waiterCfg := waiter.Config{
Operation: "HeadObject",
Delay: 5,
MaxAttempts: 20,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "status",
Argument: "",
Expected: 404,
},
},
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
}
return w.Wait()
}
-10
View File
@@ -1,10 +0,0 @@
Copyright (c) 2014, Greg Roseberry
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-75
View File
@@ -1,75 +0,0 @@
## null [![GoDoc](https://godoc.org/github.com/guregu/null?status.svg)](https://godoc.org/github.com/guregu/null) [![Coverage](http://gocover.io/_badge/github.com/guregu/null)](http://gocover.io/github.com/guregu/null)
`import "gopkg.in/guregu/null.v3"`
null is a library with reasonable options for dealing with nullable SQL and JSON values
There are two packages: `null` and its subpackage `zero`.
Types in `null` will only be considered null on null input, and will JSON encode to `null`. If you need zero and null be considered separate values, use these.
Types in `zero` are treated like zero values in Go: blank string input will produce a null `zero.String`, and null Strings will JSON encode to `""`. Zero values of these types will be considered null to SQL. If you need zero and null treated the same, use these.
All types implement `sql.Scanner` and `driver.Valuer`, so you can use this library in place of `sql.NullXXX`. All types also implement: `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `json.Marshaler`, and `json.Unmarshaler`.
### null package
`import "gopkg.in/guregu/null.v3"`
#### null.String
Nullable string.
Marshals to JSON null if SQL source data is null. Zero (blank) input will not produce a null String. Can unmarshal from `sql.NullString` JSON input or string input.
#### null.Int
Nullable int64.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int. Can unmarshal from `sql.NullInt64` JSON input.
#### null.Float
Nullable float64.
Marshals to JSON null if SQL source data is null. Zero input will not produce a null Float. Can unmarshal from `sql.NullFloat64` JSON input.
#### null.Bool
Nullable bool.
Marshals to JSON null if SQL source data is null. False input will not produce a null Bool. Can unmarshal from `sql.NullBool` JSON input.
#### null.Time
Marshals to JSON null if SQL source data is null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input.
### zero package
`import "gopkg.in/guregu/null.v3/zero"`
#### zero.String
Nullable string.
Will marshal to a blank string if null. Blank string input produces a null String. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullString` JSON input.
#### zero.Int
Nullable int64.
Will marshal to 0 if null. 0 produces a null Int. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullInt64` JSON input.
#### zero.Float
Nullable float64.
Will marshal to 0 if null. 0.0 produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullFloat64` JSON input.
#### zero.Bool
Nullable bool.
Will marshal to false if null. `false` produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullBool` JSON input.
#### zero.Time
Will marshal to the zero time if null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input.
### Bugs
`json`'s `",omitempty"` struct tag does not work correctly right now. It will never omit a null or empty String. This might be [fixed eventually](https://github.com/golang/go/issues/4357).
### License
BSD
-129
View File
@@ -1,129 +0,0 @@
package null
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"reflect"
)
// Bool is a nullable bool.
// It does not consider false values to be null.
// It will decode to null, not false, if null.
type Bool struct {
sql.NullBool
}
// NewBool creates a new Bool
func NewBool(b bool, valid bool) Bool {
return Bool{
NullBool: sql.NullBool{
Bool: b,
Valid: valid,
},
}
}
// BoolFrom creates a new Bool that will always be valid.
func BoolFrom(b bool) Bool {
return NewBool(b, true)
}
// BoolFromPtr creates a new Bool that will be null if f is nil.
func BoolFromPtr(b *bool) Bool {
if b == nil {
return NewBool(false, false)
}
return NewBool(*b, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Bool.
// It also supports unmarshalling a sql.NullBool.
func (b *Bool) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch x := v.(type) {
case bool:
b.Bool = x
case map[string]interface{}:
err = json.Unmarshal(data, &b.NullBool)
case nil:
b.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Bool", reflect.TypeOf(v).Name())
}
b.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Bool if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (b *Bool) UnmarshalText(text []byte) error {
str := string(text)
switch str {
case "", "null":
b.Valid = false
return nil
case "true":
b.Bool = true
case "false":
b.Bool = false
default:
b.Valid = false
return errors.New("invalid input:" + str)
}
b.Valid = true
return nil
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Bool is null.
func (b Bool) MarshalJSON() ([]byte, error) {
if !b.Valid {
return []byte("null"), nil
}
if !b.Bool {
return []byte("false"), nil
}
return []byte("true"), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Bool is null.
func (b Bool) MarshalText() ([]byte, error) {
if !b.Valid {
return []byte{}, nil
}
if !b.Bool {
return []byte("false"), nil
}
return []byte("true"), nil
}
// SetValid changes this Bool's value and also sets it to be non-null.
func (b *Bool) SetValid(v bool) {
b.Bool = v
b.Valid = true
}
// Ptr returns a pointer to this Bool's value, or a nil pointer if this Bool is null.
func (b Bool) Ptr() *bool {
if !b.Valid {
return nil
}
return &b.Bool
}
// IsZero returns true for invalid Bools, for future omitempty support (Go 1.4?)
// A non-null Bool with a 0 value will not be considered zero.
func (b Bool) IsZero() bool {
return !b.Valid
}
-117
View File
@@ -1,117 +0,0 @@
package null
import (
"database/sql"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// Float is a nullable float64.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Float struct {
sql.NullFloat64
}
// NewFloat creates a new Float
func NewFloat(f float64, valid bool) Float {
return Float{
NullFloat64: sql.NullFloat64{
Float64: f,
Valid: valid,
},
}
}
// FloatFrom creates a new Float that will always be valid.
func FloatFrom(f float64) Float {
return NewFloat(f, true)
}
// FloatFromPtr creates a new Float that be null if f is nil.
func FloatFromPtr(f *float64) Float {
if f == nil {
return NewFloat(0, false)
}
return NewFloat(*f, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Float.
// It also supports unmarshalling a sql.NullFloat64.
func (f *Float) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch x := v.(type) {
case float64:
f.Float64 = float64(x)
case map[string]interface{}:
err = json.Unmarshal(data, &f.NullFloat64)
case nil:
f.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Float", reflect.TypeOf(v).Name())
}
f.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Float if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (f *Float) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
f.Valid = false
return nil
}
var err error
f.Float64, err = strconv.ParseFloat(string(text), 64)
f.Valid = err == nil
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Float is null.
func (f Float) MarshalJSON() ([]byte, error) {
if !f.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Float is null.
func (f Float) MarshalText() ([]byte, error) {
if !f.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil
}
// SetValid changes this Float's value and also sets it to be non-null.
func (f *Float) SetValid(n float64) {
f.Float64 = n
f.Valid = true
}
// Ptr returns a pointer to this Float's value, or a nil pointer if this Float is null.
func (f Float) Ptr() *float64 {
if !f.Valid {
return nil
}
return &f.Float64
}
// IsZero returns true for invalid Floats, for future omitempty support (Go 1.4?)
// A non-null Float with a 0 value will not be considered zero.
func (f Float) IsZero() bool {
return !f.Valid
}
-118
View File
@@ -1,118 +0,0 @@
package null
import (
"database/sql"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// Int is an nullable int64.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
type Int struct {
sql.NullInt64
}
// NewInt creates a new Int
func NewInt(i int64, valid bool) Int {
return Int{
NullInt64: sql.NullInt64{
Int64: i,
Valid: valid,
},
}
}
// IntFrom creates a new Int that will always be valid.
func IntFrom(i int64) Int {
return NewInt(i, true)
}
// IntFromPtr creates a new Int that be null if i is nil.
func IntFromPtr(i *int64) Int {
if i == nil {
return NewInt(0, false)
}
return NewInt(*i, true)
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input.
// 0 will not be considered a null Int.
// It also supports unmarshalling a sql.NullInt64.
func (i *Int) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch v.(type) {
case float64:
// Unmarshal again, directly to int64, to avoid intermediate float64
err = json.Unmarshal(data, &i.Int64)
case map[string]interface{}:
err = json.Unmarshal(data, &i.NullInt64)
case nil:
i.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Int", reflect.TypeOf(v).Name())
}
i.Valid = err == nil
return err
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null Int if the input is a blank or not an integer.
// It will return an error if the input is not an integer, blank, or "null".
func (i *Int) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
i.Valid = false
return nil
}
var err error
i.Int64, err = strconv.ParseInt(string(text), 10, 64)
i.Valid = err == nil
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this Int is null.
func (i Int) MarshalJSON() ([]byte, error) {
if !i.Valid {
return []byte("null"), nil
}
return []byte(strconv.FormatInt(i.Int64, 10)), nil
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string if this Int is null.
func (i Int) MarshalText() ([]byte, error) {
if !i.Valid {
return []byte{}, nil
}
return []byte(strconv.FormatInt(i.Int64, 10)), nil
}
// SetValid changes this Int's value and also sets it to be non-null.
func (i *Int) SetValid(n int64) {
i.Int64 = n
i.Valid = true
}
// Ptr returns a pointer to this Int's value, or a nil pointer if this Int is null.
func (i Int) Ptr() *int64 {
if !i.Valid {
return nil
}
return &i.Int64
}
// IsZero returns true for invalid Ints, for future omitempty support (Go 1.4?)
// A non-null Int with a 0 value will not be considered zero.
func (i Int) IsZero() bool {
return !i.Valid
}
-110
View File
@@ -1,110 +0,0 @@
// Package null contains SQL types that consider zero input and null input as separate values,
// with convenient support for JSON and text marshaling.
// Types in this package will always encode to their null value if null.
// Use the zero subpackage if you want zero values and null to be treated the same.
package null
import (
"database/sql"
"encoding/json"
"fmt"
"reflect"
)
// String is a nullable string. It supports SQL and JSON serialization.
// It will marshal to null if null. Blank string input will be considered null.
type String struct {
sql.NullString
}
// StringFrom creates a new String that will never be blank.
func StringFrom(s string) String {
return NewString(s, true)
}
// StringFromPtr creates a new String that be null if s is nil.
func StringFromPtr(s *string) String {
if s == nil {
return NewString("", false)
}
return NewString(*s, true)
}
// NewString creates a new String
func NewString(s string, valid bool) String {
return String{
NullString: sql.NullString{
String: s,
Valid: valid,
},
}
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports string and null input. Blank string input does not produce a null String.
// It also supports unmarshalling a sql.NullString.
func (s *String) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch x := v.(type) {
case string:
s.String = x
case map[string]interface{}:
err = json.Unmarshal(data, &s.NullString)
case nil:
s.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.String", reflect.TypeOf(v).Name())
}
s.Valid = err == nil
return err
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this String is null.
func (s String) MarshalJSON() ([]byte, error) {
if !s.Valid {
return []byte("null"), nil
}
return json.Marshal(s.String)
}
// MarshalText implements encoding.TextMarshaler.
// It will encode a blank string when this String is null.
func (s String) MarshalText() ([]byte, error) {
if !s.Valid {
return []byte{}, nil
}
return []byte(s.String), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
// It will unmarshal to a null String if the input is a blank string.
func (s *String) UnmarshalText(text []byte) error {
s.String = string(text)
s.Valid = s.String != ""
return nil
}
// SetValid changes this String's value and also sets it to be non-null.
func (s *String) SetValid(v string) {
s.String = v
s.Valid = true
}
// Ptr returns a pointer to this String's value, or a nil pointer if this String is null.
func (s String) Ptr() *string {
if !s.Valid {
return nil
}
return &s.String
}
// IsZero returns true for null strings, for potential future omitempty support.
func (s String) IsZero() bool {
return !s.Valid
}
-135
View File
@@ -1,135 +0,0 @@
package null
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"time"
)
// Time is a nullable time.Time. It supports SQL and JSON serialization.
// It will marshal to null if null.
type Time struct {
Time time.Time
Valid bool
}
// Scan implements the Scanner interface.
func (t *Time) Scan(value interface{}) error {
var err error
switch x := value.(type) {
case time.Time:
t.Time = x
case nil:
t.Valid = false
return nil
default:
err = fmt.Errorf("null: cannot scan type %T into null.Time: %v", value, value)
}
t.Valid = err == nil
return err
}
// Value implements the driver Valuer interface.
func (t Time) Value() (driver.Value, error) {
if !t.Valid {
return nil, nil
}
return t.Time, nil
}
// NewTime creates a new Time.
func NewTime(t time.Time, valid bool) Time {
return Time{
Time: t,
Valid: valid,
}
}
// TimeFrom creates a new Time that will always be valid.
func TimeFrom(t time.Time) Time {
return NewTime(t, true)
}
// TimeFromPtr creates a new Time that will be null if t is nil.
func TimeFromPtr(t *time.Time) Time {
if t == nil {
return NewTime(time.Time{}, false)
}
return NewTime(*t, true)
}
// MarshalJSON implements json.Marshaler.
// It will encode null if this time is null.
func (t Time) MarshalJSON() ([]byte, error) {
if !t.Valid {
return []byte("null"), nil
}
return t.Time.MarshalJSON()
}
// UnmarshalJSON implements json.Unmarshaler.
// It supports string, object (e.g. pq.NullTime and friends)
// and null input.
func (t *Time) UnmarshalJSON(data []byte) error {
var err error
var v interface{}
if err = json.Unmarshal(data, &v); err != nil {
return err
}
switch x := v.(type) {
case string:
err = t.Time.UnmarshalJSON(data)
case map[string]interface{}:
ti, tiOK := x["Time"].(string)
valid, validOK := x["Valid"].(bool)
if !tiOK || !validOK {
return fmt.Errorf(`json: unmarshalling object into Go value of type null.Time requires key "Time" to be of type string and key "Valid" to be of type bool; found %T and %T, respectively`, x["Time"], x["Valid"])
}
err = t.Time.UnmarshalText([]byte(ti))
t.Valid = valid
return err
case nil:
t.Valid = false
return nil
default:
err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Time", reflect.TypeOf(v).Name())
}
t.Valid = err == nil
return err
}
func (t Time) MarshalText() ([]byte, error) {
if !t.Valid {
return []byte("null"), nil
}
return t.Time.MarshalText()
}
func (t *Time) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
t.Valid = false
return nil
}
if err := t.Time.UnmarshalText(text); err != nil {
return err
}
t.Valid = true
return nil
}
// SetValid changes this Time's value and sets it to be non-null.
func (t *Time) SetValid(v time.Time) {
t.Time = v
t.Valid = true
}
// Ptr returns a pointer to this Time's value, or a nil pointer if this Time is null.
func (t Time) Ptr() *time.Time {
if !t.Valid {
return nil
}
return &t.Time
}
+8 -6
View File
@@ -298,6 +298,14 @@
"version": "v1.5.8",
"versionExact": "v1.5.8"
},
{
"checksumSHA1": "HtKiIAPKsBg2s1c5ytRkdZ/lqO8=",
"path": "github.com/aws/aws-sdk-go/service/s3",
"revision": "898c81ba64b9a467379d35e3fabad133beae0ee4",
"revisionTime": "2016-11-18T23:08:35Z",
"version": "v1.5.8",
"versionExact": "v1.5.8"
},
{
"checksumSHA1": "ouwhxcAsIYQ6oJbMRdLW/Ys/iyg=",
"path": "github.com/aws/aws-sdk-go/service/sts",
@@ -409,12 +417,6 @@
"path": "gopkg.in/gomail.v2",
"revision": "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1",
"revisionTime": "2016-04-11T21:29:32Z"
},
{
"checksumSHA1": "PoHLopxwkiXxa3uVhezeq/qJ/Vo=",
"path": "gopkg.in/guregu/null.v3",
"revision": "41961cea0328defc5f95c1c473f89ebf0d1813f6",
"revisionTime": "2016-02-28T00:53:16Z"
}
],
"rootPath": "github.com/grafana/grafana"