mirror of
https://github.com/Luzifer/mondash.git
synced 2024-12-23 04:21:18 +00:00
Fixed docker build
This commit is contained in:
parent
6cc4dc4572
commit
1f9b6f07a0
315 changed files with 54784 additions and 60 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
||||||
gin-bin
|
gin-bin
|
||||||
mondash
|
mondash
|
||||||
Godeps/_workspace/
|
|
||||||
*.sh
|
*.sh
|
||||||
data
|
data
|
||||||
|
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
MAINTAINER Knut Ahlers <knut@luzifer.io>
|
||||||
|
|
||||||
|
ENV GOPATH /go:/go/src/github.com/Luzifer/mondash/Godeps/_workspace
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ADD . /go/src/github.com/Luzifer/mondash
|
||||||
|
WORKDIR /go/src/github.com/Luzifer/mondash
|
||||||
|
|
||||||
|
RUN apk --update add git go ca-certificates \
|
||||||
|
&& go install -ldflags "-X main.version=$(git describe --tags || git rev-parse --short HEAD || echo dev)" \
|
||||||
|
&& apk del --purge go git
|
||||||
|
|
||||||
|
ENTRYPOINT ["/go/bin/mondash"]
|
33
Godeps/Godeps.json
generated
33
Godeps/Godeps.json
generated
|
@ -1,7 +1,12 @@
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/Luzifer/mondash",
|
"ImportPath": "github.com/Luzifer/mondash",
|
||||||
"GoVersion": "go1.4.2",
|
"GoVersion": "go1.5.3",
|
||||||
"Deps": [
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "code.google.com/p/go.text/transform",
|
||||||
|
"Comment": "null-104",
|
||||||
|
"Rev": "7ab6d3749429e2c62c53eecadf3e4ec5e715c11b"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "code.google.com/p/go.text/unicode/norm",
|
"ImportPath": "code.google.com/p/go.text/unicode/norm",
|
||||||
"Comment": "null-104",
|
"Comment": "null-104",
|
||||||
|
@ -47,11 +52,6 @@
|
||||||
"Comment": "v0.6.4-2-g168a70b",
|
"Comment": "v0.6.4-2-g168a70b",
|
||||||
"Rev": "168a70b9c21a4f60166d7925b690356605907adb"
|
"Rev": "168a70b9c21a4f60166d7925b690356605907adb"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/codegangsta/inject",
|
|
||||||
"Comment": "v1.0-rc1-4-g4b81725",
|
|
||||||
"Rev": "4b8172520a03fa190f427bbd284db01b459bfce7"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/extemporalgenome/slug",
|
"ImportPath": "github.com/extemporalgenome/slug",
|
||||||
"Rev": "de70af6e24d0e6bdd6eaef276d2e9910e68a1397"
|
"Rev": "de70af6e24d0e6bdd6eaef276d2e9910e68a1397"
|
||||||
|
@ -62,22 +62,25 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/flosch/pongo2",
|
"ImportPath": "github.com/flosch/pongo2",
|
||||||
"Comment": "v1.0-rc1-128-g97072c3",
|
"Comment": "v1.0-rc1-174-gf5d79aa",
|
||||||
"Rev": "97072c3325f05afc51c66ae2561b8f3b1543aee4"
|
"Rev": "f5d79aa0a914c08eb7f51a96cd7b2dbbe46fca46"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/flosch/pongo2-addons",
|
"ImportPath": "github.com/flosch/pongo2-addons",
|
||||||
"Rev": "bb4da1901facc08b4edd9261050143b95e36d0a1"
|
"Rev": "bb4da1901facc08b4edd9261050143b95e36d0a1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/go-martini/martini",
|
"ImportPath": "github.com/gorilla/context",
|
||||||
"Comment": "v1.0-39-g42b0b68",
|
"Rev": "1c83b3eabd45b6d76072b66b746c20815fb2872d"
|
||||||
"Rev": "42b0b68fb383aac4f72331d0bd0948001b9be080"
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/mux",
|
||||||
|
"Rev": "49c024275504f0341e5a9971eb7ba7fa3dc7af40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/russross/blackfriday",
|
"ImportPath": "github.com/russross/blackfriday",
|
||||||
"Comment": "v1.2-21-g7c8f3c1",
|
"Comment": "v1.3",
|
||||||
"Rev": "7c8f3c1dcc7e0f87c62af34ee193fc3251f6d8a4"
|
"Rev": "8cec3a854e68dba10faabbe31c089abf4a3e57a6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/shurcooL/sanitized_anchor_name",
|
"ImportPath": "github.com/shurcooL/sanitized_anchor_name",
|
||||||
|
@ -90,10 +93,6 @@
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/vaughan0/go-ini",
|
"ImportPath": "github.com/vaughan0/go-ini",
|
||||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/transform",
|
|
||||||
"Rev": "8ec34a0272a20b3a6b12ecab475dd99e16610e4c"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/pkg
|
||||||
|
/bin
|
616
Godeps/_workspace/src/code.google.com/p/go.text/transform/transform.go
generated
vendored
Normal file
616
Godeps/_workspace/src/code.google.com/p/go.text/transform/transform.go
generated
vendored
Normal file
|
@ -0,0 +1,616 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package transform provides reader and writer wrappers that transform the
|
||||||
|
// bytes passing through as well as various transformations. Example
|
||||||
|
// transformations provided by other packages include normalization and
|
||||||
|
// conversion between character sets.
|
||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrShortDst means that the destination buffer was too short to
|
||||||
|
// receive all of the transformed bytes.
|
||||||
|
ErrShortDst = errors.New("transform: short destination buffer")
|
||||||
|
|
||||||
|
// ErrShortSrc means that the source buffer has insufficient data to
|
||||||
|
// complete the transformation.
|
||||||
|
ErrShortSrc = errors.New("transform: short source buffer")
|
||||||
|
|
||||||
|
// errInconsistentByteCount means that Transform returned success (nil
|
||||||
|
// error) but also returned nSrc inconsistent with the src argument.
|
||||||
|
errInconsistentByteCount = errors.New("transform: inconsistent byte count returned")
|
||||||
|
|
||||||
|
// errShortInternal means that an internal buffer is not large enough
|
||||||
|
// to make progress and the Transform operation must be aborted.
|
||||||
|
errShortInternal = errors.New("transform: short internal buffer")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transformer transforms bytes.
|
||||||
|
type Transformer interface {
|
||||||
|
// Transform writes to dst the transformed bytes read from src, and
|
||||||
|
// returns the number of dst bytes written and src bytes read. The
|
||||||
|
// atEOF argument tells whether src represents the last bytes of the
|
||||||
|
// input.
|
||||||
|
//
|
||||||
|
// Callers should always process the nDst bytes produced and account
|
||||||
|
// for the nSrc bytes consumed before considering the error err.
|
||||||
|
//
|
||||||
|
// A nil error means that all of the transformed bytes (whether freshly
|
||||||
|
// transformed from src or left over from previous Transform calls)
|
||||||
|
// were written to dst. A nil error can be returned regardless of
|
||||||
|
// whether atEOF is true. If err is nil then nSrc must equal len(src);
|
||||||
|
// the converse is not necessarily true.
|
||||||
|
//
|
||||||
|
// ErrShortDst means that dst was too short to receive all of the
|
||||||
|
// transformed bytes. ErrShortSrc means that src had insufficient data
|
||||||
|
// to complete the transformation. If both conditions apply, then
|
||||||
|
// either error may be returned. Other than the error conditions listed
|
||||||
|
// here, implementations are free to report other errors that arise.
|
||||||
|
Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
|
||||||
|
|
||||||
|
// Reset resets the state and allows a Transformer to be reused.
|
||||||
|
Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NopResetter can be embedded by implementations of Transformer to add a nop
|
||||||
|
// Reset method.
|
||||||
|
type NopResetter struct{}
|
||||||
|
|
||||||
|
// Reset implements the Reset method of the Transformer interface.
|
||||||
|
func (NopResetter) Reset() {}
|
||||||
|
|
||||||
|
// Reader wraps another io.Reader by transforming the bytes read.
|
||||||
|
type Reader struct {
|
||||||
|
r io.Reader
|
||||||
|
t Transformer
|
||||||
|
err error
|
||||||
|
|
||||||
|
// dst[dst0:dst1] contains bytes that have been transformed by t but
|
||||||
|
// not yet copied out via Read.
|
||||||
|
dst []byte
|
||||||
|
dst0, dst1 int
|
||||||
|
|
||||||
|
// src[src0:src1] contains bytes that have been read from r but not
|
||||||
|
// yet transformed through t.
|
||||||
|
src []byte
|
||||||
|
src0, src1 int
|
||||||
|
|
||||||
|
// transformComplete is whether the transformation is complete,
|
||||||
|
// regardless of whether or not it was successful.
|
||||||
|
transformComplete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultBufSize = 4096
|
||||||
|
|
||||||
|
// NewReader returns a new Reader that wraps r by transforming the bytes read
|
||||||
|
// via t. It calls Reset on t.
|
||||||
|
func NewReader(r io.Reader, t Transformer) *Reader {
|
||||||
|
t.Reset()
|
||||||
|
return &Reader{
|
||||||
|
r: r,
|
||||||
|
t: t,
|
||||||
|
dst: make([]byte, defaultBufSize),
|
||||||
|
src: make([]byte, defaultBufSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements the io.Reader interface.
|
||||||
|
func (r *Reader) Read(p []byte) (int, error) {
|
||||||
|
n, err := 0, error(nil)
|
||||||
|
for {
|
||||||
|
// Copy out any transformed bytes and return the final error if we are done.
|
||||||
|
if r.dst0 != r.dst1 {
|
||||||
|
n = copy(p, r.dst[r.dst0:r.dst1])
|
||||||
|
r.dst0 += n
|
||||||
|
if r.dst0 == r.dst1 && r.transformComplete {
|
||||||
|
return n, r.err
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
} else if r.transformComplete {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to transform some source bytes, or to flush the transformer if we
|
||||||
|
// are out of source bytes. We do this even if r.r.Read returned an error.
|
||||||
|
// As the io.Reader documentation says, "process the n > 0 bytes returned
|
||||||
|
// before considering the error".
|
||||||
|
if r.src0 != r.src1 || r.err != nil {
|
||||||
|
r.dst0 = 0
|
||||||
|
r.dst1, n, err = r.t.Transform(r.dst, r.src[r.src0:r.src1], r.err == io.EOF)
|
||||||
|
r.src0 += n
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
if r.src0 != r.src1 {
|
||||||
|
r.err = errInconsistentByteCount
|
||||||
|
}
|
||||||
|
// The Transform call was successful; we are complete if we
|
||||||
|
// cannot read more bytes into src.
|
||||||
|
r.transformComplete = r.err != nil
|
||||||
|
continue
|
||||||
|
case err == ErrShortDst && (r.dst1 != 0 || n != 0):
|
||||||
|
// Make room in dst by copying out, and try again.
|
||||||
|
continue
|
||||||
|
case err == ErrShortSrc && r.src1-r.src0 != len(r.src) && r.err == nil:
|
||||||
|
// Read more bytes into src via the code below, and try again.
|
||||||
|
default:
|
||||||
|
r.transformComplete = true
|
||||||
|
// The reader error (r.err) takes precedence over the
|
||||||
|
// transformer error (err) unless r.err is nil or io.EOF.
|
||||||
|
if r.err == nil || r.err == io.EOF {
|
||||||
|
r.err = err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move any untransformed source bytes to the start of the buffer
|
||||||
|
// and read more bytes.
|
||||||
|
if r.src0 != 0 {
|
||||||
|
r.src0, r.src1 = 0, copy(r.src, r.src[r.src0:r.src1])
|
||||||
|
}
|
||||||
|
n, r.err = r.r.Read(r.src[r.src1:])
|
||||||
|
r.src1 += n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement ReadByte (and ReadRune??).
|
||||||
|
|
||||||
|
// Writer wraps another io.Writer by transforming the bytes read.
|
||||||
|
// The user needs to call Close to flush unwritten bytes that may
|
||||||
|
// be buffered.
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
t Transformer
|
||||||
|
dst []byte
|
||||||
|
|
||||||
|
// src[:n] contains bytes that have not yet passed through t.
|
||||||
|
src []byte
|
||||||
|
n int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer that wraps w by transforming the bytes written
|
||||||
|
// via t. It calls Reset on t.
|
||||||
|
func NewWriter(w io.Writer, t Transformer) *Writer {
|
||||||
|
t.Reset()
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
t: t,
|
||||||
|
dst: make([]byte, defaultBufSize),
|
||||||
|
src: make([]byte, defaultBufSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the io.Writer interface. If there are not enough
|
||||||
|
// bytes available to complete a Transform, the bytes will be buffered
|
||||||
|
// for the next write. Call Close to convert the remaining bytes.
|
||||||
|
func (w *Writer) Write(data []byte) (n int, err error) {
|
||||||
|
src := data
|
||||||
|
if w.n > 0 {
|
||||||
|
// Append bytes from data to the last remainder.
|
||||||
|
// TODO: limit the amount copied on first try.
|
||||||
|
n = copy(w.src[w.n:], data)
|
||||||
|
w.n += n
|
||||||
|
src = w.src[:w.n]
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
nDst, nSrc, err := w.t.Transform(w.dst, src, false)
|
||||||
|
if _, werr := w.w.Write(w.dst[:nDst]); werr != nil {
|
||||||
|
return n, werr
|
||||||
|
}
|
||||||
|
src = src[nSrc:]
|
||||||
|
if w.n > 0 && len(src) <= n {
|
||||||
|
// Enough bytes from w.src have been consumed. We make src point
|
||||||
|
// to data instead to reduce the copying.
|
||||||
|
w.n = 0
|
||||||
|
n -= len(src)
|
||||||
|
src = data[n:]
|
||||||
|
if n < len(data) && (err == nil || err == ErrShortSrc) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n += nSrc
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case err == ErrShortDst && (nDst > 0 || nSrc > 0):
|
||||||
|
case err == ErrShortSrc && len(src) < len(w.src):
|
||||||
|
m := copy(w.src, src)
|
||||||
|
// If w.n > 0, bytes from data were already copied to w.src and n
|
||||||
|
// was already set to the number of bytes consumed.
|
||||||
|
if w.n == 0 {
|
||||||
|
n += m
|
||||||
|
}
|
||||||
|
w.n = m
|
||||||
|
return n, nil
|
||||||
|
case err == nil && w.n > 0:
|
||||||
|
return n, errInconsistentByteCount
|
||||||
|
default:
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the io.Closer interface.
|
||||||
|
func (w *Writer) Close() error {
|
||||||
|
for src := w.src[:w.n]; len(src) > 0; {
|
||||||
|
nDst, nSrc, err := w.t.Transform(w.dst, src, true)
|
||||||
|
if nDst == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, werr := w.w.Write(w.dst[:nDst]); werr != nil {
|
||||||
|
return werr
|
||||||
|
}
|
||||||
|
if err != ErrShortDst {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
src = src[nSrc:]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type nop struct{ NopResetter }
|
||||||
|
|
||||||
|
func (nop) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
|
n := copy(dst, src)
|
||||||
|
if n < len(src) {
|
||||||
|
err = ErrShortDst
|
||||||
|
}
|
||||||
|
return n, n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type discard struct{ NopResetter }
|
||||||
|
|
||||||
|
func (discard) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
|
return 0, len(src), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Discard is a Transformer for which all Transform calls succeed
|
||||||
|
// by consuming all bytes and writing nothing.
|
||||||
|
Discard Transformer = discard{}
|
||||||
|
|
||||||
|
// Nop is a Transformer that copies src to dst.
|
||||||
|
Nop Transformer = nop{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// chain is a sequence of links. A chain with N Transformers has N+1 links and
|
||||||
|
// N+1 buffers. Of those N+1 buffers, the first and last are the src and dst
|
||||||
|
// buffers given to chain.Transform and the middle N-1 buffers are intermediate
|
||||||
|
// buffers owned by the chain. The i'th link transforms bytes from the i'th
|
||||||
|
// buffer chain.link[i].b at read offset chain.link[i].p to the i+1'th buffer
|
||||||
|
// chain.link[i+1].b at write offset chain.link[i+1].n, for i in [0, N).
|
||||||
|
type chain struct {
|
||||||
|
link []link
|
||||||
|
err error
|
||||||
|
// errStart is the index at which the error occurred plus 1. Processing
|
||||||
|
// errStart at this level at the next call to Transform. As long as
|
||||||
|
// errStart > 0, chain will not consume any more source bytes.
|
||||||
|
errStart int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *chain) fatalError(errIndex int, err error) {
|
||||||
|
if i := errIndex + 1; i > c.errStart {
|
||||||
|
c.errStart = i
|
||||||
|
c.err = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type link struct {
|
||||||
|
t Transformer
|
||||||
|
// b[p:n] holds the bytes to be transformed by t.
|
||||||
|
b []byte
|
||||||
|
p int
|
||||||
|
n int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *link) src() []byte {
|
||||||
|
return l.b[l.p:l.n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *link) dst() []byte {
|
||||||
|
return l.b[l.n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain returns a Transformer that applies t in sequence.
|
||||||
|
func Chain(t ...Transformer) Transformer {
|
||||||
|
if len(t) == 0 {
|
||||||
|
return nop{}
|
||||||
|
}
|
||||||
|
c := &chain{link: make([]link, len(t)+1)}
|
||||||
|
for i, tt := range t {
|
||||||
|
c.link[i].t = tt
|
||||||
|
}
|
||||||
|
// Allocate intermediate buffers.
|
||||||
|
b := make([][defaultBufSize]byte, len(t)-1)
|
||||||
|
for i := range b {
|
||||||
|
c.link[i+1].b = b[i][:]
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the state of Chain. It calls Reset on all the Transformers.
|
||||||
|
func (c *chain) Reset() {
|
||||||
|
for i, l := range c.link {
|
||||||
|
if l.t != nil {
|
||||||
|
l.t.Reset()
|
||||||
|
}
|
||||||
|
c.link[i].p, c.link[i].n = 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform applies the transformers of c in sequence.
|
||||||
|
func (c *chain) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
|
// Set up src and dst in the chain.
|
||||||
|
srcL := &c.link[0]
|
||||||
|
dstL := &c.link[len(c.link)-1]
|
||||||
|
srcL.b, srcL.p, srcL.n = src, 0, len(src)
|
||||||
|
dstL.b, dstL.n = dst, 0
|
||||||
|
var lastFull, needProgress bool // for detecting progress
|
||||||
|
|
||||||
|
// i is the index of the next Transformer to apply, for i in [low, high].
|
||||||
|
// low is the lowest index for which c.link[low] may still produce bytes.
|
||||||
|
// high is the highest index for which c.link[high] has a Transformer.
|
||||||
|
// The error returned by Transform determines whether to increase or
|
||||||
|
// decrease i. We try to completely fill a buffer before converting it.
|
||||||
|
for low, i, high := c.errStart, c.errStart, len(c.link)-2; low <= i && i <= high; {
|
||||||
|
in, out := &c.link[i], &c.link[i+1]
|
||||||
|
nDst, nSrc, err0 := in.t.Transform(out.dst(), in.src(), atEOF && low == i)
|
||||||
|
out.n += nDst
|
||||||
|
in.p += nSrc
|
||||||
|
if i > 0 && in.p == in.n {
|
||||||
|
in.p, in.n = 0, 0
|
||||||
|
}
|
||||||
|
needProgress, lastFull = lastFull, false
|
||||||
|
switch err0 {
|
||||||
|
case ErrShortDst:
|
||||||
|
// Process the destination buffer next. Return if we are already
|
||||||
|
// at the high index.
|
||||||
|
if i == high {
|
||||||
|
return dstL.n, srcL.p, ErrShortDst
|
||||||
|
}
|
||||||
|
if out.n != 0 {
|
||||||
|
i++
|
||||||
|
// If the Transformer at the next index is not able to process any
|
||||||
|
// source bytes there is nothing that can be done to make progress
|
||||||
|
// and the bytes will remain unprocessed. lastFull is used to
|
||||||
|
// detect this and break out of the loop with a fatal error.
|
||||||
|
lastFull = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The destination buffer was too small, but is completely empty.
|
||||||
|
// Return a fatal error as this transformation can never complete.
|
||||||
|
c.fatalError(i, errShortInternal)
|
||||||
|
case ErrShortSrc:
|
||||||
|
if i == 0 {
|
||||||
|
// Save ErrShortSrc in err. All other errors take precedence.
|
||||||
|
err = ErrShortSrc
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Source bytes were depleted before filling up the destination buffer.
|
||||||
|
// Verify we made some progress, move the remaining bytes to the errStart
|
||||||
|
// and try to get more source bytes.
|
||||||
|
if needProgress && nSrc == 0 || in.n-in.p == len(in.b) {
|
||||||
|
// There were not enough source bytes to proceed while the source
|
||||||
|
// buffer cannot hold any more bytes. Return a fatal error as this
|
||||||
|
// transformation can never complete.
|
||||||
|
c.fatalError(i, errShortInternal)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// in.b is an internal buffer and we can make progress.
|
||||||
|
in.p, in.n = 0, copy(in.b, in.src())
|
||||||
|
fallthrough
|
||||||
|
case nil:
|
||||||
|
// if i == low, we have depleted the bytes at index i or any lower levels.
|
||||||
|
// In that case we increase low and i. In all other cases we decrease i to
|
||||||
|
// fetch more bytes before proceeding to the next index.
|
||||||
|
if i > low {
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c.fatalError(i, err0)
|
||||||
|
}
|
||||||
|
// Exhausted level low or fatal error: increase low and continue
|
||||||
|
// to process the bytes accepted so far.
|
||||||
|
i++
|
||||||
|
low = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// If c.errStart > 0, this means we found a fatal error. We will clear
|
||||||
|
// all upstream buffers. At this point, no more progress can be made
|
||||||
|
// downstream, as Transform would have bailed while handling ErrShortDst.
|
||||||
|
if c.errStart > 0 {
|
||||||
|
for i := 1; i < c.errStart; i++ {
|
||||||
|
c.link[i].p, c.link[i].n = 0, 0
|
||||||
|
}
|
||||||
|
err, c.errStart, c.err = c.err, 0, nil
|
||||||
|
}
|
||||||
|
return dstL.n, srcL.p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFunc returns a Transformer that removes from the input all runes r for
|
||||||
|
// which f(r) is true. Illegal bytes in the input are replaced by RuneError.
|
||||||
|
func RemoveFunc(f func(r rune) bool) Transformer {
|
||||||
|
return removeF(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeF func(r rune) bool
|
||||||
|
|
||||||
|
func (removeF) Reset() {}
|
||||||
|
|
||||||
|
// Transform implements the Transformer interface.
|
||||||
|
func (t removeF) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
|
for r, sz := rune(0), 0; len(src) > 0; src = src[sz:] {
|
||||||
|
|
||||||
|
if r = rune(src[0]); r < utf8.RuneSelf {
|
||||||
|
sz = 1
|
||||||
|
} else {
|
||||||
|
r, sz = utf8.DecodeRune(src)
|
||||||
|
|
||||||
|
if sz == 1 {
|
||||||
|
// Invalid rune.
|
||||||
|
if !atEOF && !utf8.FullRune(src) {
|
||||||
|
err = ErrShortSrc
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// We replace illegal bytes with RuneError. Not doing so might
|
||||||
|
// otherwise turn a sequence of invalid UTF-8 into valid UTF-8.
|
||||||
|
// The resulting byte sequence may subsequently contain runes
|
||||||
|
// for which t(r) is true that were passed unnoticed.
|
||||||
|
if !t(r) {
|
||||||
|
if nDst+3 > len(dst) {
|
||||||
|
err = ErrShortDst
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nDst += copy(dst[nDst:], "\uFFFD")
|
||||||
|
}
|
||||||
|
nSrc++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !t(r) {
|
||||||
|
if nDst+sz > len(dst) {
|
||||||
|
err = ErrShortDst
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nDst += copy(dst[nDst:], src[:sz])
|
||||||
|
}
|
||||||
|
nSrc += sz
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// grow returns a new []byte that is longer than b, and copies the first n bytes
|
||||||
|
// of b to the start of the new slice.
|
||||||
|
func grow(b []byte, n int) []byte {
|
||||||
|
m := len(b)
|
||||||
|
if m <= 256 {
|
||||||
|
m *= 2
|
||||||
|
} else {
|
||||||
|
m += m >> 1
|
||||||
|
}
|
||||||
|
buf := make([]byte, m)
|
||||||
|
copy(buf, b[:n])
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialBufSize = 128
|
||||||
|
|
||||||
|
// String returns a string with the result of converting s[:n] using t, where
|
||||||
|
// n <= len(s). If err == nil, n will be len(s). It calls Reset on t.
|
||||||
|
func String(t Transformer, s string) (result string, n int, err error) {
|
||||||
|
if s == "" {
|
||||||
|
return "", 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Reset()
|
||||||
|
|
||||||
|
// Allocate only once. Note that both dst and src escape when passed to
|
||||||
|
// Transform.
|
||||||
|
buf := [2 * initialBufSize]byte{}
|
||||||
|
dst := buf[:initialBufSize:initialBufSize]
|
||||||
|
src := buf[initialBufSize : 2*initialBufSize]
|
||||||
|
|
||||||
|
// Avoid allocation if the transformed string is identical to the original.
|
||||||
|
// After this loop, pDst will point to the furthest point in s for which it
|
||||||
|
// could be detected that t gives equal results, src[:nSrc] will
|
||||||
|
// indicated the last processed chunk of s for which the output is not equal
|
||||||
|
// and dst[:nDst] will be the transform of this chunk.
|
||||||
|
var nDst, nSrc int
|
||||||
|
pDst := 0 // Used as index in both src and dst in this loop.
|
||||||
|
for {
|
||||||
|
n := copy(src, s[pDst:])
|
||||||
|
nDst, nSrc, err = t.Transform(dst, src[:n], pDst+n == len(s))
|
||||||
|
|
||||||
|
// Note 1: we will not enter the loop with pDst == len(s) and we will
|
||||||
|
// not end the loop with it either. So if nSrc is 0, this means there is
|
||||||
|
// some kind of error from which we cannot recover given the current
|
||||||
|
// buffer sizes. We will give up in this case.
|
||||||
|
// Note 2: it is not entirely correct to simply do a bytes.Equal as
|
||||||
|
// a Transformer may buffer internally. It will work in most cases,
|
||||||
|
// though, and no harm is done if it doesn't work.
|
||||||
|
// TODO: let transformers implement an optional Spanner interface, akin
|
||||||
|
// to norm's QuickSpan. This would even allow us to avoid any allocation.
|
||||||
|
if nSrc == 0 || !bytes.Equal(dst[:nDst], src[:nSrc]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if pDst += nDst; pDst == len(s) {
|
||||||
|
return s, pDst, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the bytes seen so far to dst.
|
||||||
|
pSrc := pDst + nSrc
|
||||||
|
if pDst+nDst <= initialBufSize {
|
||||||
|
copy(dst[pDst:], dst[:nDst])
|
||||||
|
} else {
|
||||||
|
b := make([]byte, len(s)+nDst-nSrc)
|
||||||
|
copy(b[pDst:], dst[:nDst])
|
||||||
|
dst = b
|
||||||
|
}
|
||||||
|
copy(dst, s[:pDst])
|
||||||
|
pDst += nDst
|
||||||
|
|
||||||
|
if err != nil && err != ErrShortDst && err != ErrShortSrc {
|
||||||
|
return string(dst[:pDst]), pSrc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the string with the remainder.
|
||||||
|
for {
|
||||||
|
n := copy(src, s[pSrc:])
|
||||||
|
nDst, nSrc, err = t.Transform(dst[pDst:], src[:n], pSrc+n == len(s))
|
||||||
|
pDst += nDst
|
||||||
|
pSrc += nSrc
|
||||||
|
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
if pSrc == len(s) {
|
||||||
|
return string(dst[:pDst]), pSrc, nil
|
||||||
|
}
|
||||||
|
case ErrShortDst:
|
||||||
|
// Do not grow as long as we can make progress. This may avoid
|
||||||
|
// excessive allocations.
|
||||||
|
if nDst == 0 {
|
||||||
|
dst = grow(dst, pDst)
|
||||||
|
}
|
||||||
|
case ErrShortSrc:
|
||||||
|
if nSrc == 0 {
|
||||||
|
src = grow(src, 0)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return string(dst[:pDst]), pSrc, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns a new byte slice with the result of converting b[:n] using t,
|
||||||
|
// where n <= len(b). If err == nil, n will be len(b). It calls Reset on t.
|
||||||
|
func Bytes(t Transformer, b []byte) (result []byte, n int, err error) {
|
||||||
|
t.Reset()
|
||||||
|
dst := make([]byte, len(b))
|
||||||
|
pDst, pSrc := 0, 0
|
||||||
|
for {
|
||||||
|
nDst, nSrc, err := t.Transform(dst[pDst:], b[pSrc:], true)
|
||||||
|
pDst += nDst
|
||||||
|
pSrc += nSrc
|
||||||
|
if err != ErrShortDst {
|
||||||
|
return dst[:pDst], pSrc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow the destination buffer, but do not grow as long as we can make
|
||||||
|
// progress. This may avoid excessive allocations.
|
||||||
|
if nDst == 0 {
|
||||||
|
dst = grow(dst, pDst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/Makefile
generated
vendored
Normal file
23
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style
|
||||||
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
maketables: maketables.go triegen.go
|
||||||
|
go build $^
|
||||||
|
|
||||||
|
normregtest: normregtest.go
|
||||||
|
go build $^
|
||||||
|
|
||||||
|
tables: maketables
|
||||||
|
./maketables > tables.go
|
||||||
|
gofmt -w tables.go
|
||||||
|
|
||||||
|
# Downloads from www.unicode.org, so not part
|
||||||
|
# of standard test scripts.
|
||||||
|
test: testtables regtest
|
||||||
|
|
||||||
|
testtables: maketables
|
||||||
|
./maketables -test > data_test.go && go test -tags=test
|
||||||
|
|
||||||
|
regtest: normregtest
|
||||||
|
./normregtest
|
514
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/composition.go
generated
vendored
Normal file
514
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/composition.go
generated
vendored
Normal file
|
@ -0,0 +1,514 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package norm
|
||||||
|
|
||||||
|
import "unicode/utf8"
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxNonStarters = 30
|
||||||
|
// The maximum number of characters needed for a buffer is
|
||||||
|
// maxNonStarters + 1 for the starter + 1 for the GCJ
|
||||||
|
maxBufferSize = maxNonStarters + 2
|
||||||
|
maxNFCExpansion = 3 // NFC(0x1D160)
|
||||||
|
maxNFKCExpansion = 18 // NFKC(0xFDFA)
|
||||||
|
|
||||||
|
maxByteBufferSize = utf8.UTFMax * maxBufferSize // 128
|
||||||
|
)
|
||||||
|
|
||||||
|
// ssState is used for reporting the segment state after inserting a rune.
|
||||||
|
// It is returned by streamSafe.next.
|
||||||
|
type ssState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Indicates a rune was successfully added to the segment.
|
||||||
|
ssSuccess ssState = iota
|
||||||
|
// Indicates a rune starts a new segment and should not be added.
|
||||||
|
ssStarter
|
||||||
|
// Indicates a rune caused a segment overflow and a CGJ should be inserted.
|
||||||
|
ssOverflow
|
||||||
|
)
|
||||||
|
|
||||||
|
// streamSafe implements the policy of when a CGJ should be inserted.
|
||||||
|
type streamSafe uint8
|
||||||
|
|
||||||
|
// mkStreamSafe is a shorthand for declaring a streamSafe var and calling
|
||||||
|
// first on it.
|
||||||
|
func mkStreamSafe(p Properties) streamSafe {
|
||||||
|
return streamSafe(p.nTrailingNonStarters())
|
||||||
|
}
|
||||||
|
|
||||||
|
// first inserts the first rune of a segment.
|
||||||
|
func (ss *streamSafe) first(p Properties) {
|
||||||
|
if *ss != 0 {
|
||||||
|
panic("!= 0")
|
||||||
|
}
|
||||||
|
*ss = streamSafe(p.nTrailingNonStarters())
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert returns a ssState value to indicate whether a rune represented by p
|
||||||
|
// can be inserted.
|
||||||
|
func (ss *streamSafe) next(p Properties) ssState {
|
||||||
|
if *ss > maxNonStarters {
|
||||||
|
panic("streamSafe was not reset")
|
||||||
|
}
|
||||||
|
n := p.nLeadingNonStarters()
|
||||||
|
if *ss += streamSafe(n); *ss > maxNonStarters {
|
||||||
|
*ss = 0
|
||||||
|
return ssOverflow
|
||||||
|
}
|
||||||
|
// The Stream-Safe Text Processing prescribes that the counting can stop
|
||||||
|
// as soon as a starter is encountered. However, there are some starters,
|
||||||
|
// like Jamo V and T, that can combine with other runes, leaving their
|
||||||
|
// successive non-starters appended to the previous, possibly causing an
|
||||||
|
// overflow. We will therefore consider any rune with a non-zero nLead to
|
||||||
|
// be a non-starter. Note that it always hold that if nLead > 0 then
|
||||||
|
// nLead == nTrail.
|
||||||
|
if n == 0 {
|
||||||
|
*ss = 0
|
||||||
|
return ssStarter
|
||||||
|
}
|
||||||
|
return ssSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
// backwards is used for checking for overflow and segment starts
|
||||||
|
// when traversing a string backwards. Users do not need to call first
|
||||||
|
// for the first rune. The state of the streamSafe retains the count of
|
||||||
|
// the non-starters loaded.
|
||||||
|
func (ss *streamSafe) backwards(p Properties) ssState {
|
||||||
|
if *ss > maxNonStarters {
|
||||||
|
panic("streamSafe was not reset")
|
||||||
|
}
|
||||||
|
c := *ss + streamSafe(p.nTrailingNonStarters())
|
||||||
|
if c > maxNonStarters {
|
||||||
|
return ssOverflow
|
||||||
|
}
|
||||||
|
*ss = c
|
||||||
|
if p.nLeadingNonStarters() == 0 {
|
||||||
|
return ssStarter
|
||||||
|
}
|
||||||
|
return ssSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss streamSafe) isMax() bool {
|
||||||
|
return ss == maxNonStarters
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphemeJoiner is inserted after maxNonStarters non-starter runes.
|
||||||
|
const GraphemeJoiner = "\u034F"
|
||||||
|
|
||||||
|
// reorderBuffer is used to normalize a single segment. Characters inserted with
|
||||||
|
// insert are decomposed and reordered based on CCC. The compose method can
|
||||||
|
// be used to recombine characters. Note that the byte buffer does not hold
|
||||||
|
// the UTF-8 characters in order. Only the rune array is maintained in sorted
|
||||||
|
// order. flush writes the resulting segment to a byte array.
|
||||||
|
type reorderBuffer struct {
|
||||||
|
rune [maxBufferSize]Properties // Per character info.
|
||||||
|
byte [maxByteBufferSize]byte // UTF-8 buffer. Referenced by runeInfo.pos.
|
||||||
|
nbyte uint8 // Number or bytes.
|
||||||
|
ss streamSafe // For limiting length of non-starter sequence.
|
||||||
|
nrune int // Number of runeInfos.
|
||||||
|
f formInfo
|
||||||
|
|
||||||
|
src input
|
||||||
|
nsrc int
|
||||||
|
tmpBytes input
|
||||||
|
|
||||||
|
out []byte
|
||||||
|
flushF func(*reorderBuffer) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *reorderBuffer) init(f Form, src []byte) {
|
||||||
|
rb.f = *formTable[f]
|
||||||
|
rb.src.setBytes(src)
|
||||||
|
rb.nsrc = len(src)
|
||||||
|
rb.ss = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *reorderBuffer) initString(f Form, src string) {
|
||||||
|
rb.f = *formTable[f]
|
||||||
|
rb.src.setString(src)
|
||||||
|
rb.nsrc = len(src)
|
||||||
|
rb.ss = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *reorderBuffer) setFlusher(out []byte, f func(*reorderBuffer) bool) {
|
||||||
|
rb.out = out
|
||||||
|
rb.flushF = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset discards all characters from the buffer.
|
||||||
|
func (rb *reorderBuffer) reset() {
|
||||||
|
rb.nrune = 0
|
||||||
|
rb.nbyte = 0
|
||||||
|
rb.ss = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *reorderBuffer) doFlush() bool {
|
||||||
|
if rb.f.composing {
|
||||||
|
rb.compose()
|
||||||
|
}
|
||||||
|
res := rb.flushF(rb)
|
||||||
|
rb.reset()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendFlush appends the normalized segment to rb.out.
|
||||||
|
func appendFlush(rb *reorderBuffer) bool {
|
||||||
|
for i := 0; i < rb.nrune; i++ {
|
||||||
|
start := rb.rune[i].pos
|
||||||
|
end := start + rb.rune[i].size
|
||||||
|
rb.out = append(rb.out, rb.byte[start:end]...)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush appends the normalized segment to out and resets rb.
|
||||||
|
func (rb *reorderBuffer) flush(out []byte) []byte {
|
||||||
|
for i := 0; i < rb.nrune; i++ {
|
||||||
|
start := rb.rune[i].pos
|
||||||
|
end := start + rb.rune[i].size
|
||||||
|
out = append(out, rb.byte[start:end]...)
|
||||||
|
}
|
||||||
|
rb.reset()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushCopy copies the normalized segment to buf and resets rb.
|
||||||
|
// It returns the number of bytes written to buf.
|
||||||
|
func (rb *reorderBuffer) flushCopy(buf []byte) int {
|
||||||
|
p := 0
|
||||||
|
for i := 0; i < rb.nrune; i++ {
|
||||||
|
runep := rb.rune[i]
|
||||||
|
p += copy(buf[p:], rb.byte[runep.pos:runep.pos+runep.size])
|
||||||
|
}
|
||||||
|
rb.reset()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertOrdered inserts a rune in the buffer, ordered by Canonical Combining Class.
|
||||||
|
// It returns false if the buffer is not large enough to hold the rune.
|
||||||
|
// It is used internally by insert and insertString only.
|
||||||
|
func (rb *reorderBuffer) insertOrdered(info Properties) {
|
||||||
|
n := rb.nrune
|
||||||
|
b := rb.rune[:]
|
||||||
|
cc := info.ccc
|
||||||
|
if cc > 0 {
|
||||||
|
// Find insertion position + move elements to make room.
|
||||||
|
for ; n > 0; n-- {
|
||||||
|
if b[n-1].ccc <= cc {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b[n] = b[n-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rb.nrune += 1
|
||||||
|
pos := uint8(rb.nbyte)
|
||||||
|
rb.nbyte += utf8.UTFMax
|
||||||
|
info.pos = pos
|
||||||
|
b[n] = info
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertErr is an error code returned by insert. Using this type instead
|
||||||
|
// of error improves performance up to 20% for many of the benchmarks.
|
||||||
|
type insertErr int
|
||||||
|
|
||||||
|
const (
|
||||||
|
iSuccess insertErr = -iota
|
||||||
|
iShortDst
|
||||||
|
iShortSrc
|
||||||
|
)
|
||||||
|
|
||||||
|
// insertFlush inserts the given rune in the buffer ordered by CCC.
|
||||||
|
// If a decomposition with multiple segments are encountered, they leading
|
||||||
|
// ones are flushed.
|
||||||
|
// It returns a non-zero error code if the rune was not inserted.
|
||||||
|
func (rb *reorderBuffer) insertFlush(src input, i int, info Properties) insertErr {
|
||||||
|
if rune := src.hangul(i); rune != 0 {
|
||||||
|
rb.decomposeHangul(rune)
|
||||||
|
return iSuccess
|
||||||
|
}
|
||||||
|
if info.hasDecomposition() {
|
||||||
|
return rb.insertDecomposed(info.Decomposition())
|
||||||
|
}
|
||||||
|
rb.insertSingle(src, i, info)
|
||||||
|
return iSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertUnsafe inserts the given rune in the buffer ordered by CCC.
|
||||||
|
// It is assumed there is sufficient space to hold the runes. It is the
|
||||||
|
// responsibility of the caller to ensure this. This can be done by checking
|
||||||
|
// the state returned by the streamSafe type.
|
||||||
|
func (rb *reorderBuffer) insertUnsafe(src input, i int, info Properties) {
|
||||||
|
if rune := src.hangul(i); rune != 0 {
|
||||||
|
rb.decomposeHangul(rune)
|
||||||
|
}
|
||||||
|
if info.hasDecomposition() {
|
||||||
|
// TODO: inline.
|
||||||
|
rb.insertDecomposed(info.Decomposition())
|
||||||
|
} else {
|
||||||
|
rb.insertSingle(src, i, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertDecomposed inserts an entry in to the reorderBuffer for each rune
|
||||||
|
// in dcomp. dcomp must be a sequence of decomposed UTF-8-encoded runes.
|
||||||
|
// It flushes the buffer on each new segment start.
|
||||||
|
func (rb *reorderBuffer) insertDecomposed(dcomp []byte) insertErr {
|
||||||
|
rb.tmpBytes.setBytes(dcomp)
|
||||||
|
for i := 0; i < len(dcomp); {
|
||||||
|
info := rb.f.info(rb.tmpBytes, i)
|
||||||
|
if info.BoundaryBefore() && rb.nrune > 0 && !rb.doFlush() {
|
||||||
|
return iShortDst
|
||||||
|
}
|
||||||
|
i += copy(rb.byte[rb.nbyte:], dcomp[i:i+int(info.size)])
|
||||||
|
rb.insertOrdered(info)
|
||||||
|
}
|
||||||
|
return iSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertSingle inserts an entry in the reorderBuffer for the rune at
|
||||||
|
// position i. info is the runeInfo for the rune at position i.
|
||||||
|
func (rb *reorderBuffer) insertSingle(src input, i int, info Properties) {
|
||||||
|
src.copySlice(rb.byte[rb.nbyte:], i, i+int(info.size))
|
||||||
|
rb.insertOrdered(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertCGJ inserts a Combining Grapheme Joiner (0x034f) into rb.
|
||||||
|
func (rb *reorderBuffer) insertCGJ() {
|
||||||
|
rb.insertSingle(input{str: GraphemeJoiner}, 0, Properties{size: uint8(len(GraphemeJoiner))})
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendRune inserts a rune at the end of the buffer. It is used for Hangul.
|
||||||
|
func (rb *reorderBuffer) appendRune(r rune) {
|
||||||
|
bn := rb.nbyte
|
||||||
|
sz := utf8.EncodeRune(rb.byte[bn:], rune(r))
|
||||||
|
rb.nbyte += utf8.UTFMax
|
||||||
|
rb.rune[rb.nrune] = Properties{pos: bn, size: uint8(sz)}
|
||||||
|
rb.nrune++
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignRune sets a rune at position pos. It is used for Hangul and recomposition.
|
||||||
|
func (rb *reorderBuffer) assignRune(pos int, r rune) {
|
||||||
|
bn := rb.rune[pos].pos
|
||||||
|
sz := utf8.EncodeRune(rb.byte[bn:], rune(r))
|
||||||
|
rb.rune[pos] = Properties{pos: bn, size: uint8(sz)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runeAt returns the rune at position n. It is used for Hangul and recomposition.
|
||||||
|
func (rb *reorderBuffer) runeAt(n int) rune {
|
||||||
|
inf := rb.rune[n]
|
||||||
|
r, _ := utf8.DecodeRune(rb.byte[inf.pos : inf.pos+inf.size])
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesAt returns the UTF-8 encoding of the rune at position n.
|
||||||
|
// It is used for Hangul and recomposition.
|
||||||
|
func (rb *reorderBuffer) bytesAt(n int) []byte {
|
||||||
|
inf := rb.rune[n]
|
||||||
|
return rb.byte[inf.pos : int(inf.pos)+int(inf.size)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Hangul we combine algorithmically, instead of using tables.
|
||||||
|
const (
|
||||||
|
hangulBase = 0xAC00 // UTF-8(hangulBase) -> EA B0 80
|
||||||
|
hangulBase0 = 0xEA
|
||||||
|
hangulBase1 = 0xB0
|
||||||
|
hangulBase2 = 0x80
|
||||||
|
|
||||||
|
hangulEnd = hangulBase + jamoLVTCount // UTF-8(0xD7A4) -> ED 9E A4
|
||||||
|
hangulEnd0 = 0xED
|
||||||
|
hangulEnd1 = 0x9E
|
||||||
|
hangulEnd2 = 0xA4
|
||||||
|
|
||||||
|
jamoLBase = 0x1100 // UTF-8(jamoLBase) -> E1 84 00
|
||||||
|
jamoLBase0 = 0xE1
|
||||||
|
jamoLBase1 = 0x84
|
||||||
|
jamoLEnd = 0x1113
|
||||||
|
jamoVBase = 0x1161
|
||||||
|
jamoVEnd = 0x1176
|
||||||
|
jamoTBase = 0x11A7
|
||||||
|
jamoTEnd = 0x11C3
|
||||||
|
|
||||||
|
jamoTCount = 28
|
||||||
|
jamoVCount = 21
|
||||||
|
jamoVTCount = 21 * 28
|
||||||
|
jamoLVTCount = 19 * 21 * 28
|
||||||
|
)
|
||||||
|
|
||||||
|
const hangulUTF8Size = 3
|
||||||
|
|
||||||
|
func isHangul(b []byte) bool {
|
||||||
|
if len(b) < hangulUTF8Size {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b0 := b[0]
|
||||||
|
if b0 < hangulBase0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b1 := b[1]
|
||||||
|
switch {
|
||||||
|
case b0 == hangulBase0:
|
||||||
|
return b1 >= hangulBase1
|
||||||
|
case b0 < hangulEnd0:
|
||||||
|
return true
|
||||||
|
case b0 > hangulEnd0:
|
||||||
|
return false
|
||||||
|
case b1 < hangulEnd1:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return b1 == hangulEnd1 && b[2] < hangulEnd2
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHangulString(b string) bool {
|
||||||
|
if len(b) < hangulUTF8Size {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b0 := b[0]
|
||||||
|
if b0 < hangulBase0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b1 := b[1]
|
||||||
|
switch {
|
||||||
|
case b0 == hangulBase0:
|
||||||
|
return b1 >= hangulBase1
|
||||||
|
case b0 < hangulEnd0:
|
||||||
|
return true
|
||||||
|
case b0 > hangulEnd0:
|
||||||
|
return false
|
||||||
|
case b1 < hangulEnd1:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return b1 == hangulEnd1 && b[2] < hangulEnd2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caller must ensure len(b) >= 2.
|
||||||
|
func isJamoVT(b []byte) bool {
|
||||||
|
// True if (rune & 0xff00) == jamoLBase
|
||||||
|
return b[0] == jamoLBase0 && (b[1]&0xFC) == jamoLBase1
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHangulWithoutJamoT(b []byte) bool {
|
||||||
|
c, _ := utf8.DecodeRune(b)
|
||||||
|
c -= hangulBase
|
||||||
|
return c < jamoLVTCount && c%jamoTCount == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// decomposeHangul writes the decomposed Hangul to buf and returns the number
|
||||||
|
// of bytes written. len(buf) should be at least 9.
|
||||||
|
func decomposeHangul(buf []byte, r rune) int {
|
||||||
|
const JamoUTF8Len = 3
|
||||||
|
r -= hangulBase
|
||||||
|
x := r % jamoTCount
|
||||||
|
r /= jamoTCount
|
||||||
|
utf8.EncodeRune(buf, jamoLBase+r/jamoVCount)
|
||||||
|
utf8.EncodeRune(buf[JamoUTF8Len:], jamoVBase+r%jamoVCount)
|
||||||
|
if x != 0 {
|
||||||
|
utf8.EncodeRune(buf[2*JamoUTF8Len:], jamoTBase+x)
|
||||||
|
return 3 * JamoUTF8Len
|
||||||
|
}
|
||||||
|
return 2 * JamoUTF8Len
|
||||||
|
}
|
||||||
|
|
||||||
|
// decomposeHangul algorithmically decomposes a Hangul rune into
|
||||||
|
// its Jamo components.
|
||||||
|
// See http://unicode.org/reports/tr15/#Hangul for details on decomposing Hangul.
|
||||||
|
func (rb *reorderBuffer) decomposeHangul(r rune) {
|
||||||
|
r -= hangulBase
|
||||||
|
x := r % jamoTCount
|
||||||
|
r /= jamoTCount
|
||||||
|
rb.appendRune(jamoLBase + r/jamoVCount)
|
||||||
|
rb.appendRune(jamoVBase + r%jamoVCount)
|
||||||
|
if x != 0 {
|
||||||
|
rb.appendRune(jamoTBase + x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// combineHangul algorithmically combines Jamo character components into Hangul.
|
||||||
|
// See http://unicode.org/reports/tr15/#Hangul for details on combining Hangul.
|
||||||
|
func (rb *reorderBuffer) combineHangul(s, i, k int) {
|
||||||
|
b := rb.rune[:]
|
||||||
|
bn := rb.nrune
|
||||||
|
for ; i < bn; i++ {
|
||||||
|
cccB := b[k-1].ccc
|
||||||
|
cccC := b[i].ccc
|
||||||
|
if cccB == 0 {
|
||||||
|
s = k - 1
|
||||||
|
}
|
||||||
|
if s != k-1 && cccB >= cccC {
|
||||||
|
// b[i] is blocked by greater-equal cccX below it
|
||||||
|
b[k] = b[i]
|
||||||
|
k++
|
||||||
|
} else {
|
||||||
|
l := rb.runeAt(s) // also used to compare to hangulBase
|
||||||
|
v := rb.runeAt(i) // also used to compare to jamoT
|
||||||
|
switch {
|
||||||
|
case jamoLBase <= l && l < jamoLEnd &&
|
||||||
|
jamoVBase <= v && v < jamoVEnd:
|
||||||
|
// 11xx plus 116x to LV
|
||||||
|
rb.assignRune(s, hangulBase+
|
||||||
|
(l-jamoLBase)*jamoVTCount+(v-jamoVBase)*jamoTCount)
|
||||||
|
case hangulBase <= l && l < hangulEnd &&
|
||||||
|
jamoTBase < v && v < jamoTEnd &&
|
||||||
|
((l-hangulBase)%jamoTCount) == 0:
|
||||||
|
// ACxx plus 11Ax to LVT
|
||||||
|
rb.assignRune(s, l+v-jamoTBase)
|
||||||
|
default:
|
||||||
|
b[k] = b[i]
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rb.nrune = k
|
||||||
|
}
|
||||||
|
|
||||||
|
// compose recombines the runes in the buffer.
|
||||||
|
// It should only be used to recompose a single segment, as it will not
|
||||||
|
// handle alternations between Hangul and non-Hangul characters correctly.
|
||||||
|
func (rb *reorderBuffer) compose() {
|
||||||
|
// UAX #15, section X5 , including Corrigendum #5
|
||||||
|
// "In any character sequence beginning with starter S, a character C is
|
||||||
|
// blocked from S if and only if there is some character B between S
|
||||||
|
// and C, and either B is a starter or it has the same or higher
|
||||||
|
// combining class as C."
|
||||||
|
bn := rb.nrune
|
||||||
|
if bn == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k := 1
|
||||||
|
b := rb.rune[:]
|
||||||
|
for s, i := 0, 1; i < bn; i++ {
|
||||||
|
if isJamoVT(rb.bytesAt(i)) {
|
||||||
|
// Redo from start in Hangul mode. Necessary to support
|
||||||
|
// U+320E..U+321E in NFKC mode.
|
||||||
|
rb.combineHangul(s, i, k)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ii := b[i]
|
||||||
|
// We can only use combineForward as a filter if we later
|
||||||
|
// get the info for the combined character. This is more
|
||||||
|
// expensive than using the filter. Using combinesBackward()
|
||||||
|
// is safe.
|
||||||
|
if ii.combinesBackward() {
|
||||||
|
cccB := b[k-1].ccc
|
||||||
|
cccC := ii.ccc
|
||||||
|
blocked := false // b[i] blocked by starter or greater or equal CCC?
|
||||||
|
if cccB == 0 {
|
||||||
|
s = k - 1
|
||||||
|
} else {
|
||||||
|
blocked = s != k-1 && cccB >= cccC
|
||||||
|
}
|
||||||
|
if !blocked {
|
||||||
|
combined := combine(rb.runeAt(s), rb.runeAt(i))
|
||||||
|
if combined != 0 {
|
||||||
|
rb.assignRune(s, combined)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b[k] = b[i]
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
rb.nrune = k
|
||||||
|
}
|
256
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/forminfo.go
generated
vendored
Normal file
256
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/forminfo.go
generated
vendored
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package norm
|
||||||
|
|
||||||
|
// This file contains Form-specific logic and wrappers for data in tables.go.
|
||||||
|
|
||||||
|
// Rune info is stored in a separate trie per composing form. A composing form
|
||||||
|
// and its corresponding decomposing form share the same trie. Each trie maps
|
||||||
|
// a rune to a uint16. The values take two forms. For v >= 0x8000:
|
||||||
|
// bits
|
||||||
|
// 15: 1 (inverse of NFD_QD bit of qcInfo)
|
||||||
|
// 13..7: qcInfo (see below). isYesD is always true (no decompostion).
|
||||||
|
// 6..0: ccc (compressed CCC value).
|
||||||
|
// For v < 0x8000, the respective rune has a decomposition and v is an index
|
||||||
|
// into a byte array of UTF-8 decomposition sequences and additional info and
|
||||||
|
// has the form:
|
||||||
|
// <header> <decomp_byte>* [<tccc> [<lccc>]]
|
||||||
|
// The header contains the number of bytes in the decomposition (excluding this
|
||||||
|
// length byte). The two most significant bits of this length byte correspond
|
||||||
|
// to bit 5 and 4 of qcInfo (see below). The byte sequence itself starts at v+1.
|
||||||
|
// The byte sequence is followed by a trailing and leading CCC if the values
|
||||||
|
// for these are not zero. The value of v determines which ccc are appended
|
||||||
|
// to the sequences. For v < firstCCC, there are none, for v >= firstCCC,
|
||||||
|
// the sequence is followed by a trailing ccc, and for v >= firstLeadingCC
|
||||||
|
// there is an additional leading ccc. The value of tccc itself is the
|
||||||
|
// trailing CCC shifted left 2 bits. The two least-significant bits of tccc
|
||||||
|
// are the number of trailing non-starters.
|
||||||
|
|
||||||
|
const (
|
||||||
|
qcInfoMask = 0x3F // to clear all but the relevant bits in a qcInfo
|
||||||
|
headerLenMask = 0x3F // extract the length value from the header byte
|
||||||
|
headerFlagsMask = 0xC0 // extract the qcInfo bits from the header byte
|
||||||
|
)
|
||||||
|
|
||||||
|
// Properties provides access to normalization properties of a rune.
|
||||||
|
type Properties struct {
|
||||||
|
pos uint8 // start position in reorderBuffer; used in composition.go
|
||||||
|
size uint8 // length of UTF-8 encoding of this rune
|
||||||
|
ccc uint8 // leading canonical combining class (ccc if not decomposition)
|
||||||
|
tccc uint8 // trailing canonical combining class (ccc if not decomposition)
|
||||||
|
nLead uint8 // number of leading non-starters.
|
||||||
|
flags qcInfo // quick check flags
|
||||||
|
index uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// functions dispatchable per form
|
||||||
|
type lookupFunc func(b input, i int) Properties
|
||||||
|
|
||||||
|
// formInfo holds Form-specific functions and tables.
|
||||||
|
type formInfo struct {
|
||||||
|
form Form
|
||||||
|
composing, compatibility bool // form type
|
||||||
|
info lookupFunc
|
||||||
|
nextMain iterFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
var formTable []*formInfo
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
formTable = make([]*formInfo, 4)
|
||||||
|
|
||||||
|
for i := range formTable {
|
||||||
|
f := &formInfo{}
|
||||||
|
formTable[i] = f
|
||||||
|
f.form = Form(i)
|
||||||
|
if Form(i) == NFKD || Form(i) == NFKC {
|
||||||
|
f.compatibility = true
|
||||||
|
f.info = lookupInfoNFKC
|
||||||
|
} else {
|
||||||
|
f.info = lookupInfoNFC
|
||||||
|
}
|
||||||
|
f.nextMain = nextDecomposed
|
||||||
|
if Form(i) == NFC || Form(i) == NFKC {
|
||||||
|
f.nextMain = nextComposed
|
||||||
|
f.composing = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not distinguish between boundaries for NFC, NFD, etc. to avoid
|
||||||
|
// unexpected behavior for the user. For example, in NFD, there is a boundary
|
||||||
|
// after 'a'. However, 'a' might combine with modifiers, so from the application's
|
||||||
|
// perspective it is not a good boundary. We will therefore always use the
|
||||||
|
// boundaries for the combining variants.
|
||||||
|
|
||||||
|
// BoundaryBefore returns true if this rune starts a new segment and
|
||||||
|
// cannot combine with any rune on the left.
|
||||||
|
func (p Properties) BoundaryBefore() bool {
|
||||||
|
if p.ccc == 0 && !p.combinesBackward() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// We assume that the CCC of the first character in a decomposition
|
||||||
|
// is always non-zero if different from info.ccc and that we can return
|
||||||
|
// false at this point. This is verified by maketables.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoundaryAfter returns true if runes cannot combine with or otherwise
|
||||||
|
// interact with this or previous runes.
|
||||||
|
func (p Properties) BoundaryAfter() bool {
|
||||||
|
// TODO: loosen these conditions.
|
||||||
|
return p.isInert()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We pack quick check data in 4 bits:
|
||||||
|
// 5: Combines forward (0 == false, 1 == true)
|
||||||
|
// 4..3: NFC_QC Yes(00), No (10), or Maybe (11)
|
||||||
|
// 2: NFD_QC Yes (0) or No (1). No also means there is a decomposition.
|
||||||
|
// 1..0: Number of trailing non-starters.
|
||||||
|
//
|
||||||
|
// When all 4 bits are zero, the character is inert, meaning it is never
|
||||||
|
// influenced by normalization.
|
||||||
|
type qcInfo uint8
|
||||||
|
|
||||||
|
func (p Properties) isYesC() bool { return p.flags&0x10 == 0 }
|
||||||
|
func (p Properties) isYesD() bool { return p.flags&0x4 == 0 }
|
||||||
|
|
||||||
|
func (p Properties) combinesForward() bool { return p.flags&0x20 != 0 }
|
||||||
|
func (p Properties) combinesBackward() bool { return p.flags&0x8 != 0 } // == isMaybe
|
||||||
|
func (p Properties) hasDecomposition() bool { return p.flags&0x4 != 0 } // == isNoD
|
||||||
|
|
||||||
|
func (p Properties) isInert() bool {
|
||||||
|
return p.flags&qcInfoMask == 0 && p.ccc == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Properties) multiSegment() bool {
|
||||||
|
return p.index >= firstMulti && p.index < endMulti
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Properties) nLeadingNonStarters() uint8 {
|
||||||
|
return p.nLead
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Properties) nTrailingNonStarters() uint8 {
|
||||||
|
return uint8(p.flags & 0x03)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decomposition returns the decomposition for the underlying rune
|
||||||
|
// or nil if there is none.
|
||||||
|
func (p Properties) Decomposition() []byte {
|
||||||
|
// TODO: create the decomposition for Hangul?
|
||||||
|
if p.index == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i := p.index
|
||||||
|
n := decomps[i] & headerLenMask
|
||||||
|
i++
|
||||||
|
return decomps[i : i+uint16(n)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the length of UTF-8 encoding of the rune.
|
||||||
|
func (p Properties) Size() int {
|
||||||
|
return int(p.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CCC returns the canonical combining class of the underlying rune.
|
||||||
|
func (p Properties) CCC() uint8 {
|
||||||
|
if p.index >= firstCCCZeroExcept {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return ccc[p.ccc]
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeadCCC returns the CCC of the first rune in the decomposition.
|
||||||
|
// If there is no decomposition, LeadCCC equals CCC.
|
||||||
|
func (p Properties) LeadCCC() uint8 {
|
||||||
|
return ccc[p.ccc]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrailCCC returns the CCC of the last rune in the decomposition.
|
||||||
|
// If there is no decomposition, TrailCCC equals CCC.
|
||||||
|
func (p Properties) TrailCCC() uint8 {
|
||||||
|
return ccc[p.tccc]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recomposition
|
||||||
|
// We use 32-bit keys instead of 64-bit for the two codepoint keys.
|
||||||
|
// This clips off the bits of three entries, but we know this will not
|
||||||
|
// result in a collision. In the unlikely event that changes to
|
||||||
|
// UnicodeData.txt introduce collisions, the compiler will catch it.
|
||||||
|
// Note that the recomposition map for NFC and NFKC are identical.
|
||||||
|
|
||||||
|
// combine returns the combined rune or 0 if it doesn't exist.
|
||||||
|
func combine(a, b rune) rune {
|
||||||
|
key := uint32(uint16(a))<<16 + uint32(uint16(b))
|
||||||
|
return recompMap[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupInfoNFC(b input, i int) Properties {
|
||||||
|
v, sz := b.charinfoNFC(i)
|
||||||
|
return compInfo(v, sz)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupInfoNFKC(b input, i int) Properties {
|
||||||
|
v, sz := b.charinfoNFKC(i)
|
||||||
|
return compInfo(v, sz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties returns properties for the first rune in s.
|
||||||
|
func (f Form) Properties(s []byte) Properties {
|
||||||
|
if f == NFC || f == NFD {
|
||||||
|
return compInfo(nfcData.lookup(s))
|
||||||
|
}
|
||||||
|
return compInfo(nfkcData.lookup(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PropertiesString returns properties for the first rune in s.
|
||||||
|
func (f Form) PropertiesString(s string) Properties {
|
||||||
|
if f == NFC || f == NFD {
|
||||||
|
return compInfo(nfcData.lookupString(s))
|
||||||
|
}
|
||||||
|
return compInfo(nfkcData.lookupString(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// compInfo converts the information contained in v and sz
|
||||||
|
// to a Properties. See the comment at the top of the file
|
||||||
|
// for more information on the format.
|
||||||
|
func compInfo(v uint16, sz int) Properties {
|
||||||
|
if v == 0 {
|
||||||
|
return Properties{size: uint8(sz)}
|
||||||
|
} else if v >= 0x8000 {
|
||||||
|
p := Properties{
|
||||||
|
size: uint8(sz),
|
||||||
|
ccc: uint8(v),
|
||||||
|
tccc: uint8(v),
|
||||||
|
flags: qcInfo(v >> 8),
|
||||||
|
}
|
||||||
|
if p.ccc > 0 || p.combinesBackward() {
|
||||||
|
p.nLead = uint8(p.flags & 0x3)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
// has decomposition
|
||||||
|
h := decomps[v]
|
||||||
|
f := (qcInfo(h&headerFlagsMask) >> 2) | 0x4
|
||||||
|
p := Properties{size: uint8(sz), flags: f, index: v}
|
||||||
|
if v >= firstCCC {
|
||||||
|
v += uint16(h&headerLenMask) + 1
|
||||||
|
c := decomps[v]
|
||||||
|
p.tccc = c >> 2
|
||||||
|
p.flags |= qcInfo(c & 0x3)
|
||||||
|
if v >= firstLeadingCCC {
|
||||||
|
p.nLead = c & 0x3
|
||||||
|
if v >= firstStarterWithNLead {
|
||||||
|
// We were tricked. Remove the decomposition.
|
||||||
|
p.flags &= 0x03
|
||||||
|
p.index = 0
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
p.ccc = decomps[v+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
105
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/input.go
generated
vendored
Normal file
105
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/input.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package norm
|
||||||
|
|
||||||
|
import "unicode/utf8"
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
str string
|
||||||
|
bytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func inputBytes(str []byte) input {
|
||||||
|
return input{bytes: str}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inputString(str string) input {
|
||||||
|
return input{str: str}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) setBytes(str []byte) {
|
||||||
|
in.str = ""
|
||||||
|
in.bytes = str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) setString(str string) {
|
||||||
|
in.str = str
|
||||||
|
in.bytes = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) _byte(p int) byte {
|
||||||
|
if in.bytes == nil {
|
||||||
|
return in.str[p]
|
||||||
|
}
|
||||||
|
return in.bytes[p]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) skipASCII(p, max int) int {
|
||||||
|
if in.bytes == nil {
|
||||||
|
for ; p < max && in.str[p] < utf8.RuneSelf; p++ {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for ; p < max && in.bytes[p] < utf8.RuneSelf; p++ {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) skipContinuationBytes(p int) int {
|
||||||
|
if in.bytes == nil {
|
||||||
|
for ; p < len(in.str) && !utf8.RuneStart(in.str[p]); p++ {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for ; p < len(in.bytes) && !utf8.RuneStart(in.bytes[p]); p++ {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) appendSlice(buf []byte, b, e int) []byte {
|
||||||
|
if in.bytes != nil {
|
||||||
|
return append(buf, in.bytes[b:e]...)
|
||||||
|
}
|
||||||
|
for i := b; i < e; i++ {
|
||||||
|
buf = append(buf, in.str[i])
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) copySlice(buf []byte, b, e int) int {
|
||||||
|
if in.bytes == nil {
|
||||||
|
return copy(buf, in.str[b:e])
|
||||||
|
}
|
||||||
|
return copy(buf, in.bytes[b:e])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) charinfoNFC(p int) (uint16, int) {
|
||||||
|
if in.bytes == nil {
|
||||||
|
return nfcData.lookupString(in.str[p:])
|
||||||
|
}
|
||||||
|
return nfcData.lookup(in.bytes[p:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) charinfoNFKC(p int) (uint16, int) {
|
||||||
|
if in.bytes == nil {
|
||||||
|
return nfkcData.lookupString(in.str[p:])
|
||||||
|
}
|
||||||
|
return nfkcData.lookup(in.bytes[p:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *input) hangul(p int) (r rune) {
|
||||||
|
if in.bytes == nil {
|
||||||
|
if !isHangulString(in.str[p:]) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
r, _ = utf8.DecodeRuneInString(in.str[p:])
|
||||||
|
} else {
|
||||||
|
if !isHangul(in.bytes[p:]) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
r, _ = utf8.DecodeRune(in.bytes[p:])
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
450
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/iter.go
generated
vendored
Normal file
450
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/iter.go
generated
vendored
Normal file
|
@ -0,0 +1,450 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package norm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxSegmentSize is the maximum size of a byte buffer needed to consider any
|
||||||
|
// sequence of starter and non-starter runes for the purpose of normalization.
|
||||||
|
const MaxSegmentSize = maxByteBufferSize
|
||||||
|
|
||||||
|
// An Iter iterates over a string or byte slice, while normalizing it
|
||||||
|
// to a given Form.
|
||||||
|
type Iter struct {
|
||||||
|
rb reorderBuffer
|
||||||
|
buf [maxByteBufferSize]byte
|
||||||
|
info Properties // first character saved from previous iteration
|
||||||
|
next iterFunc // implementation of next depends on form
|
||||||
|
asciiF iterFunc
|
||||||
|
|
||||||
|
p int // current position in input source
|
||||||
|
multiSeg []byte // remainder of multi-segment decomposition
|
||||||
|
}
|
||||||
|
|
||||||
|
type iterFunc func(*Iter) []byte
|
||||||
|
|
||||||
|
// Init initializes i to iterate over src after normalizing it to Form f.
|
||||||
|
func (i *Iter) Init(f Form, src []byte) {
|
||||||
|
i.p = 0
|
||||||
|
if len(src) == 0 {
|
||||||
|
i.setDone()
|
||||||
|
i.rb.nsrc = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i.multiSeg = nil
|
||||||
|
i.rb.init(f, src)
|
||||||
|
i.next = i.rb.f.nextMain
|
||||||
|
i.asciiF = nextASCIIBytes
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitString initializes i to iterate over src after normalizing it to Form f.
|
||||||
|
func (i *Iter) InitString(f Form, src string) {
|
||||||
|
i.p = 0
|
||||||
|
if len(src) == 0 {
|
||||||
|
i.setDone()
|
||||||
|
i.rb.nsrc = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i.multiSeg = nil
|
||||||
|
i.rb.initString(f, src)
|
||||||
|
i.next = i.rb.f.nextMain
|
||||||
|
i.asciiF = nextASCIIString
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek sets the segment to be returned by the next call to Next to start
|
||||||
|
// at position p. It is the responsibility of the caller to set p to the
|
||||||
|
// start of a UTF8 rune.
|
||||||
|
func (i *Iter) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
var abs int64
|
||||||
|
switch whence {
|
||||||
|
case 0:
|
||||||
|
abs = offset
|
||||||
|
case 1:
|
||||||
|
abs = int64(i.p) + offset
|
||||||
|
case 2:
|
||||||
|
abs = int64(i.rb.nsrc) + offset
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("norm: invalid whence")
|
||||||
|
}
|
||||||
|
if abs < 0 {
|
||||||
|
return 0, fmt.Errorf("norm: negative position")
|
||||||
|
}
|
||||||
|
if int(abs) >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
return int64(i.p), nil
|
||||||
|
}
|
||||||
|
i.p = int(abs)
|
||||||
|
i.multiSeg = nil
|
||||||
|
i.next = i.rb.f.nextMain
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
return abs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returnSlice returns a slice of the underlying input type as a byte slice.
|
||||||
|
// If the underlying is of type []byte, it will simply return a slice.
|
||||||
|
// If the underlying is of type string, it will copy the slice to the buffer
|
||||||
|
// and return that.
|
||||||
|
func (i *Iter) returnSlice(a, b int) []byte {
|
||||||
|
if i.rb.src.bytes == nil {
|
||||||
|
return i.buf[:copy(i.buf[:], i.rb.src.str[a:b])]
|
||||||
|
}
|
||||||
|
return i.rb.src.bytes[a:b]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pos returns the byte position at which the next call to Next will commence processing.
|
||||||
|
func (i *Iter) Pos() int {
|
||||||
|
return i.p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iter) setDone() {
|
||||||
|
i.next = nextDone
|
||||||
|
i.p = i.rb.nsrc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done returns true if there is no more input to process.
|
||||||
|
func (i *Iter) Done() bool {
|
||||||
|
return i.p >= i.rb.nsrc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns f(i.input[i.Pos():n]), where n is a boundary of i.input.
|
||||||
|
// For any input a and b for which f(a) == f(b), subsequent calls
|
||||||
|
// to Next will return the same segments.
|
||||||
|
// Modifying runes are grouped together with the preceding starter, if such a starter exists.
|
||||||
|
// Although not guaranteed, n will typically be the smallest possible n.
|
||||||
|
func (i *Iter) Next() []byte {
|
||||||
|
return i.next(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextASCIIBytes(i *Iter) []byte {
|
||||||
|
p := i.p + 1
|
||||||
|
if p >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
return i.rb.src.bytes[i.p:p]
|
||||||
|
}
|
||||||
|
if i.rb.src.bytes[p] < utf8.RuneSelf {
|
||||||
|
p0 := i.p
|
||||||
|
i.p = p
|
||||||
|
return i.rb.src.bytes[p0:p]
|
||||||
|
}
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
i.next = i.rb.f.nextMain
|
||||||
|
return i.next(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextASCIIString(i *Iter) []byte {
|
||||||
|
p := i.p + 1
|
||||||
|
if p >= i.rb.nsrc {
|
||||||
|
i.buf[0] = i.rb.src.str[i.p]
|
||||||
|
i.setDone()
|
||||||
|
return i.buf[:1]
|
||||||
|
}
|
||||||
|
if i.rb.src.str[p] < utf8.RuneSelf {
|
||||||
|
i.buf[0] = i.rb.src.str[i.p]
|
||||||
|
i.p = p
|
||||||
|
return i.buf[:1]
|
||||||
|
}
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
i.next = i.rb.f.nextMain
|
||||||
|
return i.next(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextHangul(i *Iter) []byte {
|
||||||
|
p := i.p
|
||||||
|
next := p + hangulUTF8Size
|
||||||
|
if next >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
} else if i.rb.src.hangul(next) == 0 {
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
i.next = i.rb.f.nextMain
|
||||||
|
return i.next(i)
|
||||||
|
}
|
||||||
|
i.p = next
|
||||||
|
return i.buf[:decomposeHangul(i.buf[:], i.rb.src.hangul(p))]
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextDone(i *Iter) []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextMulti is used for iterating over multi-segment decompositions
|
||||||
|
// for decomposing normal forms.
|
||||||
|
func nextMulti(i *Iter) []byte {
|
||||||
|
j := 0
|
||||||
|
d := i.multiSeg
|
||||||
|
// skip first rune
|
||||||
|
for j = 1; j < len(d) && !utf8.RuneStart(d[j]); j++ {
|
||||||
|
}
|
||||||
|
for j < len(d) {
|
||||||
|
info := i.rb.f.info(input{bytes: d}, j)
|
||||||
|
if info.BoundaryBefore() {
|
||||||
|
i.multiSeg = d[j:]
|
||||||
|
return d[:j]
|
||||||
|
}
|
||||||
|
j += int(info.size)
|
||||||
|
}
|
||||||
|
// treat last segment as normal decomposition
|
||||||
|
i.next = i.rb.f.nextMain
|
||||||
|
return i.next(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextMultiNorm is used for iterating over multi-segment decompositions
|
||||||
|
// for composing normal forms.
|
||||||
|
func nextMultiNorm(i *Iter) []byte {
|
||||||
|
j := 0
|
||||||
|
d := i.multiSeg
|
||||||
|
for j < len(d) {
|
||||||
|
info := i.rb.f.info(input{bytes: d}, j)
|
||||||
|
if info.BoundaryBefore() {
|
||||||
|
i.rb.compose()
|
||||||
|
seg := i.buf[:i.rb.flushCopy(i.buf[:])]
|
||||||
|
i.rb.ss.first(info)
|
||||||
|
i.rb.insertUnsafe(input{bytes: d}, j, info)
|
||||||
|
i.multiSeg = d[j+int(info.size):]
|
||||||
|
return seg
|
||||||
|
}
|
||||||
|
i.rb.ss.next(info)
|
||||||
|
i.rb.insertUnsafe(input{bytes: d}, j, info)
|
||||||
|
j += int(info.size)
|
||||||
|
}
|
||||||
|
i.multiSeg = nil
|
||||||
|
i.next = nextComposed
|
||||||
|
return doNormComposed(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextDecomposed is the implementation of Next for forms NFD and NFKD.
|
||||||
|
func nextDecomposed(i *Iter) (next []byte) {
|
||||||
|
outp := 0
|
||||||
|
inCopyStart, outCopyStart := i.p, 0
|
||||||
|
ss := mkStreamSafe(i.info)
|
||||||
|
for {
|
||||||
|
if sz := int(i.info.size); sz <= 1 {
|
||||||
|
p := i.p
|
||||||
|
i.p++ // ASCII or illegal byte. Either way, advance by 1.
|
||||||
|
if i.p >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
return i.returnSlice(p, i.p)
|
||||||
|
} else if i.rb.src._byte(i.p) < utf8.RuneSelf {
|
||||||
|
i.next = i.asciiF
|
||||||
|
return i.returnSlice(p, i.p)
|
||||||
|
}
|
||||||
|
outp++
|
||||||
|
} else if d := i.info.Decomposition(); d != nil {
|
||||||
|
// Note: If leading CCC != 0, then len(d) == 2 and last is also non-zero.
|
||||||
|
// Case 1: there is a leftover to copy. In this case the decomposition
|
||||||
|
// must begin with a modifier and should always be appended.
|
||||||
|
// Case 2: no leftover. Simply return d if followed by a ccc == 0 value.
|
||||||
|
p := outp + len(d)
|
||||||
|
if outp > 0 {
|
||||||
|
i.rb.src.copySlice(i.buf[outCopyStart:], inCopyStart, i.p)
|
||||||
|
if p > len(i.buf) {
|
||||||
|
return i.buf[:outp]
|
||||||
|
}
|
||||||
|
} else if i.info.multiSegment() {
|
||||||
|
// outp must be 0 as multi-segment decompositions always
|
||||||
|
// start a new segment.
|
||||||
|
if i.multiSeg == nil {
|
||||||
|
i.multiSeg = d
|
||||||
|
i.next = nextMulti
|
||||||
|
return nextMulti(i)
|
||||||
|
}
|
||||||
|
// We are in the last segment. Treat as normal decomposition.
|
||||||
|
d = i.multiSeg
|
||||||
|
i.multiSeg = nil
|
||||||
|
p = len(d)
|
||||||
|
}
|
||||||
|
prevCC := i.info.tccc
|
||||||
|
if i.p += sz; i.p >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
i.info = Properties{} // Force BoundaryBefore to succeed.
|
||||||
|
} else {
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
}
|
||||||
|
switch ss.next(i.info) {
|
||||||
|
case ssOverflow:
|
||||||
|
i.next = nextCGJDecompose
|
||||||
|
fallthrough
|
||||||
|
case ssStarter:
|
||||||
|
if outp > 0 {
|
||||||
|
copy(i.buf[outp:], d)
|
||||||
|
return i.buf[:p]
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
copy(i.buf[outp:], d)
|
||||||
|
outp = p
|
||||||
|
inCopyStart, outCopyStart = i.p, outp
|
||||||
|
if i.info.ccc < prevCC {
|
||||||
|
goto doNorm
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if r := i.rb.src.hangul(i.p); r != 0 {
|
||||||
|
outp = decomposeHangul(i.buf[:], r)
|
||||||
|
i.p += hangulUTF8Size
|
||||||
|
inCopyStart, outCopyStart = i.p, outp
|
||||||
|
if i.p >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
break
|
||||||
|
} else if i.rb.src.hangul(i.p) != 0 {
|
||||||
|
i.next = nextHangul
|
||||||
|
return i.buf[:outp]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p := outp + sz
|
||||||
|
if p > len(i.buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
outp = p
|
||||||
|
i.p += sz
|
||||||
|
}
|
||||||
|
if i.p >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevCC := i.info.tccc
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
if v := ss.next(i.info); v == ssStarter {
|
||||||
|
break
|
||||||
|
} else if v == ssOverflow {
|
||||||
|
i.next = nextCGJDecompose
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i.info.ccc < prevCC {
|
||||||
|
goto doNorm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if outCopyStart == 0 {
|
||||||
|
return i.returnSlice(inCopyStart, i.p)
|
||||||
|
} else if inCopyStart < i.p {
|
||||||
|
i.rb.src.copySlice(i.buf[outCopyStart:], inCopyStart, i.p)
|
||||||
|
}
|
||||||
|
return i.buf[:outp]
|
||||||
|
doNorm:
|
||||||
|
// Insert what we have decomposed so far in the reorderBuffer.
|
||||||
|
// As we will only reorder, there will always be enough room.
|
||||||
|
i.rb.src.copySlice(i.buf[outCopyStart:], inCopyStart, i.p)
|
||||||
|
i.rb.insertDecomposed(i.buf[0:outp])
|
||||||
|
return doNormDecomposed(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doNormDecomposed(i *Iter) []byte {
|
||||||
|
for {
|
||||||
|
if s := i.rb.ss.next(i.info); s == ssOverflow {
|
||||||
|
i.next = nextCGJDecompose
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i.rb.insertUnsafe(i.rb.src, i.p, i.info)
|
||||||
|
if i.p += int(i.info.size); i.p >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
if i.info.ccc == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// new segment or too many combining characters: exit normalization
|
||||||
|
return i.buf[:i.rb.flushCopy(i.buf[:])]
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextCGJDecompose(i *Iter) []byte {
|
||||||
|
i.rb.ss = 0
|
||||||
|
i.rb.insertCGJ()
|
||||||
|
i.next = nextDecomposed
|
||||||
|
buf := doNormDecomposed(i)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextComposed is the implementation of Next for forms NFC and NFKC.
|
||||||
|
func nextComposed(i *Iter) []byte {
|
||||||
|
outp, startp := 0, i.p
|
||||||
|
var prevCC uint8
|
||||||
|
ss := mkStreamSafe(i.info)
|
||||||
|
for {
|
||||||
|
if !i.info.isYesC() {
|
||||||
|
goto doNorm
|
||||||
|
}
|
||||||
|
prevCC = i.info.tccc
|
||||||
|
sz := int(i.info.size)
|
||||||
|
if sz == 0 {
|
||||||
|
sz = 1 // illegal rune: copy byte-by-byte
|
||||||
|
}
|
||||||
|
p := outp + sz
|
||||||
|
if p > len(i.buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
outp = p
|
||||||
|
i.p += sz
|
||||||
|
if i.p >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
break
|
||||||
|
} else if i.rb.src._byte(i.p) < utf8.RuneSelf {
|
||||||
|
i.next = i.asciiF
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
if v := ss.next(i.info); v == ssStarter {
|
||||||
|
break
|
||||||
|
} else if v == ssOverflow {
|
||||||
|
i.next = nextCGJCompose
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i.info.ccc < prevCC {
|
||||||
|
goto doNorm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i.returnSlice(startp, i.p)
|
||||||
|
doNorm:
|
||||||
|
i.p = startp
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
if i.info.multiSegment() {
|
||||||
|
d := i.info.Decomposition()
|
||||||
|
info := i.rb.f.info(input{bytes: d}, 0)
|
||||||
|
i.rb.insertUnsafe(input{bytes: d}, 0, info)
|
||||||
|
i.multiSeg = d[int(info.size):]
|
||||||
|
i.next = nextMultiNorm
|
||||||
|
return nextMultiNorm(i)
|
||||||
|
}
|
||||||
|
i.rb.ss.first(i.info)
|
||||||
|
i.rb.insertUnsafe(i.rb.src, i.p, i.info)
|
||||||
|
return doNormComposed(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doNormComposed(i *Iter) []byte {
|
||||||
|
// First rune should already be inserted.
|
||||||
|
for {
|
||||||
|
if i.p += int(i.info.size); i.p >= i.rb.nsrc {
|
||||||
|
i.setDone()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i.info = i.rb.f.info(i.rb.src, i.p)
|
||||||
|
if s := i.rb.ss.next(i.info); s == ssStarter {
|
||||||
|
break
|
||||||
|
} else if s == ssOverflow {
|
||||||
|
i.next = nextCGJCompose
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i.rb.insertUnsafe(i.rb.src, i.p, i.info)
|
||||||
|
}
|
||||||
|
i.rb.compose()
|
||||||
|
seg := i.buf[:i.rb.flushCopy(i.buf[:])]
|
||||||
|
return seg
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextCGJCompose(i *Iter) []byte {
|
||||||
|
i.rb.ss = 0 // instead of first
|
||||||
|
i.rb.insertCGJ()
|
||||||
|
i.next = nextComposed
|
||||||
|
// Note that we treat any rune with nLeadingNonStarters > 0 as a non-starter,
|
||||||
|
// even if they are not. This is particularly dubious for U+FF9E and UFF9A.
|
||||||
|
// If we ever change that, insert a check here.
|
||||||
|
i.rb.ss.first(i.info)
|
||||||
|
i.rb.insertUnsafe(i.rb.src, i.p, i.info)
|
||||||
|
return doNormComposed(i)
|
||||||
|
}
|
1033
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/maketables.go
generated
vendored
Normal file
1033
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/maketables.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
524
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/normalize.go
generated
vendored
Normal file
524
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/normalize.go
generated
vendored
Normal file
|
@ -0,0 +1,524 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package norm contains types and functions for normalizing Unicode strings.
|
||||||
|
package norm
|
||||||
|
|
||||||
|
import "unicode/utf8"
|
||||||
|
|
||||||
|
// A Form denotes a canonical representation of Unicode code points.
|
||||||
|
// The Unicode-defined normalization and equivalence forms are:
|
||||||
|
//
|
||||||
|
// NFC Unicode Normalization Form C
|
||||||
|
// NFD Unicode Normalization Form D
|
||||||
|
// NFKC Unicode Normalization Form KC
|
||||||
|
// NFKD Unicode Normalization Form KD
|
||||||
|
//
|
||||||
|
// For a Form f, this documentation uses the notation f(x) to mean
|
||||||
|
// the bytes or string x converted to the given form.
|
||||||
|
// A position n in x is called a boundary if conversion to the form can
|
||||||
|
// proceed independently on both sides:
|
||||||
|
// f(x) == append(f(x[0:n]), f(x[n:])...)
|
||||||
|
//
|
||||||
|
// References: http://unicode.org/reports/tr15/ and
|
||||||
|
// http://unicode.org/notes/tn5/.
|
||||||
|
type Form int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NFC Form = iota
|
||||||
|
NFD
|
||||||
|
NFKC
|
||||||
|
NFKD
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bytes returns f(b). May return b if f(b) = b.
|
||||||
|
func (f Form) Bytes(b []byte) []byte {
|
||||||
|
src := inputBytes(b)
|
||||||
|
ft := formTable[f]
|
||||||
|
n, ok := ft.quickSpan(src, 0, len(b), true)
|
||||||
|
if ok {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
out := make([]byte, n, len(b))
|
||||||
|
copy(out, b[0:n])
|
||||||
|
rb := reorderBuffer{f: *ft, src: src, nsrc: len(b), out: out, flushF: appendFlush}
|
||||||
|
return doAppendInner(&rb, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns f(s).
|
||||||
|
func (f Form) String(s string) string {
|
||||||
|
src := inputString(s)
|
||||||
|
ft := formTable[f]
|
||||||
|
n, ok := ft.quickSpan(src, 0, len(s), true)
|
||||||
|
if ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
out := make([]byte, n, len(s))
|
||||||
|
copy(out, s[0:n])
|
||||||
|
rb := reorderBuffer{f: *ft, src: src, nsrc: len(s), out: out, flushF: appendFlush}
|
||||||
|
return string(doAppendInner(&rb, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNormal returns true if b == f(b).
|
||||||
|
func (f Form) IsNormal(b []byte) bool {
|
||||||
|
src := inputBytes(b)
|
||||||
|
ft := formTable[f]
|
||||||
|
bp, ok := ft.quickSpan(src, 0, len(b), true)
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
rb := reorderBuffer{f: *ft, src: src, nsrc: len(b)}
|
||||||
|
rb.setFlusher(nil, cmpNormalBytes)
|
||||||
|
for bp < len(b) {
|
||||||
|
rb.out = b[bp:]
|
||||||
|
if bp = decomposeSegment(&rb, bp, true); bp < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bp, _ = rb.f.quickSpan(rb.src, bp, len(b), true)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmpNormalBytes(rb *reorderBuffer) bool {
|
||||||
|
b := rb.out
|
||||||
|
for i := 0; i < rb.nrune; i++ {
|
||||||
|
info := rb.rune[i]
|
||||||
|
if int(info.size) > len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
p := info.pos
|
||||||
|
pe := p + info.size
|
||||||
|
for ; p < pe; p++ {
|
||||||
|
if b[0] != rb.byte[p] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b = b[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNormalString returns true if s == f(s).
|
||||||
|
func (f Form) IsNormalString(s string) bool {
|
||||||
|
src := inputString(s)
|
||||||
|
ft := formTable[f]
|
||||||
|
bp, ok := ft.quickSpan(src, 0, len(s), true)
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
rb := reorderBuffer{f: *ft, src: src, nsrc: len(s)}
|
||||||
|
rb.setFlusher(nil, func(rb *reorderBuffer) bool {
|
||||||
|
for i := 0; i < rb.nrune; i++ {
|
||||||
|
info := rb.rune[i]
|
||||||
|
if bp+int(info.size) > len(s) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
p := info.pos
|
||||||
|
pe := p + info.size
|
||||||
|
for ; p < pe; p++ {
|
||||||
|
if s[bp] != rb.byte[p] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bp++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
for bp < len(s) {
|
||||||
|
if bp = decomposeSegment(&rb, bp, true); bp < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bp, _ = rb.f.quickSpan(rb.src, bp, len(s), true)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// patchTail fixes a case where a rune may be incorrectly normalized
|
||||||
|
// if it is followed by illegal continuation bytes. It returns the
|
||||||
|
// patched buffer and whether the decomposition is still in progress.
|
||||||
|
func patchTail(rb *reorderBuffer) bool {
|
||||||
|
info, p := lastRuneStart(&rb.f, rb.out)
|
||||||
|
if p == -1 || info.size == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
end := p + int(info.size)
|
||||||
|
extra := len(rb.out) - end
|
||||||
|
if extra > 0 {
|
||||||
|
// Potentially allocating memory. However, this only
|
||||||
|
// happens with ill-formed UTF-8.
|
||||||
|
x := make([]byte, 0)
|
||||||
|
x = append(x, rb.out[len(rb.out)-extra:]...)
|
||||||
|
rb.out = rb.out[:end]
|
||||||
|
decomposeToLastBoundary(rb)
|
||||||
|
rb.doFlush()
|
||||||
|
rb.out = append(rb.out, x...)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
buf := rb.out[p:]
|
||||||
|
rb.out = rb.out[:p]
|
||||||
|
decomposeToLastBoundary(rb)
|
||||||
|
if s := rb.ss.next(info); s == ssStarter {
|
||||||
|
rb.doFlush()
|
||||||
|
rb.ss.first(info)
|
||||||
|
} else if s == ssOverflow {
|
||||||
|
rb.doFlush()
|
||||||
|
rb.insertCGJ()
|
||||||
|
rb.ss = 0
|
||||||
|
}
|
||||||
|
rb.insertUnsafe(inputBytes(buf), 0, info)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendQuick(rb *reorderBuffer, i int) int {
|
||||||
|
if rb.nsrc == i {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
end, _ := rb.f.quickSpan(rb.src, i, rb.nsrc, true)
|
||||||
|
rb.out = rb.src.appendSlice(rb.out, i, end)
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append returns f(append(out, b...)).
|
||||||
|
// The buffer out must be nil, empty, or equal to f(out).
|
||||||
|
func (f Form) Append(out []byte, src ...byte) []byte {
|
||||||
|
return f.doAppend(out, inputBytes(src), len(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Form) doAppend(out []byte, src input, n int) []byte {
|
||||||
|
if n == 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
ft := formTable[f]
|
||||||
|
// Attempt to do a quickSpan first so we can avoid initializing the reorderBuffer.
|
||||||
|
if len(out) == 0 {
|
||||||
|
p, _ := ft.quickSpan(src, 0, n, true)
|
||||||
|
out = src.appendSlice(out, 0, p)
|
||||||
|
if p == n {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
rb := reorderBuffer{f: *ft, src: src, nsrc: n, out: out, flushF: appendFlush}
|
||||||
|
return doAppendInner(&rb, p)
|
||||||
|
}
|
||||||
|
rb := reorderBuffer{f: *ft, src: src, nsrc: n}
|
||||||
|
return doAppend(&rb, out, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doAppend(rb *reorderBuffer, out []byte, p int) []byte {
|
||||||
|
rb.setFlusher(out, appendFlush)
|
||||||
|
src, n := rb.src, rb.nsrc
|
||||||
|
doMerge := len(out) > 0
|
||||||
|
if q := src.skipContinuationBytes(p); q > p {
|
||||||
|
// Move leading non-starters to destination.
|
||||||
|
rb.out = src.appendSlice(rb.out, p, q)
|
||||||
|
p = q
|
||||||
|
doMerge = patchTail(rb)
|
||||||
|
}
|
||||||
|
fd := &rb.f
|
||||||
|
if doMerge {
|
||||||
|
var info Properties
|
||||||
|
if p < n {
|
||||||
|
info = fd.info(src, p)
|
||||||
|
if !info.BoundaryBefore() || info.nLeadingNonStarters() > 0 {
|
||||||
|
if p == 0 {
|
||||||
|
decomposeToLastBoundary(rb)
|
||||||
|
}
|
||||||
|
p = decomposeSegment(rb, p, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.size == 0 {
|
||||||
|
rb.doFlush()
|
||||||
|
// Append incomplete UTF-8 encoding.
|
||||||
|
return src.appendSlice(rb.out, p, n)
|
||||||
|
}
|
||||||
|
if rb.nrune > 0 {
|
||||||
|
return doAppendInner(rb, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = appendQuick(rb, p)
|
||||||
|
return doAppendInner(rb, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doAppendInner(rb *reorderBuffer, p int) []byte {
|
||||||
|
for n := rb.nsrc; p < n; {
|
||||||
|
p = decomposeSegment(rb, p, true)
|
||||||
|
p = appendQuick(rb, p)
|
||||||
|
}
|
||||||
|
return rb.out
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendString returns f(append(out, []byte(s))).
|
||||||
|
// The buffer out must be nil, empty, or equal to f(out).
|
||||||
|
func (f Form) AppendString(out []byte, src string) []byte {
|
||||||
|
return f.doAppend(out, inputString(src), len(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuickSpan returns a boundary n such that b[0:n] == f(b[0:n]).
|
||||||
|
// It is not guaranteed to return the largest such n.
|
||||||
|
func (f Form) QuickSpan(b []byte) int {
|
||||||
|
n, _ := formTable[f].quickSpan(inputBytes(b), 0, len(b), true)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// quickSpan returns a boundary n such that src[0:n] == f(src[0:n]) and
|
||||||
|
// whether any non-normalized parts were found. If atEOF is false, n will
|
||||||
|
// not point past the last segment if this segment might be become
|
||||||
|
// non-normalized by appending other runes.
|
||||||
|
func (f *formInfo) quickSpan(src input, i, end int, atEOF bool) (n int, ok bool) {
|
||||||
|
var lastCC uint8
|
||||||
|
ss := streamSafe(0)
|
||||||
|
lastSegStart := i
|
||||||
|
for n = end; i < n; {
|
||||||
|
if j := src.skipASCII(i, n); i != j {
|
||||||
|
i = j
|
||||||
|
lastSegStart = i - 1
|
||||||
|
lastCC = 0
|
||||||
|
ss = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info := f.info(src, i)
|
||||||
|
if info.size == 0 {
|
||||||
|
if atEOF {
|
||||||
|
// include incomplete runes
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
return lastSegStart, true
|
||||||
|
}
|
||||||
|
// This block needs to be before the next, because it is possible to
|
||||||
|
// have an overflow for runes that are starters (e.g. with U+FF9E).
|
||||||
|
switch ss.next(info) {
|
||||||
|
case ssStarter:
|
||||||
|
ss.first(info)
|
||||||
|
lastSegStart = i
|
||||||
|
case ssOverflow:
|
||||||
|
return lastSegStart, false
|
||||||
|
case ssSuccess:
|
||||||
|
if lastCC > info.ccc {
|
||||||
|
return lastSegStart, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f.composing {
|
||||||
|
if !info.isYesC() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !info.isYesD() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastCC = info.ccc
|
||||||
|
i += int(info.size)
|
||||||
|
}
|
||||||
|
if i == n {
|
||||||
|
if !atEOF {
|
||||||
|
n = lastSegStart
|
||||||
|
}
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
return lastSegStart, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuickSpanString returns a boundary n such that b[0:n] == f(s[0:n]).
|
||||||
|
// It is not guaranteed to return the largest such n.
|
||||||
|
func (f Form) QuickSpanString(s string) int {
|
||||||
|
n, _ := formTable[f].quickSpan(inputString(s), 0, len(s), true)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstBoundary returns the position i of the first boundary in b
|
||||||
|
// or -1 if b contains no boundary.
|
||||||
|
func (f Form) FirstBoundary(b []byte) int {
|
||||||
|
return f.firstBoundary(inputBytes(b), len(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Form) firstBoundary(src input, nsrc int) int {
|
||||||
|
i := src.skipContinuationBytes(0)
|
||||||
|
if i >= nsrc {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
fd := formTable[f]
|
||||||
|
ss := streamSafe(0)
|
||||||
|
// We should call ss.first here, but we can't as the first rune is
|
||||||
|
// skipped already. This means FirstBoundary can't really determine
|
||||||
|
// CGJ insertion points correctly. Luckily it doesn't have to.
|
||||||
|
// TODO: consider adding NextBoundary
|
||||||
|
for {
|
||||||
|
info := fd.info(src, i)
|
||||||
|
if info.size == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if s := ss.next(info); s != ssSuccess {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
i += int(info.size)
|
||||||
|
if i >= nsrc {
|
||||||
|
if !info.BoundaryAfter() && !ss.isMax() {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return nsrc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstBoundaryInString returns the position i of the first boundary in s
|
||||||
|
// or -1 if s contains no boundary.
|
||||||
|
func (f Form) FirstBoundaryInString(s string) int {
|
||||||
|
return f.firstBoundary(inputString(s), len(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastBoundary returns the position i of the last boundary in b
|
||||||
|
// or -1 if b contains no boundary.
|
||||||
|
func (f Form) LastBoundary(b []byte) int {
|
||||||
|
return lastBoundary(formTable[f], b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastBoundary(fd *formInfo, b []byte) int {
|
||||||
|
i := len(b)
|
||||||
|
info, p := lastRuneStart(fd, b)
|
||||||
|
if p == -1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if info.size == 0 { // ends with incomplete rune
|
||||||
|
if p == 0 { // starts with incomplete rune
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
i = p
|
||||||
|
info, p = lastRuneStart(fd, b[:i])
|
||||||
|
if p == -1 { // incomplete UTF-8 encoding or non-starter bytes without a starter
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p+int(info.size) != i { // trailing non-starter bytes: illegal UTF-8
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if info.BoundaryAfter() {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
ss := streamSafe(0)
|
||||||
|
v := ss.backwards(info)
|
||||||
|
for i = p; i >= 0 && v != ssStarter; i = p {
|
||||||
|
info, p = lastRuneStart(fd, b[:i])
|
||||||
|
if v = ss.backwards(info); v == ssOverflow {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if p+int(info.size) != i {
|
||||||
|
if p == -1 { // no boundary found
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return i // boundary after an illegal UTF-8 encoding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// decomposeSegment scans the first segment in src into rb. It inserts 0x034f
|
||||||
|
// (Grapheme Joiner) when it encounters a sequence of more than 30 non-starters
|
||||||
|
// and returns the number of bytes consumed from src or iShortDst or iShortSrc.
|
||||||
|
func decomposeSegment(rb *reorderBuffer, sp int, atEOF bool) int {
|
||||||
|
// Force one character to be consumed.
|
||||||
|
info := rb.f.info(rb.src, sp)
|
||||||
|
if info.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if rb.nrune > 0 {
|
||||||
|
if s := rb.ss.next(info); s == ssStarter {
|
||||||
|
goto end
|
||||||
|
} else if s == ssOverflow {
|
||||||
|
rb.insertCGJ()
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rb.ss.first(info)
|
||||||
|
}
|
||||||
|
if err := rb.insertFlush(rb.src, sp, info); err != iSuccess {
|
||||||
|
return int(err)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
sp += int(info.size)
|
||||||
|
if sp >= rb.nsrc {
|
||||||
|
if !atEOF && !info.BoundaryAfter() {
|
||||||
|
return int(iShortSrc)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
info = rb.f.info(rb.src, sp)
|
||||||
|
if info.size == 0 {
|
||||||
|
if !atEOF {
|
||||||
|
return int(iShortSrc)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s := rb.ss.next(info); s == ssStarter {
|
||||||
|
break
|
||||||
|
} else if s == ssOverflow {
|
||||||
|
rb.insertCGJ()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := rb.insertFlush(rb.src, sp, info); err != iSuccess {
|
||||||
|
return int(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end:
|
||||||
|
if !rb.doFlush() {
|
||||||
|
return int(iShortDst)
|
||||||
|
}
|
||||||
|
return sp
|
||||||
|
}
|
||||||
|
|
||||||
|
// lastRuneStart returns the runeInfo and position of the last
|
||||||
|
// rune in buf or the zero runeInfo and -1 if no rune was found.
|
||||||
|
func lastRuneStart(fd *formInfo, buf []byte) (Properties, int) {
|
||||||
|
p := len(buf) - 1
|
||||||
|
for ; p >= 0 && !utf8.RuneStart(buf[p]); p-- {
|
||||||
|
}
|
||||||
|
if p < 0 {
|
||||||
|
return Properties{}, -1
|
||||||
|
}
|
||||||
|
return fd.info(inputBytes(buf), p), p
|
||||||
|
}
|
||||||
|
|
||||||
|
// decomposeToLastBoundary finds an open segment at the end of the buffer
|
||||||
|
// and scans it into rb. Returns the buffer minus the last segment.
|
||||||
|
func decomposeToLastBoundary(rb *reorderBuffer) {
|
||||||
|
fd := &rb.f
|
||||||
|
info, i := lastRuneStart(fd, rb.out)
|
||||||
|
if int(info.size) != len(rb.out)-i {
|
||||||
|
// illegal trailing continuation bytes
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if info.BoundaryAfter() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var add [maxNonStarters + 1]Properties // stores runeInfo in reverse order
|
||||||
|
padd := 0
|
||||||
|
ss := streamSafe(0)
|
||||||
|
p := len(rb.out)
|
||||||
|
for {
|
||||||
|
add[padd] = info
|
||||||
|
v := ss.backwards(info)
|
||||||
|
if v == ssOverflow {
|
||||||
|
// Note that if we have an overflow, it the string we are appending to
|
||||||
|
// is not correctly normalized. In this case the behavior is undefined.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
padd++
|
||||||
|
p -= int(info.size)
|
||||||
|
if v == ssStarter || p < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
info, i = lastRuneStart(fd, rb.out[:p])
|
||||||
|
if int(info.size) != p-i {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rb.ss = ss
|
||||||
|
// Copy bytes for insertion as we may need to overwrite rb.out.
|
||||||
|
var buf [maxBufferSize * utf8.UTFMax]byte
|
||||||
|
cp := buf[:copy(buf[:], rb.out[p:])]
|
||||||
|
rb.out = rb.out[:p]
|
||||||
|
for padd--; padd >= 0; padd-- {
|
||||||
|
info = add[padd]
|
||||||
|
rb.insertUnsafe(inputBytes(cp), 0, info)
|
||||||
|
cp = cp[info.size:]
|
||||||
|
}
|
||||||
|
}
|
318
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/normregtest.go
generated
vendored
Normal file
318
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/normregtest.go
generated
vendored
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"code.google.com/p/go.text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
loadTestData()
|
||||||
|
CharacterByCharacterTests()
|
||||||
|
StandardTests()
|
||||||
|
PerformanceTest()
|
||||||
|
if errorCount == 0 {
|
||||||
|
fmt.Println("PASS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = "NormalizationTest.txt"
|
||||||
|
|
||||||
|
var url = flag.String("url",
|
||||||
|
"http://www.unicode.org/Public/"+unicode.Version+"/ucd/"+file,
|
||||||
|
"URL of Unicode database directory")
|
||||||
|
var localFiles = flag.Bool("local",
|
||||||
|
false,
|
||||||
|
"data files have been copied to the current directory; for debugging only")
|
||||||
|
|
||||||
|
var logger = log.New(os.Stderr, "", log.Lshortfile)
|
||||||
|
|
||||||
|
// This regression test runs the test set in NormalizationTest.txt
|
||||||
|
// (taken from http://www.unicode.org/Public/<unicode.Version>/ucd/).
|
||||||
|
//
|
||||||
|
// NormalizationTest.txt has form:
|
||||||
|
// @Part0 # Specific cases
|
||||||
|
// #
|
||||||
|
// 1E0A;1E0A;0044 0307;1E0A;0044 0307; # (Ḋ; Ḋ; D◌̇; Ḋ; D◌̇; ) LATIN CAPITAL LETTER D WITH DOT ABOVE
|
||||||
|
// 1E0C;1E0C;0044 0323;1E0C;0044 0323; # (Ḍ; Ḍ; D◌̣; Ḍ; D◌̣; ) LATIN CAPITAL LETTER D WITH DOT BELOW
|
||||||
|
//
|
||||||
|
// Each test has 5 columns (c1, c2, c3, c4, c5), where
|
||||||
|
// (c1, c2, c3, c4, c5) == (c1, NFC(c1), NFD(c1), NFKC(c1), NFKD(c1))
|
||||||
|
//
|
||||||
|
// CONFORMANCE:
|
||||||
|
// 1. The following invariants must be true for all conformant implementations
|
||||||
|
//
|
||||||
|
// NFC
|
||||||
|
// c2 == NFC(c1) == NFC(c2) == NFC(c3)
|
||||||
|
// c4 == NFC(c4) == NFC(c5)
|
||||||
|
//
|
||||||
|
// NFD
|
||||||
|
// c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
||||||
|
// c5 == NFD(c4) == NFD(c5)
|
||||||
|
//
|
||||||
|
// NFKC
|
||||||
|
// c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
|
||||||
|
//
|
||||||
|
// NFKD
|
||||||
|
// c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
|
||||||
|
//
|
||||||
|
// 2. For every code point X assigned in this version of Unicode that is not
|
||||||
|
// specifically listed in Part 1, the following invariants must be true
|
||||||
|
// for all conformant implementations:
|
||||||
|
//
|
||||||
|
// X == NFC(X) == NFD(X) == NFKC(X) == NFKD(X)
|
||||||
|
//
|
||||||
|
|
||||||
|
// Column types.
|
||||||
|
const (
|
||||||
|
cRaw = iota
|
||||||
|
cNFC
|
||||||
|
cNFD
|
||||||
|
cNFKC
|
||||||
|
cNFKD
|
||||||
|
cMaxColumns
|
||||||
|
)
|
||||||
|
|
||||||
|
// Holds data from NormalizationTest.txt
|
||||||
|
var part []Part
|
||||||
|
|
||||||
|
type Part struct {
|
||||||
|
name string
|
||||||
|
number int
|
||||||
|
tests []Test
|
||||||
|
}
|
||||||
|
|
||||||
|
type Test struct {
|
||||||
|
name string
|
||||||
|
partnr int
|
||||||
|
number int
|
||||||
|
r rune // used for character by character test
|
||||||
|
cols [cMaxColumns]string // Each has 5 entries, see below.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Test) Name() string {
|
||||||
|
if t.number < 0 {
|
||||||
|
return part[t.partnr].name
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d", part[t.partnr].name, t.number)
|
||||||
|
}
|
||||||
|
|
||||||
|
var partRe = regexp.MustCompile(`@Part(\d) # (.*)$`)
|
||||||
|
var testRe = regexp.MustCompile(`^` + strings.Repeat(`([\dA-F ]+);`, 5) + ` # (.*)$`)
|
||||||
|
|
||||||
|
var counter int
|
||||||
|
|
||||||
|
// Load the data form NormalizationTest.txt
|
||||||
|
func loadTestData() {
|
||||||
|
if *localFiles {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
*url = "file://" + path.Join(pwd, file)
|
||||||
|
}
|
||||||
|
t := &http.Transport{}
|
||||||
|
t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
|
||||||
|
c := &http.Client{Transport: t}
|
||||||
|
resp, err := c.Get(*url)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
logger.Fatal("bad GET status for "+file, resp.Status)
|
||||||
|
}
|
||||||
|
f := resp.Body
|
||||||
|
defer f.Close()
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m := partRe.FindStringSubmatch(line)
|
||||||
|
if m != nil {
|
||||||
|
if len(m) < 3 {
|
||||||
|
logger.Fatal("Failed to parse Part: ", line)
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(m[1])
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(err)
|
||||||
|
}
|
||||||
|
name := m[2]
|
||||||
|
part = append(part, Part{name: name[:len(name)-1], number: i})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m = testRe.FindStringSubmatch(line)
|
||||||
|
if m == nil || len(m) < 7 {
|
||||||
|
logger.Fatalf(`Failed to parse: "%s" result: %#v`, line, m)
|
||||||
|
}
|
||||||
|
test := Test{name: m[6], partnr: len(part) - 1, number: counter}
|
||||||
|
counter++
|
||||||
|
for j := 1; j < len(m)-1; j++ {
|
||||||
|
for _, split := range strings.Split(m[j], " ") {
|
||||||
|
r, err := strconv.ParseUint(split, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(err)
|
||||||
|
}
|
||||||
|
if test.r == 0 {
|
||||||
|
// save for CharacterByCharacterTests
|
||||||
|
test.r = rune(r)
|
||||||
|
}
|
||||||
|
var buf [utf8.UTFMax]byte
|
||||||
|
sz := utf8.EncodeRune(buf[:], rune(r))
|
||||||
|
test.cols[j-1] += string(buf[:sz])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
part := &part[len(part)-1]
|
||||||
|
part.tests = append(part.tests, test)
|
||||||
|
}
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
logger.Fatal(scanner.Err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fstr = []string{"NFC", "NFD", "NFKC", "NFKD"}
|
||||||
|
|
||||||
|
var errorCount int
|
||||||
|
|
||||||
|
func cmpResult(t *Test, name string, f norm.Form, gold, test, result string) {
|
||||||
|
if gold != result {
|
||||||
|
errorCount++
|
||||||
|
if errorCount > 20 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Printf("%s:%s: %s(%+q)=%+q; want %+q: %s",
|
||||||
|
t.Name(), name, fstr[f], test, result, gold, t.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmpIsNormal(t *Test, name string, f norm.Form, test string, result, want bool) {
|
||||||
|
if result != want {
|
||||||
|
errorCount++
|
||||||
|
if errorCount > 20 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Printf("%s:%s: %s(%+q)=%v; want %v", t.Name(), name, fstr[f], test, result, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTest(t *Test, f norm.Form, gold, test string) {
|
||||||
|
testb := []byte(test)
|
||||||
|
result := f.Bytes(testb)
|
||||||
|
cmpResult(t, "Bytes", f, gold, test, string(result))
|
||||||
|
|
||||||
|
sresult := f.String(test)
|
||||||
|
cmpResult(t, "String", f, gold, test, sresult)
|
||||||
|
|
||||||
|
acc := []byte{}
|
||||||
|
i := norm.Iter{}
|
||||||
|
i.InitString(f, test)
|
||||||
|
for !i.Done() {
|
||||||
|
acc = append(acc, i.Next()...)
|
||||||
|
}
|
||||||
|
cmpResult(t, "Iter.Next", f, gold, test, string(acc))
|
||||||
|
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
acc = nil
|
||||||
|
for p := 0; p < len(testb); {
|
||||||
|
nDst, nSrc, _ := f.Transform(buf, testb[p:], true)
|
||||||
|
acc = append(acc, buf[:nDst]...)
|
||||||
|
p += nSrc
|
||||||
|
}
|
||||||
|
cmpResult(t, "Transform", f, gold, test, string(acc))
|
||||||
|
|
||||||
|
for i := range test {
|
||||||
|
out := f.Append(f.Bytes([]byte(test[:i])), []byte(test[i:])...)
|
||||||
|
cmpResult(t, fmt.Sprintf(":Append:%d", i), f, gold, test, string(out))
|
||||||
|
}
|
||||||
|
cmpIsNormal(t, "IsNormal", f, test, f.IsNormal([]byte(test)), test == gold)
|
||||||
|
cmpIsNormal(t, "IsNormalString", f, test, f.IsNormalString(test), test == gold)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doConformanceTests(t *Test, partn int) {
|
||||||
|
for i := 0; i <= 2; i++ {
|
||||||
|
doTest(t, norm.NFC, t.cols[1], t.cols[i])
|
||||||
|
doTest(t, norm.NFD, t.cols[2], t.cols[i])
|
||||||
|
doTest(t, norm.NFKC, t.cols[3], t.cols[i])
|
||||||
|
doTest(t, norm.NFKD, t.cols[4], t.cols[i])
|
||||||
|
}
|
||||||
|
for i := 3; i <= 4; i++ {
|
||||||
|
doTest(t, norm.NFC, t.cols[3], t.cols[i])
|
||||||
|
doTest(t, norm.NFD, t.cols[4], t.cols[i])
|
||||||
|
doTest(t, norm.NFKC, t.cols[3], t.cols[i])
|
||||||
|
doTest(t, norm.NFKD, t.cols[4], t.cols[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CharacterByCharacterTests() {
|
||||||
|
tests := part[1].tests
|
||||||
|
var last rune = 0
|
||||||
|
for i := 0; i <= len(tests); i++ { // last one is special case
|
||||||
|
var r rune
|
||||||
|
if i == len(tests) {
|
||||||
|
r = 0x2FA1E // Don't have to go to 0x10FFFF
|
||||||
|
} else {
|
||||||
|
r = tests[i].r
|
||||||
|
}
|
||||||
|
for last++; last < r; last++ {
|
||||||
|
// Check all characters that were not explicitly listed in the test.
|
||||||
|
t := &Test{partnr: 1, number: -1}
|
||||||
|
char := string(last)
|
||||||
|
doTest(t, norm.NFC, char, char)
|
||||||
|
doTest(t, norm.NFD, char, char)
|
||||||
|
doTest(t, norm.NFKC, char, char)
|
||||||
|
doTest(t, norm.NFKD, char, char)
|
||||||
|
}
|
||||||
|
if i < len(tests) {
|
||||||
|
doConformanceTests(&tests[i], 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StandardTests() {
|
||||||
|
for _, j := range []int{0, 2, 3} {
|
||||||
|
for _, test := range part[j].tests {
|
||||||
|
doConformanceTests(&test, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceTest verifies that normalization is O(n). If any of the
|
||||||
|
// code does not properly check for maxCombiningChars, normalization
|
||||||
|
// may exhibit O(n**2) behavior.
|
||||||
|
func PerformanceTest() {
|
||||||
|
runtime.GOMAXPROCS(2)
|
||||||
|
success := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
buf := bytes.Repeat([]byte("\u035D"), 1024*1024)
|
||||||
|
buf = append(buf, "\u035B"...)
|
||||||
|
norm.NFC.Append(nil, buf...)
|
||||||
|
success <- true
|
||||||
|
}()
|
||||||
|
timeout := time.After(1 * time.Second)
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
|
// test completed before the timeout
|
||||||
|
case <-timeout:
|
||||||
|
errorCount++
|
||||||
|
logger.Printf(`unexpectedly long time to complete PerformanceTest`)
|
||||||
|
}
|
||||||
|
}
|
126
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/readwriter.go
generated
vendored
Normal file
126
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/readwriter.go
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package norm
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type normWriter struct {
|
||||||
|
rb reorderBuffer
|
||||||
|
w io.Writer
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the standard write interface. If the last characters are
|
||||||
|
// not at a normalization boundary, the bytes will be buffered for the next
|
||||||
|
// write. The remaining bytes will be written on close.
|
||||||
|
func (w *normWriter) Write(data []byte) (n int, err error) {
|
||||||
|
// Process data in pieces to keep w.buf size bounded.
|
||||||
|
const chunk = 4000
|
||||||
|
|
||||||
|
for len(data) > 0 {
|
||||||
|
// Normalize into w.buf.
|
||||||
|
m := len(data)
|
||||||
|
if m > chunk {
|
||||||
|
m = chunk
|
||||||
|
}
|
||||||
|
w.rb.src = inputBytes(data[:m])
|
||||||
|
w.rb.nsrc = m
|
||||||
|
w.buf = doAppend(&w.rb, w.buf, 0)
|
||||||
|
data = data[m:]
|
||||||
|
n += m
|
||||||
|
|
||||||
|
// Write out complete prefix, save remainder.
|
||||||
|
// Note that lastBoundary looks back at most 31 runes.
|
||||||
|
i := lastBoundary(&w.rb.f, w.buf)
|
||||||
|
if i == -1 {
|
||||||
|
i = 0
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
if _, err = w.w.Write(w.buf[:i]); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
bn := copy(w.buf, w.buf[i:])
|
||||||
|
w.buf = w.buf[:bn]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close forces data that remains in the buffer to be written.
|
||||||
|
func (w *normWriter) Close() error {
|
||||||
|
if len(w.buf) > 0 {
|
||||||
|
_, err := w.w.Write(w.buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer returns a new writer that implements Write(b)
|
||||||
|
// by writing f(b) to w. The returned writer may use an
|
||||||
|
// an internal buffer to maintain state across Write calls.
|
||||||
|
// Calling its Close method writes any buffered data to w.
|
||||||
|
func (f Form) Writer(w io.Writer) io.WriteCloser {
|
||||||
|
wr := &normWriter{rb: reorderBuffer{}, w: w}
|
||||||
|
wr.rb.init(f, nil)
|
||||||
|
return wr
|
||||||
|
}
|
||||||
|
|
||||||
|
type normReader struct {
|
||||||
|
rb reorderBuffer
|
||||||
|
r io.Reader
|
||||||
|
inbuf []byte
|
||||||
|
outbuf []byte
|
||||||
|
bufStart int
|
||||||
|
lastBoundary int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements the standard read interface.
|
||||||
|
func (r *normReader) Read(p []byte) (int, error) {
|
||||||
|
for {
|
||||||
|
if r.lastBoundary-r.bufStart > 0 {
|
||||||
|
n := copy(p, r.outbuf[r.bufStart:r.lastBoundary])
|
||||||
|
r.bufStart += n
|
||||||
|
if r.lastBoundary-r.bufStart > 0 {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
return n, r.err
|
||||||
|
}
|
||||||
|
if r.err != nil {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
outn := copy(r.outbuf, r.outbuf[r.lastBoundary:])
|
||||||
|
r.outbuf = r.outbuf[0:outn]
|
||||||
|
r.bufStart = 0
|
||||||
|
|
||||||
|
n, err := r.r.Read(r.inbuf)
|
||||||
|
r.rb.src = inputBytes(r.inbuf[0:n])
|
||||||
|
r.rb.nsrc, r.err = n, err
|
||||||
|
if n > 0 {
|
||||||
|
r.outbuf = doAppend(&r.rb, r.outbuf, 0)
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
r.lastBoundary = len(r.outbuf)
|
||||||
|
} else {
|
||||||
|
r.lastBoundary = lastBoundary(&r.rb.f, r.outbuf)
|
||||||
|
if r.lastBoundary == -1 {
|
||||||
|
r.lastBoundary = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("should not reach here")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader returns a new reader that implements Read
|
||||||
|
// by reading data from r and returning f(data).
|
||||||
|
func (f Form) Reader(r io.Reader) io.Reader {
|
||||||
|
const chunk = 4000
|
||||||
|
buf := make([]byte, chunk)
|
||||||
|
rr := &normReader{rb: reorderBuffer{}, r: r, inbuf: buf}
|
||||||
|
rr.rb.init(f, buf)
|
||||||
|
return rr
|
||||||
|
}
|
7549
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/tables.go
generated
vendored
Normal file
7549
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/tables.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
88
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/transform.go
generated
vendored
Normal file
88
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/transform.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package norm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"code.google.com/p/go.text/transform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reset implements the Reset method of the transform.Transformer interface.
|
||||||
|
func (Form) Reset() {}
|
||||||
|
|
||||||
|
// Transform implements the Transform method of the transform.Transformer
|
||||||
|
// interface. It may need to write segments of up to MaxSegmentSize at once.
|
||||||
|
// Users should either catch ErrShortDst and allow dst to grow or have dst be at
|
||||||
|
// least of size MaxTransformChunkSize to be guaranteed of progress.
|
||||||
|
func (f Form) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
|
n := 0
|
||||||
|
// Cap the maximum number of src bytes to check.
|
||||||
|
b := src
|
||||||
|
eof := atEOF
|
||||||
|
if ns := len(dst); ns < len(b) {
|
||||||
|
err = transform.ErrShortDst
|
||||||
|
eof = false
|
||||||
|
b = b[:ns]
|
||||||
|
}
|
||||||
|
i, ok := formTable[f].quickSpan(inputBytes(b), n, len(b), eof)
|
||||||
|
n += copy(dst[n:], b[n:i])
|
||||||
|
if !ok {
|
||||||
|
nDst, nSrc, err = f.transform(dst[n:], src[n:], atEOF)
|
||||||
|
return nDst + n, nSrc + n, err
|
||||||
|
}
|
||||||
|
if n < len(src) && !atEOF {
|
||||||
|
err = transform.ErrShortSrc
|
||||||
|
}
|
||||||
|
return n, n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func flushTransform(rb *reorderBuffer) bool {
|
||||||
|
// Write out (must fully fit in dst, or else it is a ErrShortDst).
|
||||||
|
if len(rb.out) < rb.nrune*utf8.UTFMax {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rb.out = rb.out[rb.flushCopy(rb.out):]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs = []error{nil, transform.ErrShortDst, transform.ErrShortSrc}
|
||||||
|
|
||||||
|
// transform implements the transform.Transformer interface. It is only called
|
||||||
|
// when quickSpan does not pass for a given string.
|
||||||
|
func (f Form) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
|
// TODO: get rid of reorderBuffer. See CL 23460044.
|
||||||
|
rb := reorderBuffer{}
|
||||||
|
rb.init(f, src)
|
||||||
|
for {
|
||||||
|
// Load segment into reorder buffer.
|
||||||
|
rb.setFlusher(dst[nDst:], flushTransform)
|
||||||
|
end := decomposeSegment(&rb, nSrc, atEOF)
|
||||||
|
if end < 0 {
|
||||||
|
return nDst, nSrc, errs[-end]
|
||||||
|
}
|
||||||
|
nDst = len(dst) - len(rb.out)
|
||||||
|
nSrc = end
|
||||||
|
|
||||||
|
// Next quickSpan.
|
||||||
|
end = rb.nsrc
|
||||||
|
eof := atEOF
|
||||||
|
if n := nSrc + len(dst) - nDst; n < end {
|
||||||
|
err = transform.ErrShortDst
|
||||||
|
end = n
|
||||||
|
eof = false
|
||||||
|
}
|
||||||
|
end, ok := rb.f.quickSpan(rb.src, nSrc, end, eof)
|
||||||
|
n := copy(dst[nDst:], rb.src.bytes[nSrc:end])
|
||||||
|
nSrc += n
|
||||||
|
nDst += n
|
||||||
|
if ok {
|
||||||
|
if n < rb.nsrc && !atEOF {
|
||||||
|
err = transform.ErrShortSrc
|
||||||
|
}
|
||||||
|
return nDst, nSrc, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/trie.go
generated
vendored
Normal file
54
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/trie.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package norm
|
||||||
|
|
||||||
|
type valueRange struct {
|
||||||
|
value uint16 // header: value:stride
|
||||||
|
lo, hi byte // header: lo:n
|
||||||
|
}
|
||||||
|
|
||||||
|
type sparseBlocks struct {
|
||||||
|
values []valueRange
|
||||||
|
offset []uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var nfcSparse = sparseBlocks{
|
||||||
|
values: nfcSparseValues[:],
|
||||||
|
offset: nfcSparseOffset[:],
|
||||||
|
}
|
||||||
|
|
||||||
|
var nfkcSparse = sparseBlocks{
|
||||||
|
values: nfkcSparseValues[:],
|
||||||
|
offset: nfkcSparseOffset[:],
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
nfcData = newNfcTrie(0)
|
||||||
|
nfkcData = newNfkcTrie(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// lookupValue determines the type of block n and looks up the value for b.
|
||||||
|
// For n < t.cutoff, the block is a simple lookup table. Otherwise, the block
|
||||||
|
// is a list of ranges with an accompanying value. Given a matching range r,
|
||||||
|
// the value for b is by r.value + (b - r.lo) * stride.
|
||||||
|
func (t *sparseBlocks) lookup(n uint32, b byte) uint16 {
|
||||||
|
offset := t.offset[n]
|
||||||
|
header := t.values[offset]
|
||||||
|
lo := offset + 1
|
||||||
|
hi := lo + uint16(header.lo)
|
||||||
|
for lo < hi {
|
||||||
|
m := lo + (hi-lo)/2
|
||||||
|
r := t.values[m]
|
||||||
|
if r.lo <= b && b <= r.hi {
|
||||||
|
return r.value + uint16(b-r.lo)*header.value
|
||||||
|
}
|
||||||
|
if b < r.lo {
|
||||||
|
hi = m
|
||||||
|
} else {
|
||||||
|
lo = m + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
117
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/triegen.go
generated
vendored
Normal file
117
Godeps/_workspace/src/code.google.com/p/go.text/unicode/norm/triegen.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// Trie table generator.
|
||||||
|
// Used by make*tables tools to generate a go file with trie data structures
|
||||||
|
// for mapping UTF-8 to a 16-bit value. All but the last byte in a UTF-8 byte
|
||||||
|
// sequence are used to lookup offsets in the index table to be used for the
|
||||||
|
// next byte. The last byte is used to index into a table with 16-bit values.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxSparseEntries = 16
|
||||||
|
|
||||||
|
type normCompacter struct {
|
||||||
|
sparseBlocks [][]uint64
|
||||||
|
sparseOffset []uint16
|
||||||
|
sparseCount int
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func mostFrequentStride(a []uint64) int {
|
||||||
|
counts := make(map[int]int)
|
||||||
|
var v int
|
||||||
|
for _, x := range a {
|
||||||
|
if stride := int(x) - v; v != 0 && stride >= 0 {
|
||||||
|
counts[stride]++
|
||||||
|
}
|
||||||
|
v = int(x)
|
||||||
|
}
|
||||||
|
var maxs, maxc int
|
||||||
|
for stride, cnt := range counts {
|
||||||
|
if cnt > maxc || (cnt == maxc && stride < maxs) {
|
||||||
|
maxs, maxc = stride, cnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxs
|
||||||
|
}
|
||||||
|
|
||||||
|
func countSparseEntries(a []uint64) int {
|
||||||
|
stride := mostFrequentStride(a)
|
||||||
|
var v, count int
|
||||||
|
for _, tv := range a {
|
||||||
|
if int(tv)-v != stride {
|
||||||
|
if tv != 0 {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = int(tv)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *normCompacter) Size(v []uint64) (sz int, ok bool) {
|
||||||
|
if n := countSparseEntries(v); n <= maxSparseEntries {
|
||||||
|
return (n+1)*4 + 2, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *normCompacter) Store(v []uint64) uint32 {
|
||||||
|
h := uint32(len(c.sparseOffset))
|
||||||
|
c.sparseBlocks = append(c.sparseBlocks, v)
|
||||||
|
c.sparseOffset = append(c.sparseOffset, uint16(c.sparseCount))
|
||||||
|
c.sparseCount += countSparseEntries(v) + 1
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *normCompacter) Handler() string {
|
||||||
|
return c.name + "Sparse.lookup"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *normCompacter) Print(w io.Writer) (retErr error) {
|
||||||
|
p := func(f string, x ...interface{}) {
|
||||||
|
if _, err := fmt.Fprintf(w, f, x...); retErr == nil && err != nil {
|
||||||
|
retErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ls := len(c.sparseBlocks)
|
||||||
|
p("// %sSparseOffset: %d entries, %d bytes\n", c.name, ls, ls*2)
|
||||||
|
p("var %sSparseOffset = %#v\n\n", c.name, c.sparseOffset)
|
||||||
|
|
||||||
|
ns := c.sparseCount
|
||||||
|
p("// %sSparseValues: %d entries, %d bytes\n", c.name, ns, ns*4)
|
||||||
|
p("var %sSparseValues = [%d]valueRange {", c.name, ns)
|
||||||
|
for i, b := range c.sparseBlocks {
|
||||||
|
p("\n// Block %#x, offset %#x", i, c.sparseOffset[i])
|
||||||
|
var v int
|
||||||
|
stride := mostFrequentStride(b)
|
||||||
|
n := countSparseEntries(b)
|
||||||
|
p("\n{value:%#04x,lo:%#02x},", stride, uint8(n))
|
||||||
|
for i, nv := range b {
|
||||||
|
if int(nv)-v != stride {
|
||||||
|
if v != 0 {
|
||||||
|
p(",hi:%#02x},", 0x80+i-1)
|
||||||
|
}
|
||||||
|
if nv != 0 {
|
||||||
|
p("\n{value:%#04x,lo:%#02x", nv, 0x80+i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = int(nv)
|
||||||
|
}
|
||||||
|
if v != 0 {
|
||||||
|
p(",hi:%#02x},", 0x80+len(b)-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p("\n}\n\n")
|
||||||
|
return
|
||||||
|
}
|
105
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/error.go
generated
vendored
Normal file
105
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/error.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Package awserr represents API error interface accessors for the SDK.
|
||||||
|
package awserr
|
||||||
|
|
||||||
|
// An Error wraps lower level errors with code, message and an original error.
|
||||||
|
// The underlying concrete error type may also satisfy other interfaces which
|
||||||
|
// can be to used to obtain more specific information about the error.
|
||||||
|
//
|
||||||
|
// Calling Error() or String() will always include the full information about
|
||||||
|
// an error based on its underlying type.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// output, err := s3manage.Upload(svc, input, opts)
|
||||||
|
// if err != nil {
|
||||||
|
// if awsErr, ok := err.(awserr.Error); ok {
|
||||||
|
// // Get error details
|
||||||
|
// log.Println("Error:", err.Code(), err.Message())
|
||||||
|
//
|
||||||
|
// Prints out full error message, including original error if there was one.
|
||||||
|
// log.Println("Error:", err.Error())
|
||||||
|
//
|
||||||
|
// // Get original error
|
||||||
|
// if origErr := err.Err(); origErr != nil {
|
||||||
|
// // operate on original error.
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// fmt.Println(err.Error())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type Error interface {
|
||||||
|
// Satisfy the generic error interface.
|
||||||
|
error
|
||||||
|
|
||||||
|
// Returns the short phrase depicting the classification of the error.
|
||||||
|
Code() string
|
||||||
|
|
||||||
|
// Returns the error details message.
|
||||||
|
Message() string
|
||||||
|
|
||||||
|
// Returns the original error if one was set. Nil is returned if not set.
|
||||||
|
OrigErr() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an Error object described by the code, message, and origErr.
|
||||||
|
//
|
||||||
|
// If origErr satisfies the Error interface it will not be wrapped within a new
|
||||||
|
// Error object and will instead be returned.
|
||||||
|
func New(code, message string, origErr error) Error {
|
||||||
|
if e, ok := origErr.(Error); ok && e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return newBaseError(code, message, origErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A RequestFailure is an interface to extract request failure information from
|
||||||
|
// an Error such as the request ID of the failed request returned by a service.
|
||||||
|
// RequestFailures may not always have a requestID value if the request failed
|
||||||
|
// prior to reaching the service such as a connection error.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// output, err := s3manage.Upload(svc, input, opts)
|
||||||
|
// if err != nil {
|
||||||
|
// if reqerr, ok := err.(RequestFailure); ok {
|
||||||
|
// log.Printf("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID())
|
||||||
|
// } else {
|
||||||
|
// log.Printf("Error:", err.Error()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Combined with awserr.Error:
|
||||||
|
//
|
||||||
|
// output, err := s3manage.Upload(svc, input, opts)
|
||||||
|
// if err != nil {
|
||||||
|
// if awsErr, ok := err.(awserr.Error); ok {
|
||||||
|
// // Generic AWS Error with Code, Message, and original error (if any)
|
||||||
|
// fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
|
||||||
|
//
|
||||||
|
// if reqErr, ok := err.(awserr.RequestFailure); ok {
|
||||||
|
// // A service error occurred
|
||||||
|
// fmt.Println(reqErr.StatusCode(), reqErr.RequestID())
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// fmt.Println(err.Error())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type RequestFailure interface {
|
||||||
|
Error
|
||||||
|
|
||||||
|
// The status code of the HTTP response.
|
||||||
|
StatusCode() int
|
||||||
|
|
||||||
|
// The request ID returned by the service for a request failure. This will
|
||||||
|
// be empty if no request ID is available such as the request failed due
|
||||||
|
// to a connection error.
|
||||||
|
RequestID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequestFailure returns a new request error wrapper for the given Error
|
||||||
|
// provided.
|
||||||
|
func NewRequestFailure(err Error, statusCode int, reqID string) RequestFailure {
|
||||||
|
return newRequestError(err, statusCode, reqID)
|
||||||
|
}
|
135
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/types.go
generated
vendored
Normal file
135
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/types.go
generated
vendored
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package awserr
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// SprintError returns a string of the formatted error code.
|
||||||
|
//
|
||||||
|
// Both extra and origErr are optional. If they are included their lines
|
||||||
|
// will be added, but if they are not included their lines will be ignored.
|
||||||
|
func SprintError(code, message, extra string, origErr error) string {
|
||||||
|
msg := fmt.Sprintf("%s: %s", code, message)
|
||||||
|
if extra != "" {
|
||||||
|
msg = fmt.Sprintf("%s\n\t%s", msg, extra)
|
||||||
|
}
|
||||||
|
if origErr != nil {
|
||||||
|
msg = fmt.Sprintf("%s\ncaused by: %s", msg, origErr.Error())
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// A baseError wraps the code and message which defines an error. It also
|
||||||
|
// can be used to wrap an original error object.
|
||||||
|
//
|
||||||
|
// Should be used as the root for errors satisfying the awserr.Error. Also
|
||||||
|
// for any error which does not fit into a specific error wrapper type.
|
||||||
|
type baseError struct {
|
||||||
|
// Classification of error
|
||||||
|
code string
|
||||||
|
|
||||||
|
// Detailed information about error
|
||||||
|
message string
|
||||||
|
|
||||||
|
// Optional original error this error is based off of. Allows building
|
||||||
|
// chained errors.
|
||||||
|
origErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBaseError returns an error object for the code, message, and err.
|
||||||
|
//
|
||||||
|
// code is a short no whitespace phrase depicting the classification of
|
||||||
|
// the error that is being created.
|
||||||
|
//
|
||||||
|
// message is the free flow string containing detailed information about the error.
|
||||||
|
//
|
||||||
|
// origErr is the error object which will be nested under the new error to be returned.
|
||||||
|
func newBaseError(code, message string, origErr error) *baseError {
|
||||||
|
return &baseError{
|
||||||
|
code: code,
|
||||||
|
message: message,
|
||||||
|
origErr: origErr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the string representation of the error.
|
||||||
|
//
|
||||||
|
// See ErrorWithExtra for formatting.
|
||||||
|
//
|
||||||
|
// Satisfies the error interface.
|
||||||
|
func (b baseError) Error() string {
|
||||||
|
return SprintError(b.code, b.message, "", b.origErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the error.
|
||||||
|
// Alias for Error to satisfy the stringer interface.
|
||||||
|
func (b baseError) String() string {
|
||||||
|
return b.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code returns the short phrase depicting the classification of the error.
|
||||||
|
func (b baseError) Code() string {
|
||||||
|
return b.code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message returns the error details message.
|
||||||
|
func (b baseError) Message() string {
|
||||||
|
return b.message
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrigErr returns the original error if one was set. Nil is returned if no error
|
||||||
|
// was set.
|
||||||
|
func (b baseError) OrigErr() error {
|
||||||
|
return b.origErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// So that the Error interface type can be included as an anonymous field
|
||||||
|
// in the requestError struct and not conflict with the error.Error() method.
|
||||||
|
type awsError Error
|
||||||
|
|
||||||
|
// A requestError wraps a request or service error.
|
||||||
|
//
|
||||||
|
// Composed of baseError for code, message, and original error.
|
||||||
|
type requestError struct {
|
||||||
|
awsError
|
||||||
|
statusCode int
|
||||||
|
requestID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRequestError returns a wrapped error with additional information for request
|
||||||
|
// status code, and service requestID.
|
||||||
|
//
|
||||||
|
// Should be used to wrap all request which involve service requests. Even if
|
||||||
|
// the request failed without a service response, but had an HTTP status code
|
||||||
|
// that may be meaningful.
|
||||||
|
//
|
||||||
|
// Also wraps original errors via the baseError.
|
||||||
|
func newRequestError(err Error, statusCode int, requestID string) *requestError {
|
||||||
|
return &requestError{
|
||||||
|
awsError: err,
|
||||||
|
statusCode: statusCode,
|
||||||
|
requestID: requestID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the string representation of the error.
|
||||||
|
// Satisfies the error interface.
|
||||||
|
func (r requestError) Error() string {
|
||||||
|
extra := fmt.Sprintf("status code: %d, request id: [%s]",
|
||||||
|
r.statusCode, r.requestID)
|
||||||
|
return SprintError(r.Code(), r.Message(), extra, r.OrigErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the error.
|
||||||
|
// Alias for Error to satisfy the stringer interface.
|
||||||
|
func (r requestError) String() string {
|
||||||
|
return r.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusCode returns the wrapped status code for the error
|
||||||
|
func (r requestError) StatusCode() int {
|
||||||
|
return r.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestID returns the wrapped requestID
|
||||||
|
func (r requestError) RequestID() string {
|
||||||
|
return r.requestID
|
||||||
|
}
|
95
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package awsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy deeply copies a src structure to dst. Useful for copying request and
|
||||||
|
// response structures.
|
||||||
|
//
|
||||||
|
// Can copy between structs of different type, but will only copy fields which
|
||||||
|
// are assignable, and exist in both structs. Fields which are not assignable,
|
||||||
|
// or do not exist in both structs are ignored.
|
||||||
|
func Copy(dst, src interface{}) {
|
||||||
|
dstval := reflect.ValueOf(dst)
|
||||||
|
if !dstval.IsValid() {
|
||||||
|
panic("Copy dst cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
rcopy(dstval, reflect.ValueOf(src), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyOf returns a copy of src while also allocating the memory for dst.
|
||||||
|
// src must be a pointer type or this operation will fail.
|
||||||
|
func CopyOf(src interface{}) (dst interface{}) {
|
||||||
|
dsti := reflect.New(reflect.TypeOf(src).Elem())
|
||||||
|
dst = dsti.Interface()
|
||||||
|
rcopy(dsti, reflect.ValueOf(src), true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// rcopy performs a recursive copy of values from the source to destination.
|
||||||
|
//
|
||||||
|
// root is used to skip certain aspects of the copy which are not valid
|
||||||
|
// for the root node of a object.
|
||||||
|
func rcopy(dst, src reflect.Value, root bool) {
|
||||||
|
if !src.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch src.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if _, ok := src.Interface().(io.Reader); ok {
|
||||||
|
if dst.Kind() == reflect.Ptr && dst.Elem().CanSet() {
|
||||||
|
dst.Elem().Set(src)
|
||||||
|
} else if dst.CanSet() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e := src.Type().Elem()
|
||||||
|
if dst.CanSet() && !src.IsNil() {
|
||||||
|
dst.Set(reflect.New(e))
|
||||||
|
}
|
||||||
|
if src.Elem().IsValid() {
|
||||||
|
// Keep the current root state since the depth hasn't changed
|
||||||
|
rcopy(dst.Elem(), src.Elem(), root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
s := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||||
|
dst.Set(s)
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
rcopy(dst.Index(i), src.Index(i), false)
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
s := reflect.MakeMap(src.Type())
|
||||||
|
dst.Set(s)
|
||||||
|
for _, k := range src.MapKeys() {
|
||||||
|
v := src.MapIndex(k)
|
||||||
|
v2 := reflect.New(v.Type()).Elem()
|
||||||
|
rcopy(v2, v, false)
|
||||||
|
dst.SetMapIndex(k, v2)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Assign the value if possible. If its not assignable, the value would
|
||||||
|
// need to be converted and the impact of that may be unexpected, or is
|
||||||
|
// not compatible with the dst type.
|
||||||
|
if src.Type().AssignableTo(dst.Type()) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
193
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy_test.go
generated
vendored
Normal file
193
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy_test.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package awsutil_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleCopy() {
|
||||||
|
type Foo struct {
|
||||||
|
A int
|
||||||
|
B []*string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the initial value
|
||||||
|
str1 := "hello"
|
||||||
|
str2 := "bye bye"
|
||||||
|
f1 := &Foo{A: 1, B: []*string{&str1, &str2}}
|
||||||
|
|
||||||
|
// Do the copy
|
||||||
|
var f2 Foo
|
||||||
|
awsutil.Copy(&f2, f1)
|
||||||
|
|
||||||
|
// Print the result
|
||||||
|
fmt.Println(awsutil.StringValue(f2))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// {
|
||||||
|
// A: 1,
|
||||||
|
// B: ["hello","bye bye"]
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
type Foo struct {
|
||||||
|
A int
|
||||||
|
B []*string
|
||||||
|
C map[string]*int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the initial value
|
||||||
|
str1 := "hello"
|
||||||
|
str2 := "bye bye"
|
||||||
|
int1 := 1
|
||||||
|
int2 := 2
|
||||||
|
f1 := &Foo{
|
||||||
|
A: 1,
|
||||||
|
B: []*string{&str1, &str2},
|
||||||
|
C: map[string]*int{
|
||||||
|
"A": &int1,
|
||||||
|
"B": &int2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the copy
|
||||||
|
var f2 Foo
|
||||||
|
awsutil.Copy(&f2, f1)
|
||||||
|
|
||||||
|
// Values are equal
|
||||||
|
assert.Equal(t, f2.A, f1.A)
|
||||||
|
assert.Equal(t, f2.B, f1.B)
|
||||||
|
assert.Equal(t, f2.C, f1.C)
|
||||||
|
|
||||||
|
// But pointers are not!
|
||||||
|
str3 := "nothello"
|
||||||
|
int3 := 57
|
||||||
|
f2.A = 100
|
||||||
|
f2.B[0] = &str3
|
||||||
|
f2.C["B"] = &int3
|
||||||
|
assert.NotEqual(t, f2.A, f1.A)
|
||||||
|
assert.NotEqual(t, f2.B, f1.B)
|
||||||
|
assert.NotEqual(t, f2.C, f1.C)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyIgnoreNilMembers(t *testing.T) {
|
||||||
|
type Foo struct {
|
||||||
|
A *string
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &Foo{}
|
||||||
|
assert.Nil(t, f.A)
|
||||||
|
|
||||||
|
var f2 Foo
|
||||||
|
awsutil.Copy(&f2, f)
|
||||||
|
assert.Nil(t, f2.A)
|
||||||
|
|
||||||
|
fcopy := awsutil.CopyOf(f)
|
||||||
|
f3 := fcopy.(*Foo)
|
||||||
|
assert.Nil(t, f3.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyPrimitive(t *testing.T) {
|
||||||
|
str := "hello"
|
||||||
|
var s string
|
||||||
|
awsutil.Copy(&s, &str)
|
||||||
|
assert.Equal(t, "hello", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyNil(t *testing.T) {
|
||||||
|
var s string
|
||||||
|
awsutil.Copy(&s, nil)
|
||||||
|
assert.Equal(t, "", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyReader(t *testing.T) {
|
||||||
|
var buf io.Reader = bytes.NewReader([]byte("hello world"))
|
||||||
|
var r io.Reader
|
||||||
|
awsutil.Copy(&r, buf)
|
||||||
|
b, err := ioutil.ReadAll(r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []byte("hello world"), b)
|
||||||
|
|
||||||
|
// empty bytes because this is not a deep copy
|
||||||
|
b, err = ioutil.ReadAll(buf)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []byte(""), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyDifferentStructs(t *testing.T) {
|
||||||
|
type SrcFoo struct {
|
||||||
|
A int
|
||||||
|
B []*string
|
||||||
|
C map[string]*int
|
||||||
|
SrcUnique string
|
||||||
|
SameNameDiffType int
|
||||||
|
}
|
||||||
|
type DstFoo struct {
|
||||||
|
A int
|
||||||
|
B []*string
|
||||||
|
C map[string]*int
|
||||||
|
DstUnique int
|
||||||
|
SameNameDiffType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the initial value
|
||||||
|
str1 := "hello"
|
||||||
|
str2 := "bye bye"
|
||||||
|
int1 := 1
|
||||||
|
int2 := 2
|
||||||
|
f1 := &SrcFoo{
|
||||||
|
A: 1,
|
||||||
|
B: []*string{&str1, &str2},
|
||||||
|
C: map[string]*int{
|
||||||
|
"A": &int1,
|
||||||
|
"B": &int2,
|
||||||
|
},
|
||||||
|
SrcUnique: "unique",
|
||||||
|
SameNameDiffType: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the copy
|
||||||
|
var f2 DstFoo
|
||||||
|
awsutil.Copy(&f2, f1)
|
||||||
|
|
||||||
|
// Values are equal
|
||||||
|
assert.Equal(t, f2.A, f1.A)
|
||||||
|
assert.Equal(t, f2.B, f1.B)
|
||||||
|
assert.Equal(t, f2.C, f1.C)
|
||||||
|
assert.Equal(t, "unique", f1.SrcUnique)
|
||||||
|
assert.Equal(t, 1, f1.SameNameDiffType)
|
||||||
|
assert.Equal(t, 0, f2.DstUnique)
|
||||||
|
assert.Equal(t, "", f2.SameNameDiffType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCopyOf() {
|
||||||
|
type Foo struct {
|
||||||
|
A int
|
||||||
|
B []*string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the initial value
|
||||||
|
str1 := "hello"
|
||||||
|
str2 := "bye bye"
|
||||||
|
f1 := &Foo{A: 1, B: []*string{&str1, &str2}}
|
||||||
|
|
||||||
|
// Do the copy
|
||||||
|
v := awsutil.CopyOf(f1)
|
||||||
|
var f2 *Foo = v.(*Foo)
|
||||||
|
|
||||||
|
// Print the result
|
||||||
|
fmt.Println(awsutil.StringValue(f2))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// {
|
||||||
|
// A: 1,
|
||||||
|
// B: ["hello","bye bye"]
|
||||||
|
// }
|
||||||
|
}
|
175
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go
generated
vendored
Normal file
175
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go
generated
vendored
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package awsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
pathparts := strings.Split(path, "||")
|
||||||
|
if len(pathparts) > 1 {
|
||||||
|
for _, pathpart := range pathparts {
|
||||||
|
vals := rValuesAtPath(v, pathpart, create, caseSensitive)
|
||||||
|
if vals != nil && len(vals) > 0 {
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
values := []reflect.Value{reflect.Indirect(reflect.ValueOf(v))}
|
||||||
|
components := strings.Split(path, ".")
|
||||||
|
for len(values) > 0 && len(components) > 0 {
|
||||||
|
var index *int64
|
||||||
|
var indexStar bool
|
||||||
|
c := strings.TrimSpace(components[0])
|
||||||
|
if c == "" { // no actual component, illegal syntax
|
||||||
|
return nil
|
||||||
|
} else if caseSensitive && c != "*" && strings.ToLower(c[0:1]) == c[0:1] {
|
||||||
|
// TODO normalize case for user
|
||||||
|
return nil // don't support unexported fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse this component
|
||||||
|
if m := indexRe.FindStringSubmatch(c); m != nil {
|
||||||
|
c = m[1]
|
||||||
|
if m[2] == "" {
|
||||||
|
index = nil
|
||||||
|
indexStar = true
|
||||||
|
} else {
|
||||||
|
i, _ := strconv.ParseInt(m[2], 10, 32)
|
||||||
|
index = &i
|
||||||
|
indexStar = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextvals := []reflect.Value{}
|
||||||
|
for _, value := range values {
|
||||||
|
// pull component name out of struct member
|
||||||
|
if value.Kind() != reflect.Struct {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == "*" { // pull all members
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
if f := reflect.Indirect(value.Field(i)); f.IsValid() {
|
||||||
|
nextvals = append(nextvals, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value = value.FieldByNameFunc(func(name string) bool {
|
||||||
|
if c == name {
|
||||||
|
return true
|
||||||
|
} else if !caseSensitive && strings.ToLower(name) == strings.ToLower(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if create && value.Kind() == reflect.Ptr && value.IsNil() {
|
||||||
|
value.Set(reflect.New(value.Type().Elem()))
|
||||||
|
value = value.Elem()
|
||||||
|
} else {
|
||||||
|
value = reflect.Indirect(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.IsValid() {
|
||||||
|
nextvals = append(nextvals, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values = nextvals
|
||||||
|
|
||||||
|
if indexStar || index != nil {
|
||||||
|
nextvals = []reflect.Value{}
|
||||||
|
for _, value := range values {
|
||||||
|
value := reflect.Indirect(value)
|
||||||
|
if value.Kind() != reflect.Slice {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if indexStar { // grab all indices
|
||||||
|
for i := 0; i < value.Len(); i++ {
|
||||||
|
idx := reflect.Indirect(value.Index(i))
|
||||||
|
if idx.IsValid() {
|
||||||
|
nextvals = append(nextvals, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// pull out index
|
||||||
|
i := int(*index)
|
||||||
|
if i >= value.Len() { // check out of bounds
|
||||||
|
if create {
|
||||||
|
// TODO resize slice
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if i < 0 { // support negative indexing
|
||||||
|
i = value.Len() + i
|
||||||
|
}
|
||||||
|
value = reflect.Indirect(value.Index(i))
|
||||||
|
|
||||||
|
if value.IsValid() {
|
||||||
|
nextvals = append(nextvals, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values = nextvals
|
||||||
|
}
|
||||||
|
|
||||||
|
components = components[1:]
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
return 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
|
||||||
|
func SetValueAtPath(i interface{}, path string, v interface{}) {
|
||||||
|
if rvals := rValuesAtPath(i, path, true, true); rvals != nil {
|
||||||
|
for _, rval := range rvals {
|
||||||
|
rval.Set(reflect.ValueOf(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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go
generated
vendored
Normal file
65
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package awsutil_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Struct struct {
|
||||||
|
A []Struct
|
||||||
|
z []Struct
|
||||||
|
B *Struct
|
||||||
|
D *Struct
|
||||||
|
C string
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = Struct{
|
||||||
|
A: []Struct{{C: "value1"}, {C: "value2"}, {C: "value3"}},
|
||||||
|
z: []Struct{{C: "value1"}, {C: "value2"}, {C: "value3"}},
|
||||||
|
B: &Struct{B: &Struct{C: "terminal"}, D: &Struct{C: "terminal2"}},
|
||||||
|
C: "initial",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueAtPathSuccess(t *testing.T) {
|
||||||
|
assert.Equal(t, []interface{}{"initial"}, awsutil.ValuesAtPath(data, "C"))
|
||||||
|
assert.Equal(t, []interface{}{"value1"}, awsutil.ValuesAtPath(data, "A[0].C"))
|
||||||
|
assert.Equal(t, []interface{}{"value2"}, awsutil.ValuesAtPath(data, "A[1].C"))
|
||||||
|
assert.Equal(t, []interface{}{"value3"}, awsutil.ValuesAtPath(data, "A[2].C"))
|
||||||
|
assert.Equal(t, []interface{}{"value3"}, awsutil.ValuesAtAnyPath(data, "a[2].c"))
|
||||||
|
assert.Equal(t, []interface{}{"value3"}, awsutil.ValuesAtPath(data, "A[-1].C"))
|
||||||
|
assert.Equal(t, []interface{}{"value1", "value2", "value3"}, awsutil.ValuesAtPath(data, "A[].C"))
|
||||||
|
assert.Equal(t, []interface{}{"terminal"}, awsutil.ValuesAtPath(data, "B . B . C"))
|
||||||
|
assert.Equal(t, []interface{}{"terminal", "terminal2"}, awsutil.ValuesAtPath(data, "B.*.C"))
|
||||||
|
assert.Equal(t, []interface{}{"initial"}, awsutil.ValuesAtPath(data, "A.D.X || C"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueAtPathFailure(t *testing.T) {
|
||||||
|
assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, "C.x"))
|
||||||
|
assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, ".x"))
|
||||||
|
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "X.Y.Z"))
|
||||||
|
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "A[100].C"))
|
||||||
|
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "A[3].C"))
|
||||||
|
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "B.B.C.Z"))
|
||||||
|
assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, "z[-1].C"))
|
||||||
|
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(nil, "A.B.C"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetValueAtPathSuccess(t *testing.T) {
|
||||||
|
var s Struct
|
||||||
|
awsutil.SetValueAtPath(&s, "C", "test1")
|
||||||
|
awsutil.SetValueAtPath(&s, "B.B.C", "test2")
|
||||||
|
awsutil.SetValueAtPath(&s, "B.D.C", "test3")
|
||||||
|
assert.Equal(t, "test1", s.C)
|
||||||
|
assert.Equal(t, "test2", s.B.B.C)
|
||||||
|
assert.Equal(t, "test3", s.B.D.C)
|
||||||
|
|
||||||
|
awsutil.SetValueAtPath(&s, "B.*.C", "test0")
|
||||||
|
assert.Equal(t, "test0", s.B.B.C)
|
||||||
|
assert.Equal(t, "test0", s.B.D.C)
|
||||||
|
|
||||||
|
var s2 Struct
|
||||||
|
awsutil.SetValueAtAnyPath(&s2, "b.b.c", "test0")
|
||||||
|
assert.Equal(t, "test0", s2.B.B.C)
|
||||||
|
}
|
103
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/string_value.go
generated
vendored
Normal file
103
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/string_value.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package awsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringValue will recursively walk value v to build a textual
|
||||||
|
// representation of the value.
|
||||||
|
func stringValue(v reflect.Value, indent int, buf *bytes.Buffer) {
|
||||||
|
for v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
strtype := v.Type().String()
|
||||||
|
if strtype == "time.Time" {
|
||||||
|
fmt.Fprintf(buf, "%s", v.Interface())
|
||||||
|
break
|
||||||
|
} else if strings.HasPrefix(strtype, "io.") {
|
||||||
|
buf.WriteString("<buffer>")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Kind() == reflect.Map) && 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"
|
||||||
|
case io.ReadSeeker, io.Reader:
|
||||||
|
format = "buffer(%p)"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buf, format, v.Interface())
|
||||||
|
}
|
||||||
|
}
|
173
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go
generated
vendored
Normal file
173
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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: ""},
|
||||||
|
&credentials.EC2RoleProvider{ExpiryWindow: 5 * time.Minute},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// DefaultConfig is the default all service configuration will be based off of.
|
||||||
|
var DefaultConfig = &Config{
|
||||||
|
Credentials: DefaultChainCredentials,
|
||||||
|
Endpoint: "",
|
||||||
|
Region: os.Getenv("AWS_REGION"),
|
||||||
|
DisableSSL: false,
|
||||||
|
ManualSend: false,
|
||||||
|
HTTPClient: http.DefaultClient,
|
||||||
|
LogHTTPBody: false,
|
||||||
|
LogLevel: 0,
|
||||||
|
Logger: os.Stdout,
|
||||||
|
MaxRetries: DefaultRetries,
|
||||||
|
DisableParamValidation: false,
|
||||||
|
DisableComputeChecksums: false,
|
||||||
|
S3ForcePathStyle: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Config provides service configuration
|
||||||
|
type Config struct {
|
||||||
|
Credentials *credentials.Credentials
|
||||||
|
Endpoint string
|
||||||
|
Region string
|
||||||
|
DisableSSL bool
|
||||||
|
ManualSend bool
|
||||||
|
HTTPClient *http.Client
|
||||||
|
LogHTTPBody bool
|
||||||
|
LogLevel uint
|
||||||
|
Logger io.Writer
|
||||||
|
MaxRetries int
|
||||||
|
DisableParamValidation bool
|
||||||
|
DisableComputeChecksums bool
|
||||||
|
S3ForcePathStyle bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy will return a shallow copy of the Config object.
|
||||||
|
func (c Config) Copy() Config {
|
||||||
|
dst := Config{}
|
||||||
|
dst.Credentials = c.Credentials
|
||||||
|
dst.Endpoint = c.Endpoint
|
||||||
|
dst.Region = c.Region
|
||||||
|
dst.DisableSSL = c.DisableSSL
|
||||||
|
dst.ManualSend = c.ManualSend
|
||||||
|
dst.HTTPClient = c.HTTPClient
|
||||||
|
dst.LogHTTPBody = c.LogHTTPBody
|
||||||
|
dst.LogLevel = c.LogLevel
|
||||||
|
dst.Logger = c.Logger
|
||||||
|
dst.MaxRetries = c.MaxRetries
|
||||||
|
dst.DisableParamValidation = c.DisableParamValidation
|
||||||
|
dst.DisableComputeChecksums = c.DisableComputeChecksums
|
||||||
|
dst.S3ForcePathStyle = c.S3ForcePathStyle
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges the newcfg attribute values into this Config. Each attribute
|
||||||
|
// will be merged into this config if the newcfg attribute's value is non-zero.
|
||||||
|
// Due to this, newcfg attributes with zero values cannot be merged in. For
|
||||||
|
// example bool attributes cannot be cleared using Merge, and must be explicitly
|
||||||
|
// set on the Config structure.
|
||||||
|
func (c Config) Merge(newcfg *Config) *Config {
|
||||||
|
if newcfg == nil {
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := Config{}
|
||||||
|
|
||||||
|
if newcfg.Credentials != nil {
|
||||||
|
cfg.Credentials = newcfg.Credentials
|
||||||
|
} else {
|
||||||
|
cfg.Credentials = c.Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.Endpoint != "" {
|
||||||
|
cfg.Endpoint = newcfg.Endpoint
|
||||||
|
} else {
|
||||||
|
cfg.Endpoint = c.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.Region != "" {
|
||||||
|
cfg.Region = newcfg.Region
|
||||||
|
} else {
|
||||||
|
cfg.Region = c.Region
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.DisableSSL {
|
||||||
|
cfg.DisableSSL = newcfg.DisableSSL
|
||||||
|
} else {
|
||||||
|
cfg.DisableSSL = c.DisableSSL
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.ManualSend {
|
||||||
|
cfg.ManualSend = newcfg.ManualSend
|
||||||
|
} else {
|
||||||
|
cfg.ManualSend = c.ManualSend
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.HTTPClient != nil {
|
||||||
|
cfg.HTTPClient = newcfg.HTTPClient
|
||||||
|
} else {
|
||||||
|
cfg.HTTPClient = c.HTTPClient
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.LogHTTPBody {
|
||||||
|
cfg.LogHTTPBody = newcfg.LogHTTPBody
|
||||||
|
} else {
|
||||||
|
cfg.LogHTTPBody = c.LogHTTPBody
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.LogLevel != 0 {
|
||||||
|
cfg.LogLevel = newcfg.LogLevel
|
||||||
|
} else {
|
||||||
|
cfg.LogLevel = c.LogLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.Logger != nil {
|
||||||
|
cfg.Logger = newcfg.Logger
|
||||||
|
} else {
|
||||||
|
cfg.Logger = c.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.MaxRetries != DefaultRetries {
|
||||||
|
cfg.MaxRetries = newcfg.MaxRetries
|
||||||
|
} else {
|
||||||
|
cfg.MaxRetries = c.MaxRetries
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.DisableParamValidation {
|
||||||
|
cfg.DisableParamValidation = newcfg.DisableParamValidation
|
||||||
|
} else {
|
||||||
|
cfg.DisableParamValidation = c.DisableParamValidation
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.DisableComputeChecksums {
|
||||||
|
cfg.DisableComputeChecksums = newcfg.DisableComputeChecksums
|
||||||
|
} else {
|
||||||
|
cfg.DisableComputeChecksums = c.DisableComputeChecksums
|
||||||
|
}
|
||||||
|
|
||||||
|
if newcfg.S3ForcePathStyle {
|
||||||
|
cfg.S3ForcePathStyle = newcfg.S3ForcePathStyle
|
||||||
|
} else {
|
||||||
|
cfg.S3ForcePathStyle = c.S3ForcePathStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg
|
||||||
|
}
|
92
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config_test.go
generated
vendored
Normal file
92
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config_test.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testCredentials = credentials.NewChainCredentials([]credentials.Provider{
|
||||||
|
&credentials.EnvProvider{},
|
||||||
|
&credentials.SharedCredentialsProvider{
|
||||||
|
Filename: "TestFilename",
|
||||||
|
Profile: "TestProfile"},
|
||||||
|
&credentials.EC2RoleProvider{ExpiryWindow: 5 * time.Minute},
|
||||||
|
})
|
||||||
|
|
||||||
|
var copyTestConfig = Config{
|
||||||
|
Credentials: testCredentials,
|
||||||
|
Endpoint: "CopyTestEndpoint",
|
||||||
|
Region: "COPY_TEST_AWS_REGION",
|
||||||
|
DisableSSL: true,
|
||||||
|
ManualSend: true,
|
||||||
|
HTTPClient: http.DefaultClient,
|
||||||
|
LogHTTPBody: true,
|
||||||
|
LogLevel: 2,
|
||||||
|
Logger: os.Stdout,
|
||||||
|
MaxRetries: DefaultRetries,
|
||||||
|
DisableParamValidation: true,
|
||||||
|
DisableComputeChecksums: true,
|
||||||
|
S3ForcePathStyle: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
want := copyTestConfig
|
||||||
|
got := copyTestConfig.Copy()
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("Copy() = %+v", got)
|
||||||
|
t.Errorf(" want %+v", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyReturnsNewInstance(t *testing.T) {
|
||||||
|
want := copyTestConfig
|
||||||
|
got := copyTestConfig.Copy()
|
||||||
|
if &got == &want {
|
||||||
|
t.Errorf("Copy() = %p; want different instance as source %p", &got, &want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergeTestZeroValueConfig = Config{MaxRetries: DefaultRetries}
|
||||||
|
|
||||||
|
var mergeTestConfig = Config{
|
||||||
|
Credentials: testCredentials,
|
||||||
|
Endpoint: "MergeTestEndpoint",
|
||||||
|
Region: "MERGE_TEST_AWS_REGION",
|
||||||
|
DisableSSL: true,
|
||||||
|
ManualSend: true,
|
||||||
|
HTTPClient: http.DefaultClient,
|
||||||
|
LogHTTPBody: true,
|
||||||
|
LogLevel: 2,
|
||||||
|
Logger: os.Stdout,
|
||||||
|
MaxRetries: 10,
|
||||||
|
DisableParamValidation: true,
|
||||||
|
DisableComputeChecksums: true,
|
||||||
|
S3ForcePathStyle: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergeTests = []struct {
|
||||||
|
cfg *Config
|
||||||
|
in *Config
|
||||||
|
want *Config
|
||||||
|
}{
|
||||||
|
{&Config{}, nil, &Config{}},
|
||||||
|
{&Config{}, &mergeTestZeroValueConfig, &Config{}},
|
||||||
|
{&Config{}, &mergeTestConfig, &mergeTestConfig},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMerge(t *testing.T) {
|
||||||
|
for _, tt := range mergeTests {
|
||||||
|
got := tt.cfg.Merge(tt.in)
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Config %+v", tt.cfg)
|
||||||
|
t.Errorf(" Merge(%+v)", tt.in)
|
||||||
|
t.Errorf(" got %+v", got)
|
||||||
|
t.Errorf(" want %+v", tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNoValidProvidersFoundInChain Is returned when there are no valid
|
||||||
|
// providers in the ChainProvider.
|
||||||
|
ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders", "no valid providers in chain", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ChainProvider will search for a provider which returns credentials
|
||||||
|
// and cache that provider until Retrieve is called again.
|
||||||
|
//
|
||||||
|
// The ChainProvider provides a way of chaining multiple providers together
|
||||||
|
// which will pick the first available using priority order of the Providers
|
||||||
|
// in the list.
|
||||||
|
//
|
||||||
|
// If none of the Providers retrieve valid credentials Value, ChainProvider's
|
||||||
|
// Retrieve() will return the error ErrNoValidProvidersFoundInChain.
|
||||||
|
//
|
||||||
|
// If a Provider is found which returns valid credentials Value ChainProvider
|
||||||
|
// will cache that Provider for all calls to IsExpired(), until Retrieve is
|
||||||
|
// called again.
|
||||||
|
//
|
||||||
|
// Example of ChainProvider to be used with an EnvProvider and EC2RoleProvider.
|
||||||
|
// In this example EnvProvider will first check if any credentials are available
|
||||||
|
// vai the environment variables. If there are none ChainProvider will check
|
||||||
|
// the next Provider in the list, EC2RoleProvider in this case. If EC2RoleProvider
|
||||||
|
// does not return any credentials ChainProvider will return the error
|
||||||
|
// ErrNoValidProvidersFoundInChain
|
||||||
|
//
|
||||||
|
// creds := NewChainCredentials(
|
||||||
|
// []Provider{
|
||||||
|
// &EnvProvider{},
|
||||||
|
// &EC2RoleProvider{},
|
||||||
|
// })
|
||||||
|
// creds.Retrieve()
|
||||||
|
//
|
||||||
|
type ChainProvider struct {
|
||||||
|
Providers []Provider
|
||||||
|
curr Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChainCredentials returns a pointer to a new Credentials object
|
||||||
|
// wrapping a chain of providers.
|
||||||
|
func NewChainCredentials(providers []Provider) *Credentials {
|
||||||
|
return NewCredentials(&ChainProvider{
|
||||||
|
Providers: append([]Provider{}, providers...),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve returns the credentials value or error if no provider returned
|
||||||
|
// without error.
|
||||||
|
//
|
||||||
|
// If a provider is found it will be cached and any calls to IsExpired()
|
||||||
|
// will return the expired state of the cached provider.
|
||||||
|
func (c *ChainProvider) Retrieve() (Value, error) {
|
||||||
|
for _, p := range c.Providers {
|
||||||
|
if creds, err := p.Retrieve(); err == nil {
|
||||||
|
c.curr = p
|
||||||
|
return creds, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.curr = nil
|
||||||
|
|
||||||
|
// TODO better error reporting. maybe report error for each failed retrieve?
|
||||||
|
|
||||||
|
return Value{}, ErrNoValidProvidersFoundInChain
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExpired will returned the expired state of the currently cached provider
|
||||||
|
// if there is one. If there is no current provider, true will be returned.
|
||||||
|
func (c *ChainProvider) IsExpired() bool {
|
||||||
|
if c.curr != nil {
|
||||||
|
return c.curr.IsExpired()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
73
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider_test.go
generated
vendored
Normal file
73
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider_test.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChainProviderGet(t *testing.T) {
|
||||||
|
p := &ChainProvider{
|
||||||
|
Providers: []Provider{
|
||||||
|
&stubProvider{err: awserr.New("FirstError", "first provider error", nil)},
|
||||||
|
&stubProvider{err: awserr.New("SecondError", "second provider error", nil)},
|
||||||
|
&stubProvider{
|
||||||
|
creds: Value{
|
||||||
|
AccessKeyID: "AKID",
|
||||||
|
SecretAccessKey: "SECRET",
|
||||||
|
SessionToken: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match")
|
||||||
|
assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Empty(t, creds.SessionToken, "Expect session token to be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainProviderIsExpired(t *testing.T) {
|
||||||
|
stubProvider := &stubProvider{expired: true}
|
||||||
|
p := &ChainProvider{
|
||||||
|
Providers: []Provider{
|
||||||
|
stubProvider,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, p.IsExpired(), "Expect expired to be true before any Retrieve")
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
assert.False(t, p.IsExpired(), "Expect not expired after retrieve")
|
||||||
|
|
||||||
|
stubProvider.expired = true
|
||||||
|
assert.True(t, p.IsExpired(), "Expect return of expired provider")
|
||||||
|
|
||||||
|
_, err = p.Retrieve()
|
||||||
|
assert.False(t, p.IsExpired(), "Expect not expired after retrieve")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainProviderWithNoProvider(t *testing.T) {
|
||||||
|
p := &ChainProvider{
|
||||||
|
Providers: []Provider{},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, p.IsExpired(), "Expect expired with no providers")
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
assert.Equal(t, ErrNoValidProvidersFoundInChain, err, "Expect no providers error returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainProviderWithNoValidProvider(t *testing.T) {
|
||||||
|
p := &ChainProvider{
|
||||||
|
Providers: []Provider{
|
||||||
|
&stubProvider{err: awserr.New("FirstError", "first provider error", nil)},
|
||||||
|
&stubProvider{err: awserr.New("SecondError", "second provider error", nil)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, p.IsExpired(), "Expect expired with no providers")
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
assert.Equal(t, ErrNoValidProvidersFoundInChain, err, "Expect no providers error returned")
|
||||||
|
}
|
219
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials.go
generated
vendored
Normal file
219
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
// Package credentials provides credential retrieval and management
|
||||||
|
//
|
||||||
|
// The Credentials is the primary method of getting access to and managing
|
||||||
|
// credentials Values. Using dependency injection retrieval of the credential
|
||||||
|
// values is handled by a object which satisfies the Provider interface.
|
||||||
|
//
|
||||||
|
// By default the Credentials.Get() will cache the successful result of a
|
||||||
|
// Provider's Retrieve() until Provider.IsExpired() returns true. At which
|
||||||
|
// point Credentials will call Provider's Retrieve() to get new credential Value.
|
||||||
|
//
|
||||||
|
// The Provider is responsible for determining when credentials Value have expired.
|
||||||
|
// It is also important to note that Credentials will always call Retrieve the
|
||||||
|
// first time Credentials.Get() is called.
|
||||||
|
//
|
||||||
|
// Example of using the environment variable credentials.
|
||||||
|
//
|
||||||
|
// creds := NewEnvCredentials()
|
||||||
|
//
|
||||||
|
// // Retrieve the credentials value
|
||||||
|
// credValue, err := creds.Get()
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Example of forcing credentials to expire and be refreshed on the next Get().
|
||||||
|
// This may be helpful to proactively expire credentials and refresh them sooner
|
||||||
|
// than they would naturally expire on their own.
|
||||||
|
//
|
||||||
|
// creds := NewCredentials(&EC2RoleProvider{})
|
||||||
|
// creds.Expire()
|
||||||
|
// credsValue, err := creds.Get()
|
||||||
|
// // New credentials will be retrieved instead of from cache.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Custom Provider
|
||||||
|
//
|
||||||
|
// Each Provider built into this package also provides a helper method to generate
|
||||||
|
// a Credentials pointer setup with the provider. To use a custom Provider just
|
||||||
|
// create a type which satisfies the Provider interface and pass it to the
|
||||||
|
// NewCredentials method.
|
||||||
|
//
|
||||||
|
// type MyProvider struct{}
|
||||||
|
// func (m *MyProvider) Retrieve() (Value, error) {...}
|
||||||
|
// func (m *MyProvider) IsExpired() bool {...}
|
||||||
|
//
|
||||||
|
// creds := NewCredentials(&MyProvider{})
|
||||||
|
// credValue, err := creds.Get()
|
||||||
|
//
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create an empty Credential object that can be used as dummy placeholder
|
||||||
|
// credentials for requests that do not need signed.
|
||||||
|
//
|
||||||
|
// This Credentials can be used to configure a service to not sign requests
|
||||||
|
// when making service API calls. For example, when accessing public
|
||||||
|
// s3 buckets.
|
||||||
|
//
|
||||||
|
// svc := s3.New(&aws.Config{Credentials: AnonymousCredentials})
|
||||||
|
// // Access public S3 buckets.
|
||||||
|
//
|
||||||
|
var AnonymousCredentials = NewStaticCredentials("", "", "")
|
||||||
|
|
||||||
|
// A Value is the AWS credentials value for individual credential fields.
|
||||||
|
type Value struct {
|
||||||
|
// AWS Access key ID
|
||||||
|
AccessKeyID string
|
||||||
|
|
||||||
|
// AWS Secret Access Key
|
||||||
|
SecretAccessKey string
|
||||||
|
|
||||||
|
// AWS Session Token
|
||||||
|
SessionToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Provider is the interface for any component which will provide credentials
|
||||||
|
// Value. A provider is required to manage its own Expired state, and what to
|
||||||
|
// be expired means.
|
||||||
|
//
|
||||||
|
// The Provider should not need to implement its own mutexes, because
|
||||||
|
// that will be managed by Credentials.
|
||||||
|
type Provider interface {
|
||||||
|
// Refresh returns nil if it successfully retrieved the value.
|
||||||
|
// Error is returned if the value were not obtainable, or empty.
|
||||||
|
Retrieve() (Value, error)
|
||||||
|
|
||||||
|
// IsExpired returns if the credentials are no longer valid, and need
|
||||||
|
// to be retrieved.
|
||||||
|
IsExpired() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Expiry provides shared expiration logic to be used by credentials
|
||||||
|
// providers to implement expiry functionality.
|
||||||
|
//
|
||||||
|
// The best method to use this struct is as an anonymous field within the
|
||||||
|
// provider's struct.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// type EC2RoleProvider struct {
|
||||||
|
// Expiry
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
type Expiry struct {
|
||||||
|
// The date/time when to expire on
|
||||||
|
expiration time.Time
|
||||||
|
|
||||||
|
// If set will be used by IsExpired to determine the current time.
|
||||||
|
// Defaults to time.Now if CurrentTime is not set. Available for testing
|
||||||
|
// to be able to mock out the current time.
|
||||||
|
CurrentTime func() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExpiration sets the expiration IsExpired will check when called.
|
||||||
|
//
|
||||||
|
// If window is greater than 0 the expiration time will be reduced by the
|
||||||
|
// window value.
|
||||||
|
//
|
||||||
|
// Using a window is helpful to trigger credentials to expire sooner than
|
||||||
|
// the expiration time given to ensure no requests are made with expired
|
||||||
|
// tokens.
|
||||||
|
func (e *Expiry) SetExpiration(expiration time.Time, window time.Duration) {
|
||||||
|
e.expiration = expiration
|
||||||
|
if window > 0 {
|
||||||
|
e.expiration = e.expiration.Add(-window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExpired returns if the credentials are expired.
|
||||||
|
func (e *Expiry) IsExpired() bool {
|
||||||
|
if e.CurrentTime == nil {
|
||||||
|
e.CurrentTime = time.Now
|
||||||
|
}
|
||||||
|
return e.expiration.Before(e.CurrentTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Credentials provides synchronous safe retrieval of AWS credentials Value.
|
||||||
|
// Credentials will cache the credentials value until they expire. Once the value
|
||||||
|
// expires the next Get will attempt to retrieve valid credentials.
|
||||||
|
//
|
||||||
|
// Credentials is safe to use across multiple goroutines and will manage the
|
||||||
|
// synchronous state so the Providers do not need to implement their own
|
||||||
|
// synchronization.
|
||||||
|
//
|
||||||
|
// The first Credentials.Get() will always call Provider.Retrieve() to get the
|
||||||
|
// first instance of the credentials Value. All calls to Get() after that
|
||||||
|
// will return the cached credentials Value until IsExpired() returns true.
|
||||||
|
type Credentials struct {
|
||||||
|
creds Value
|
||||||
|
forceRefresh bool
|
||||||
|
m sync.Mutex
|
||||||
|
|
||||||
|
provider Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCredentials returns a pointer to a new Credentials with the provider set.
|
||||||
|
func NewCredentials(provider Provider) *Credentials {
|
||||||
|
return &Credentials{
|
||||||
|
provider: provider,
|
||||||
|
forceRefresh: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the credentials value, or error if the credentials Value failed
|
||||||
|
// to be retrieved.
|
||||||
|
//
|
||||||
|
// Will return the cached credentials Value if it has not expired. If the
|
||||||
|
// credentials Value has expired the Provider's Retrieve() will be called
|
||||||
|
// to refresh the credentials.
|
||||||
|
//
|
||||||
|
// If Credentials.Expire() was called the credentials Value will be force
|
||||||
|
// expired, and the next call to Get() will cause them to be refreshed.
|
||||||
|
func (c *Credentials) Get() (Value, error) {
|
||||||
|
c.m.Lock()
|
||||||
|
defer c.m.Unlock()
|
||||||
|
|
||||||
|
if c.isExpired() {
|
||||||
|
creds, err := c.provider.Retrieve()
|
||||||
|
if err != nil {
|
||||||
|
return Value{}, err
|
||||||
|
}
|
||||||
|
c.creds = creds
|
||||||
|
c.forceRefresh = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire expires the credentials and forces them to be retrieved on the
|
||||||
|
// next call to Get().
|
||||||
|
//
|
||||||
|
// This will override the Provider's expired state, and force Credentials
|
||||||
|
// to call the Provider's Retrieve().
|
||||||
|
func (c *Credentials) Expire() {
|
||||||
|
c.m.Lock()
|
||||||
|
defer c.m.Unlock()
|
||||||
|
|
||||||
|
c.forceRefresh = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExpired returns if the credentials are no longer valid, and need
|
||||||
|
// to be retrieved.
|
||||||
|
//
|
||||||
|
// If the Credentials were forced to be expired with Expire() this will
|
||||||
|
// reflect that override.
|
||||||
|
func (c *Credentials) IsExpired() bool {
|
||||||
|
c.m.Lock()
|
||||||
|
defer c.m.Unlock()
|
||||||
|
|
||||||
|
return c.isExpired()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExpired helper method wrapping the definition of expired credentials.
|
||||||
|
func (c *Credentials) isExpired() bool {
|
||||||
|
return c.forceRefresh || c.provider.IsExpired()
|
||||||
|
}
|
62
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials_test.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials_test.go
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stubProvider struct {
|
||||||
|
creds Value
|
||||||
|
expired bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubProvider) Retrieve() (Value, error) {
|
||||||
|
s.expired = false
|
||||||
|
return s.creds, s.err
|
||||||
|
}
|
||||||
|
func (s *stubProvider) IsExpired() bool {
|
||||||
|
return s.expired
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCredentialsGet(t *testing.T) {
|
||||||
|
c := NewCredentials(&stubProvider{
|
||||||
|
creds: Value{
|
||||||
|
AccessKeyID: "AKID",
|
||||||
|
SecretAccessKey: "SECRET",
|
||||||
|
SessionToken: "",
|
||||||
|
},
|
||||||
|
expired: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
creds, err := c.Get()
|
||||||
|
assert.Nil(t, err, "Expected no error")
|
||||||
|
assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match")
|
||||||
|
assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Empty(t, creds.SessionToken, "Expect session token to be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCredentialsGetWithError(t *testing.T) {
|
||||||
|
c := NewCredentials(&stubProvider{err: awserr.New("provider error", "", nil), expired: true})
|
||||||
|
|
||||||
|
_, err := c.Get()
|
||||||
|
assert.Equal(t, "provider error", err.(awserr.Error).Code(), "Expected provider error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCredentialsExpire(t *testing.T) {
|
||||||
|
stub := &stubProvider{}
|
||||||
|
c := NewCredentials(stub)
|
||||||
|
|
||||||
|
stub.expired = false
|
||||||
|
assert.True(t, c.IsExpired(), "Expected to start out expired")
|
||||||
|
c.Expire()
|
||||||
|
assert.True(t, c.IsExpired(), "Expected to be expired")
|
||||||
|
|
||||||
|
c.forceRefresh = false
|
||||||
|
assert.False(t, c.IsExpired(), "Expected not to be expired")
|
||||||
|
|
||||||
|
stub.expired = true
|
||||||
|
assert.True(t, c.IsExpired(), "Expected to be expired")
|
||||||
|
}
|
163
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider.go
generated
vendored
Normal file
163
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider.go
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const metadataCredentialsEndpoint = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
||||||
|
|
||||||
|
// A EC2RoleProvider retrieves credentials from the EC2 service, and keeps track if
|
||||||
|
// those credentials are expired.
|
||||||
|
//
|
||||||
|
// Example how to configure the EC2RoleProvider with custom http Client, Endpoint
|
||||||
|
// or ExpiryWindow
|
||||||
|
//
|
||||||
|
// p := &credentials.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: "",
|
||||||
|
// // Do not use early expiry of credentials. If a non zero value is
|
||||||
|
// // specified the credentials will be expired early
|
||||||
|
// ExpiryWindow: 0,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type EC2RoleProvider struct {
|
||||||
|
Expiry
|
||||||
|
|
||||||
|
// Endpoint must be fully quantified URL
|
||||||
|
Endpoint string
|
||||||
|
|
||||||
|
// HTTP client to use when connecting to EC2 service
|
||||||
|
Client *http.Client
|
||||||
|
|
||||||
|
// ExpiryWindow will allow the credentials to trigger refreshing prior to
|
||||||
|
// the credentials actually expiring. This is beneficial so race conditions
|
||||||
|
// with expiring credentials do not cause request to fail unexpectedly
|
||||||
|
// due to ExpiredTokenException exceptions.
|
||||||
|
//
|
||||||
|
// So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
|
||||||
|
// 10 seconds before the credentials are actually expired.
|
||||||
|
//
|
||||||
|
// If ExpiryWindow is 0 or less it will be ignored.
|
||||||
|
ExpiryWindow time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEC2RoleCredentials 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 NewEC2RoleCredentials(client *http.Client, endpoint string, window time.Duration) *Credentials {
|
||||||
|
return NewCredentials(&EC2RoleProvider{
|
||||||
|
Endpoint: endpoint,
|
||||||
|
Client: client,
|
||||||
|
ExpiryWindow: window,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() (Value, error) {
|
||||||
|
if m.Client == nil {
|
||||||
|
m.Client = http.DefaultClient
|
||||||
|
}
|
||||||
|
if m.Endpoint == "" {
|
||||||
|
m.Endpoint = metadataCredentialsEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
credsList, err := requestCredList(m.Client, m.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return Value{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(credsList) == 0 {
|
||||||
|
return Value{}, awserr.New("EmptyEC2RoleList", "empty EC2 Role list", nil)
|
||||||
|
}
|
||||||
|
credsName := credsList[0]
|
||||||
|
|
||||||
|
roleCreds, err := requestCred(m.Client, m.Endpoint, credsName)
|
||||||
|
if err != nil {
|
||||||
|
return Value{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.SetExpiration(roleCreds.Expiration, m.ExpiryWindow)
|
||||||
|
|
||||||
|
return Value{
|
||||||
|
AccessKeyID: roleCreds.AccessKeyID,
|
||||||
|
SecretAccessKey: roleCreds.SecretAccessKey,
|
||||||
|
SessionToken: roleCreds.Token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ec2RoleCredRespBody provides the shape for deserializing credential
|
||||||
|
// request responses.
|
||||||
|
type ec2RoleCredRespBody struct {
|
||||||
|
Expiration time.Time
|
||||||
|
AccessKeyID string
|
||||||
|
SecretAccessKey string
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *http.Client, endpoint string) ([]string, error) {
|
||||||
|
resp, err := client.Get(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, awserr.New("ListEC2Role", "failed to list EC2 Roles", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
credsList := []string{}
|
||||||
|
s := bufio.NewScanner(resp.Body)
|
||||||
|
for s.Scan() {
|
||||||
|
credsList = append(credsList, s.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, awserr.New("ReadEC2Role", "failed to read list of EC2 Roles", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return credsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestCred requests the credentials for a specific credentials from the EC2 service.
|
||||||
|
//
|
||||||
|
// If the credentials cannot be found, or there is an error reading the response
|
||||||
|
// and error will be returned.
|
||||||
|
func requestCred(client *http.Client, endpoint, credsName string) (*ec2RoleCredRespBody, error) {
|
||||||
|
resp, err := client.Get(endpoint + credsName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, awserr.New("GetEC2RoleCredentials",
|
||||||
|
fmt.Sprintf("failed to get %s EC2 Role credentials", credsName),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respCreds := &ec2RoleCredRespBody{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(respCreds); err != nil {
|
||||||
|
return nil, awserr.New("DecodeEC2RoleCredentials",
|
||||||
|
fmt.Sprintf("failed to decode %s EC2 Role credentials", credsName),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respCreds, nil
|
||||||
|
}
|
108
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider_test.go
generated
vendored
Normal file
108
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider_test.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initTestServer(expireOn string) *httptest.Server {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.RequestURI == "/" {
|
||||||
|
fmt.Fprintln(w, "/creds")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, `{
|
||||||
|
"AccessKeyId" : "accessKey",
|
||||||
|
"SecretAccessKey" : "secret",
|
||||||
|
"Token" : "token",
|
||||||
|
"Expiration" : "%s"
|
||||||
|
}`, expireOn)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEC2RoleProvider(t *testing.T) {
|
||||||
|
server := initTestServer("2014-12-16T01:51:37Z")
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
p := &EC2RoleProvider{Client: http.DefaultClient, Endpoint: server.URL}
|
||||||
|
|
||||||
|
creds, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
|
||||||
|
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Equal(t, "token", creds.SessionToken, "Expect session token to match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEC2RoleProviderIsExpired(t *testing.T) {
|
||||||
|
server := initTestServer("2014-12-16T01:51:37Z")
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
p := &EC2RoleProvider{Client: http.DefaultClient, Endpoint: server.URL}
|
||||||
|
p.CurrentTime = func() time.Time {
|
||||||
|
return time.Date(2014, 12, 15, 21, 26, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve.")
|
||||||
|
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve.")
|
||||||
|
|
||||||
|
p.CurrentTime = func() time.Time {
|
||||||
|
return time.Date(3014, 12, 15, 21, 26, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, p.IsExpired(), "Expect creds to be expired.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEC2RoleProviderExpiryWindowIsExpired(t *testing.T) {
|
||||||
|
server := initTestServer("2014-12-16T01:51:37Z")
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
p := &EC2RoleProvider{Client: http.DefaultClient, Endpoint: server.URL, ExpiryWindow: time.Hour * 1}
|
||||||
|
p.CurrentTime = func() time.Time {
|
||||||
|
return time.Date(2014, 12, 15, 0, 51, 37, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve.")
|
||||||
|
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve.")
|
||||||
|
|
||||||
|
p.CurrentTime = func() time.Time {
|
||||||
|
return time.Date(2014, 12, 16, 0, 55, 37, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, p.IsExpired(), "Expect creds to be expired.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEC2RoleProvider(b *testing.B) {
|
||||||
|
server := initTestServer("2014-12-16T01:51:37Z")
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
p := &EC2RoleProvider{Client: http.DefaultClient, Endpoint: server.URL}
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
67
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be
|
||||||
|
// found in the process's environment.
|
||||||
|
ErrAccessKeyIDNotFound = awserr.New("EnvAccessKeyNotFound", "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment", nil)
|
||||||
|
// ErrSecretAccessKeyNotFound is returned when the AWS Secret Access Key
|
||||||
|
// can't be found in the process's environment.
|
||||||
|
ErrSecretAccessKeyNotFound = awserr.New("EnvSecretNotFound", "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// A EnvProvider retrieves credentials from the environment variables of the
|
||||||
|
// running process. Environment credentials never expire.
|
||||||
|
//
|
||||||
|
// Environment variables used:
|
||||||
|
// - Access Key ID: AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY
|
||||||
|
// - Secret Access Key: AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY
|
||||||
|
type EnvProvider struct {
|
||||||
|
retrieved bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEnvCredentials returns a pointer to a new Credentials object
|
||||||
|
// wrapping the environment variable provider.
|
||||||
|
func NewEnvCredentials() *Credentials {
|
||||||
|
return NewCredentials(&EnvProvider{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve retrieves the keys from the environment.
|
||||||
|
func (e *EnvProvider) Retrieve() (Value, error) {
|
||||||
|
e.retrieved = false
|
||||||
|
|
||||||
|
id := os.Getenv("AWS_ACCESS_KEY_ID")
|
||||||
|
if id == "" {
|
||||||
|
id = os.Getenv("AWS_ACCESS_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||||
|
if secret == "" {
|
||||||
|
secret = os.Getenv("AWS_SECRET_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
if id == "" {
|
||||||
|
return Value{}, ErrAccessKeyIDNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if secret == "" {
|
||||||
|
return Value{}, ErrSecretAccessKeyNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
e.retrieved = true
|
||||||
|
return Value{
|
||||||
|
AccessKeyID: id,
|
||||||
|
SecretAccessKey: secret,
|
||||||
|
SessionToken: os.Getenv("AWS_SESSION_TOKEN"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExpired returns if the credentials have been retrieved.
|
||||||
|
func (e *EnvProvider) IsExpired() bool {
|
||||||
|
return !e.retrieved
|
||||||
|
}
|
70
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider_test.go
generated
vendored
Normal file
70
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider_test.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnvProviderRetrieve(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||||
|
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||||
|
os.Setenv("AWS_SESSION_TOKEN", "token")
|
||||||
|
|
||||||
|
e := EnvProvider{}
|
||||||
|
creds, err := e.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.Equal(t, "access", creds.AccessKeyID, "Expect access key ID to match")
|
||||||
|
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Equal(t, "token", creds.SessionToken, "Expect session token to match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvProviderIsExpired(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||||
|
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||||
|
os.Setenv("AWS_SESSION_TOKEN", "token")
|
||||||
|
|
||||||
|
e := EnvProvider{}
|
||||||
|
|
||||||
|
assert.True(t, e.IsExpired(), "Expect creds to be expired before retrieve.")
|
||||||
|
|
||||||
|
_, err := e.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.False(t, e.IsExpired(), "Expect creds to not be expired after retrieve.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvProviderNoAccessKeyID(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||||
|
|
||||||
|
e := EnvProvider{}
|
||||||
|
creds, err := e.Retrieve()
|
||||||
|
assert.Equal(t, ErrAccessKeyIDNotFound, err, "ErrAccessKeyIDNotFound expected, but was %#v error: %#v", creds, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvProviderNoSecretAccessKey(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||||
|
|
||||||
|
e := EnvProvider{}
|
||||||
|
creds, err := e.Retrieve()
|
||||||
|
assert.Equal(t, ErrSecretAccessKeyNotFound, err, "ErrSecretAccessKeyNotFound expected, but was %#v error: %#v", creds, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvProviderAlternateNames(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_ACCESS_KEY", "access")
|
||||||
|
os.Setenv("AWS_SECRET_KEY", "secret")
|
||||||
|
|
||||||
|
e := EnvProvider{}
|
||||||
|
creds, err := e.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.Equal(t, "access", creds.AccessKeyID, "Expected access key ID")
|
||||||
|
assert.Equal(t, "secret", creds.SecretAccessKey, "Expected secret access key")
|
||||||
|
assert.Empty(t, creds.SessionToken, "Expected no token")
|
||||||
|
}
|
8
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/example.ini
generated
vendored
Normal file
8
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/example.ini
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[default]
|
||||||
|
aws_access_key_id = accessKey
|
||||||
|
aws_secret_access_key = secret
|
||||||
|
aws_session_token = token
|
||||||
|
|
||||||
|
[no_token]
|
||||||
|
aws_access_key_id = accessKey
|
||||||
|
aws_secret_access_key = secret
|
133
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go
generated
vendored
Normal file
133
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/vaughan0/go-ini"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrSharedCredentialsHomeNotFound is emitted when the user directory cannot be found.
|
||||||
|
ErrSharedCredentialsHomeNotFound = awserr.New("UserHomeNotFound", "user home directory not found.", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// A SharedCredentialsProvider retrieves credentials from the current user's home
|
||||||
|
// directory, and keeps track if those credentials are expired.
|
||||||
|
//
|
||||||
|
// Profile ini file example: $HOME/.aws/credentials
|
||||||
|
type SharedCredentialsProvider struct {
|
||||||
|
// Path to the shared credentials file. If empty will default to current user's
|
||||||
|
// home directory.
|
||||||
|
Filename string
|
||||||
|
|
||||||
|
// AWS Profile to extract credentials from the shared credentials file. If empty
|
||||||
|
// will default to environment variable "AWS_PROFILE" or "default" if
|
||||||
|
// environment variable is also not set.
|
||||||
|
Profile string
|
||||||
|
|
||||||
|
// retrieved states if the credentials have been successfully retrieved.
|
||||||
|
retrieved bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSharedCredentials returns a pointer to a new Credentials object
|
||||||
|
// wrapping the Profile file provider.
|
||||||
|
func NewSharedCredentials(filename, profile string) *Credentials {
|
||||||
|
return NewCredentials(&SharedCredentialsProvider{
|
||||||
|
Filename: filename,
|
||||||
|
Profile: profile,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve reads and extracts the shared credentials from the current
|
||||||
|
// users home directory.
|
||||||
|
func (p *SharedCredentialsProvider) Retrieve() (Value, error) {
|
||||||
|
p.retrieved = false
|
||||||
|
|
||||||
|
filename, err := p.filename()
|
||||||
|
if err != nil {
|
||||||
|
return Value{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := loadProfile(filename, p.profile())
|
||||||
|
if err != nil {
|
||||||
|
return Value{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.retrieved = true
|
||||||
|
return creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExpired returns if the shared credentials have expired.
|
||||||
|
func (p *SharedCredentialsProvider) IsExpired() bool {
|
||||||
|
return !p.retrieved
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadProfiles loads from the file pointed to by shared credentials filename for profile.
|
||||||
|
// 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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, ok := iniProfile["aws_secret_access_key"]
|
||||||
|
if !ok {
|
||||||
|
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"]
|
||||||
|
|
||||||
|
return Value{
|
||||||
|
AccessKeyID: id,
|
||||||
|
SecretAccessKey: secret,
|
||||||
|
SessionToken: token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// filename returns the filename to use to read AWS shared credentials.
|
||||||
|
//
|
||||||
|
// Will return an error if the user's home directory path cannot be found.
|
||||||
|
func (p *SharedCredentialsProvider) filename() (string, error) {
|
||||||
|
if p.Filename == "" {
|
||||||
|
homeDir := os.Getenv("HOME") // *nix
|
||||||
|
if homeDir == "" { // Windows
|
||||||
|
homeDir = os.Getenv("USERPROFILE")
|
||||||
|
}
|
||||||
|
if homeDir == "" {
|
||||||
|
return "", ErrSharedCredentialsHomeNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Filename = filepath.Join(homeDir, ".aws", "credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Filename, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// profile returns the AWS shared credentials profile. If empty will read
|
||||||
|
// environment variable "AWS_PROFILE". If that is not set profile will
|
||||||
|
// return "default".
|
||||||
|
func (p *SharedCredentialsProvider) profile() string {
|
||||||
|
if p.Profile == "" {
|
||||||
|
p.Profile = os.Getenv("AWS_PROFILE")
|
||||||
|
}
|
||||||
|
if p.Profile == "" {
|
||||||
|
p.Profile = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Profile
|
||||||
|
}
|
77
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider_test.go
generated
vendored
Normal file
77
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider_test.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSharedCredentialsProvider(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""}
|
||||||
|
creds, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
|
||||||
|
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Equal(t, "token", creds.SessionToken, "Expect session token to match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharedCredentialsProviderIsExpired(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""}
|
||||||
|
|
||||||
|
assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve")
|
||||||
|
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharedCredentialsProviderWithAWS_PROFILE(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_PROFILE", "no_token")
|
||||||
|
|
||||||
|
p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""}
|
||||||
|
creds, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
|
||||||
|
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Empty(t, creds.SessionToken, "Expect no token")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharedCredentialsProviderWithoutTokenFromProfile(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
p := SharedCredentialsProvider{Filename: "example.ini", Profile: "no_token"}
|
||||||
|
creds, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
|
||||||
|
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Empty(t, creds.SessionToken, "Expect no token")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSharedCredentialsProvider(b *testing.B) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""}
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
42
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrStaticCredentialsEmpty is emitted when static credentials are empty.
|
||||||
|
ErrStaticCredentialsEmpty = awserr.New("EmptyStaticCreds", "static credentials are empty", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// A StaticProvider is a set of credentials which are set pragmatically,
|
||||||
|
// and will never expire.
|
||||||
|
type StaticProvider struct {
|
||||||
|
Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStaticCredentials returns a pointer to a new Credentials object
|
||||||
|
// wrapping a static credentials value provider.
|
||||||
|
func NewStaticCredentials(id, secret, token string) *Credentials {
|
||||||
|
return NewCredentials(&StaticProvider{Value: Value{
|
||||||
|
AccessKeyID: id,
|
||||||
|
SecretAccessKey: secret,
|
||||||
|
SessionToken: token,
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve returns the credentials or error if the credentials are invalid.
|
||||||
|
func (s *StaticProvider) Retrieve() (Value, error) {
|
||||||
|
if s.AccessKeyID == "" || s.SecretAccessKey == "" {
|
||||||
|
return Value{}, ErrStaticCredentialsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExpired returns if the credentials are expired.
|
||||||
|
//
|
||||||
|
// For StaticProvider, the credentials never expired.
|
||||||
|
func (s *StaticProvider) IsExpired() bool {
|
||||||
|
return false
|
||||||
|
}
|
34
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider_test.go
generated
vendored
Normal file
34
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider_test.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStaticProviderGet(t *testing.T) {
|
||||||
|
s := StaticProvider{
|
||||||
|
Value: Value{
|
||||||
|
AccessKeyID: "AKID",
|
||||||
|
SecretAccessKey: "SECRET",
|
||||||
|
SessionToken: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := s.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match")
|
||||||
|
assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Empty(t, creds.SessionToken, "Expect no session token")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStaticProviderIsExpired(t *testing.T) {
|
||||||
|
s := StaticProvider{
|
||||||
|
Value: Value{
|
||||||
|
AccessKeyID: "AKID",
|
||||||
|
SecretAccessKey: "SECRET",
|
||||||
|
SessionToken: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, s.IsExpired(), "Expect static credentials to never expire")
|
||||||
|
}
|
120
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go
generated
vendored
Normal file
120
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
// Package stscreds are credential Providers to retrieve STS AWS credentials.
|
||||||
|
//
|
||||||
|
// STS provides multiple ways to retrieve credentials which can be used when making
|
||||||
|
// future AWS service API operation calls.
|
||||||
|
package stscreds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/service/sts"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AssumeRoler represents the minimal subset of the STS client API used by this provider.
|
||||||
|
type AssumeRoler interface {
|
||||||
|
AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
Client AssumeRoler
|
||||||
|
|
||||||
|
// Role to be assumed.
|
||||||
|
RoleARN string
|
||||||
|
|
||||||
|
// Session name, if you wish to reuse the credentials elsewhere.
|
||||||
|
RoleSessionName string
|
||||||
|
|
||||||
|
// Expiry duration of the STS credentials. Defaults to 15 minutes if not set.
|
||||||
|
Duration time.Duration
|
||||||
|
|
||||||
|
// ExpiryWindow will allow the credentials to trigger refreshing prior to
|
||||||
|
// the credentials actually expiring. This is beneficial so race conditions
|
||||||
|
// with expiring credentials do not cause request to fail unexpectedly
|
||||||
|
// due to ExpiredTokenException exceptions.
|
||||||
|
//
|
||||||
|
// So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
|
||||||
|
// 10 seconds before the credentials are actually expired.
|
||||||
|
//
|
||||||
|
// If ExpiryWindow is 0 or less it will be ignored.
|
||||||
|
ExpiryWindow time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCredentials 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.
|
||||||
|
//
|
||||||
|
// 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,
|
||||||
|
RoleARN: roleARN,
|
||||||
|
ExpiryWindow: window,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
roleOutput, err := p.Client.AssumeRole(&sts.AssumeRoleInput{
|
||||||
|
DurationSeconds: aws.Long(int64(p.Duration / time.Second)),
|
||||||
|
RoleARN: aws.String(p.RoleARN),
|
||||||
|
RoleSessionName: aws.String(p.RoleSessionName),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return credentials.Value{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will proactively generate new credentials before they expire.
|
||||||
|
p.SetExpiration(*roleOutput.Credentials.Expiration, p.ExpiryWindow)
|
||||||
|
|
||||||
|
return credentials.Value{
|
||||||
|
AccessKeyID: *roleOutput.Credentials.AccessKeyID,
|
||||||
|
SecretAccessKey: *roleOutput.Credentials.SecretAccessKey,
|
||||||
|
SessionToken: *roleOutput.Credentials.SessionToken,
|
||||||
|
}, nil
|
||||||
|
}
|
58
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider_test.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider_test.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package stscreds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/sts"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stubSTS struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubSTS) AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
|
||||||
|
expiry := time.Now().Add(60 * time.Minute)
|
||||||
|
return &sts.AssumeRoleOutput{
|
||||||
|
Credentials: &sts.Credentials{
|
||||||
|
// Just reflect the role arn to the provider.
|
||||||
|
AccessKeyID: input.RoleARN,
|
||||||
|
SecretAccessKey: aws.String("assumedSecretAccessKey"),
|
||||||
|
SessionToken: aws.String("assumedSessionToken"),
|
||||||
|
Expiration: &expiry,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssumeRoleProvider(t *testing.T) {
|
||||||
|
stub := &stubSTS{}
|
||||||
|
p := &AssumeRoleProvider{
|
||||||
|
Client: stub,
|
||||||
|
RoleARN: "roleARN",
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := p.Retrieve()
|
||||||
|
assert.Nil(t, err, "Expect no error")
|
||||||
|
|
||||||
|
assert.Equal(t, "roleARN", creds.AccessKeyID, "Expect access key ID to be reflected role ARN")
|
||||||
|
assert.Equal(t, "assumedSecretAccessKey", creds.SecretAccessKey, "Expect secret access key to match")
|
||||||
|
assert.Equal(t, "assumedSessionToken", creds.SessionToken, "Expect session token to match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAssumeRoleProvider(b *testing.B) {
|
||||||
|
stub := &stubSTS{}
|
||||||
|
p := &AssumeRoleProvider{
|
||||||
|
Client: stub,
|
||||||
|
RoleARN: "roleARN",
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_, err := p.Retrieve()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
153
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions.go
generated
vendored
Normal file
153
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions.go
generated
vendored
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sleepDelay = func(delay time.Duration) {
|
||||||
|
time.Sleep(delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface for matching types which also have a Len method.
|
||||||
|
type lener interface {
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildContentLength 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.
|
||||||
|
func BuildContentLength(r *Request) {
|
||||||
|
if slength := r.HTTPRequest.Header.Get("Content-Length"); slength != "" {
|
||||||
|
length, _ := strconv.ParseInt(slength, 10, 64)
|
||||||
|
r.HTTPRequest.ContentLength = length
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var length int64
|
||||||
|
switch body := r.Body.(type) {
|
||||||
|
case nil:
|
||||||
|
length = 0
|
||||||
|
case lener:
|
||||||
|
length = int64(body.Len())
|
||||||
|
case io.Seeker:
|
||||||
|
r.bodyStart, _ = body.Seek(0, 1)
|
||||||
|
end, _ := body.Seek(0, 2)
|
||||||
|
body.Seek(r.bodyStart, 0) // make sure to seek back to original location
|
||||||
|
length = end - r.bodyStart
|
||||||
|
default:
|
||||||
|
panic("Cannot get length of body, must provide `ContentLength`")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.HTTPRequest.ContentLength = length
|
||||||
|
r.HTTPRequest.Header.Set("Content-Length", fmt.Sprintf("%d", length))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgentHandler is a request handler for injecting User agent into requests.
|
||||||
|
func UserAgentHandler(r *Request) {
|
||||||
|
r.HTTPRequest.Header.Set("User-Agent", SDKName+"/"+SDKVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reStatusCode = regexp.MustCompile(`^(\d+)`)
|
||||||
|
|
||||||
|
// SendHandler is a request handler to send service request using HTTP client.
|
||||||
|
func SendHandler(r *Request) {
|
||||||
|
var err error
|
||||||
|
r.HTTPResponse, err = r.Service.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
|
||||||
|
// error and r.HTTPResponse is nil. Other url redirect errors will
|
||||||
|
// comeback in a similar method.
|
||||||
|
if e, ok := err.(*url.Error); ok {
|
||||||
|
if s := reStatusCode.FindStringSubmatch(e.Error()); s != nil {
|
||||||
|
code, _ := strconv.ParseInt(s[1], 10, 64)
|
||||||
|
r.HTTPResponse = &http.Response{
|
||||||
|
StatusCode: int(code),
|
||||||
|
Status: http.StatusText(int(code)),
|
||||||
|
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.HTTPRequest == nil {
|
||||||
|
// Add a dummy request response object to ensure the HTTPResponse
|
||||||
|
// value is consistent.
|
||||||
|
r.HTTPResponse = &http.Response{
|
||||||
|
StatusCode: int(0),
|
||||||
|
Status: http.StatusText(int(0)),
|
||||||
|
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Catch all other request errors.
|
||||||
|
r.Error = awserr.New("RequestError", "send request failed", err)
|
||||||
|
r.Retryable.Set(true) // network errors are retryable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateResponseHandler is a request handler to validate service response.
|
||||||
|
func ValidateResponseHandler(r *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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterRetryHandler performs final checks to determine if the request should
|
||||||
|
// be retried and how long to delay.
|
||||||
|
func AfterRetryHandler(r *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.IsSet() {
|
||||||
|
r.Retryable.Set(r.Service.ShouldRetry(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.WillRetry() {
|
||||||
|
r.RetryDelay = r.Service.RetryRules(r)
|
||||||
|
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.Error != nil {
|
||||||
|
if err, ok := r.Error.(awserr.Error); ok {
|
||||||
|
if isCodeExpiredCreds(err.Code()) {
|
||||||
|
r.Config.Credentials.Expire()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.RetryCount++
|
||||||
|
r.Error = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrMissingRegion is an error that is returned if region configuration is
|
||||||
|
// not found.
|
||||||
|
ErrMissingRegion error = 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.
|
||||||
|
ErrMissingEndpoint error = awserr.New("MissingEndpoint", "'Endpoint' configuration is required for this service", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func ValidateEndpointHandler(r *Request) {
|
||||||
|
if r.Service.SigningRegion == "" && r.Service.Config.Region == "" {
|
||||||
|
r.Error = ErrMissingRegion
|
||||||
|
} else if r.Service.Endpoint == "" {
|
||||||
|
r.Error = ErrMissingEndpoint
|
||||||
|
}
|
||||||
|
}
|
81
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions_test.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions_test.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateEndpointHandler(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
svc := NewService(&Config{Region: "us-west-2"})
|
||||||
|
svc.Handlers.Clear()
|
||||||
|
svc.Handlers.Validate.PushBack(ValidateEndpointHandler)
|
||||||
|
|
||||||
|
req := NewRequest(svc, &Operation{Name: "Operation"}, nil, nil)
|
||||||
|
err := req.Build()
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEndpointHandlerErrorRegion(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
svc := NewService(nil)
|
||||||
|
svc.Handlers.Clear()
|
||||||
|
svc.Handlers.Validate.PushBack(ValidateEndpointHandler)
|
||||||
|
|
||||||
|
req := NewRequest(svc, &Operation{Name: "Operation"}, nil, nil)
|
||||||
|
err := req.Build()
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, ErrMissingRegion, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockCredsProvider struct {
|
||||||
|
expired bool
|
||||||
|
retreiveCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCredsProvider) Retrieve() (credentials.Value, error) {
|
||||||
|
m.retreiveCalled = true
|
||||||
|
return credentials.Value{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCredsProvider) IsExpired() bool {
|
||||||
|
return m.expired
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAfterRetryRefreshCreds(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
credProvider := &mockCredsProvider{}
|
||||||
|
svc := NewService(&Config{Credentials: credentials.NewCredentials(credProvider), MaxRetries: 1})
|
||||||
|
|
||||||
|
svc.Handlers.Clear()
|
||||||
|
svc.Handlers.ValidateResponse.PushBack(func(r *Request) {
|
||||||
|
r.Error = awserr.New("UnknownError", "", nil)
|
||||||
|
r.HTTPResponse = &http.Response{StatusCode: 400}
|
||||||
|
})
|
||||||
|
svc.Handlers.UnmarshalError.PushBack(func(r *Request) {
|
||||||
|
r.Error = awserr.New("ExpiredTokenException", "", nil)
|
||||||
|
})
|
||||||
|
svc.Handlers.AfterRetry.PushBack(func(r *Request) {
|
||||||
|
AfterRetryHandler(r)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.True(t, svc.Config.Credentials.IsExpired(), "Expect to start out expired")
|
||||||
|
assert.False(t, credProvider.retreiveCalled)
|
||||||
|
|
||||||
|
req := NewRequest(svc, &Operation{Name: "Operation"}, nil, nil)
|
||||||
|
req.Send()
|
||||||
|
|
||||||
|
assert.True(t, svc.Config.Credentials.IsExpired())
|
||||||
|
assert.False(t, credProvider.retreiveCalled)
|
||||||
|
|
||||||
|
_, err := svc.Config.Credentials.Get()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, credProvider.retreiveCalled)
|
||||||
|
}
|
85
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers.go
generated
vendored
Normal file
85
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
// A Handlers provides a collection of request handlers for various
|
||||||
|
// stages of handling requests.
|
||||||
|
type Handlers struct {
|
||||||
|
Validate HandlerList
|
||||||
|
Build HandlerList
|
||||||
|
Sign HandlerList
|
||||||
|
Send HandlerList
|
||||||
|
ValidateResponse HandlerList
|
||||||
|
Unmarshal HandlerList
|
||||||
|
UnmarshalMeta HandlerList
|
||||||
|
UnmarshalError HandlerList
|
||||||
|
Retry HandlerList
|
||||||
|
AfterRetry HandlerList
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy returns of this handler's lists.
|
||||||
|
func (h *Handlers) copy() Handlers {
|
||||||
|
return Handlers{
|
||||||
|
Validate: h.Validate.copy(),
|
||||||
|
Build: h.Build.copy(),
|
||||||
|
Sign: h.Sign.copy(),
|
||||||
|
Send: h.Send.copy(),
|
||||||
|
ValidateResponse: h.ValidateResponse.copy(),
|
||||||
|
Unmarshal: h.Unmarshal.copy(),
|
||||||
|
UnmarshalError: h.UnmarshalError.copy(),
|
||||||
|
UnmarshalMeta: h.UnmarshalMeta.copy(),
|
||||||
|
Retry: h.Retry.copy(),
|
||||||
|
AfterRetry: h.AfterRetry.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes callback functions for all handlers
|
||||||
|
func (h *Handlers) Clear() {
|
||||||
|
h.Validate.Clear()
|
||||||
|
h.Build.Clear()
|
||||||
|
h.Send.Clear()
|
||||||
|
h.Sign.Clear()
|
||||||
|
h.Unmarshal.Clear()
|
||||||
|
h.UnmarshalMeta.Clear()
|
||||||
|
h.UnmarshalError.Clear()
|
||||||
|
h.ValidateResponse.Clear()
|
||||||
|
h.Retry.Clear()
|
||||||
|
h.AfterRetry.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A HandlerList manages zero or more handlers in a list.
|
||||||
|
type HandlerList struct {
|
||||||
|
list []func(*Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy creates a copy of the handler list.
|
||||||
|
func (l *HandlerList) copy() HandlerList {
|
||||||
|
var n HandlerList
|
||||||
|
n.list = append([]func(*Request){}, l.list...)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear clears the handler list.
|
||||||
|
func (l *HandlerList) Clear() {
|
||||||
|
l.list = []func(*Request){}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of handlers in the list.
|
||||||
|
func (l *HandlerList) Len() int {
|
||||||
|
return len(l.list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushBack pushes handlers f to the back of the handler list.
|
||||||
|
func (l *HandlerList) PushBack(f ...func(*Request)) {
|
||||||
|
l.list = append(l.list, f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushFront pushes handlers f to the front of the handler list.
|
||||||
|
func (l *HandlerList) PushFront(f ...func(*Request)) {
|
||||||
|
l.list = append(f, l.list...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes all handlers in the list with a given request object.
|
||||||
|
func (l *HandlerList) Run(r *Request) {
|
||||||
|
for _, f := range l.list {
|
||||||
|
f(r)
|
||||||
|
}
|
||||||
|
}
|
31
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers_test.go
generated
vendored
Normal file
31
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers_test.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandlerList(t *testing.T) {
|
||||||
|
s := ""
|
||||||
|
r := &Request{}
|
||||||
|
l := HandlerList{}
|
||||||
|
l.PushBack(func(r *Request) {
|
||||||
|
s += "a"
|
||||||
|
r.Data = s
|
||||||
|
})
|
||||||
|
l.Run(r)
|
||||||
|
assert.Equal(t, "a", s)
|
||||||
|
assert.Equal(t, "a", r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleHandlers(t *testing.T) {
|
||||||
|
r := &Request{}
|
||||||
|
l := HandlerList{}
|
||||||
|
l.PushBack(func(r *Request) { r.Data = nil })
|
||||||
|
l.PushFront(func(r *Request) { r.Data = Boolean(true) })
|
||||||
|
l.Run(r)
|
||||||
|
if r.Data != nil {
|
||||||
|
t.Error("Expected handler to execute")
|
||||||
|
}
|
||||||
|
}
|
89
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator.go
generated
vendored
Normal file
89
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateParameters is a request handler to validate the input parameters.
|
||||||
|
// Validating parameters only has meaning if done prior to the request being sent.
|
||||||
|
func ValidateParameters(r *Request) {
|
||||||
|
if r.ParamsFilled() {
|
||||||
|
v := validator{errors: []string{}}
|
||||||
|
v.validateAny(reflect.ValueOf(r.Params), "")
|
||||||
|
|
||||||
|
if count := len(v.errors); count > 0 {
|
||||||
|
format := "%d validation errors:\n- %s"
|
||||||
|
msg := fmt.Sprintf(format, count, strings.Join(v.errors, "\n- "))
|
||||||
|
r.Error = awserr.New("InvalidParameter", msg, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A validator validates values. Collects validations errors which occurs.
|
||||||
|
type validator struct {
|
||||||
|
errors []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAny will validate any struct, slice or map type. All validations
|
||||||
|
// are also performed recursively for nested types.
|
||||||
|
func (v *validator) validateAny(value reflect.Value, path string) {
|
||||||
|
value = reflect.Indirect(value)
|
||||||
|
if !value.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
v.validateStruct(value, path)
|
||||||
|
case reflect.Slice:
|
||||||
|
for i := 0; i < value.Len(); i++ {
|
||||||
|
v.validateAny(value.Index(i), path+fmt.Sprintf("[%d]", i))
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
for _, n := range value.MapKeys() {
|
||||||
|
v.validateAny(value.MapIndex(n), path+fmt.Sprintf("[%q]", n.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateStruct will validate the struct value's fields. If the structure has
|
||||||
|
// nested types those types will be validated also.
|
||||||
|
func (v *validator) validateStruct(value reflect.Value, path string) {
|
||||||
|
prefix := "."
|
||||||
|
if path == "" {
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < value.Type().NumField(); i++ {
|
||||||
|
f := value.Type().Field(i)
|
||||||
|
if strings.ToLower(f.Name[0:1]) == f.Name[0:1] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fvalue := value.FieldByName(f.Name)
|
||||||
|
|
||||||
|
notset := false
|
||||||
|
if f.Tag.Get("required") != "" {
|
||||||
|
switch fvalue.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Slice, reflect.Map:
|
||||||
|
if fvalue.IsNil() {
|
||||||
|
notset = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !fvalue.IsValid() {
|
||||||
|
notset = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if notset {
|
||||||
|
msg := "missing required parameter: " + path + prefix + f.Name
|
||||||
|
v.errors = append(v.errors, msg)
|
||||||
|
} else {
|
||||||
|
v.validateAny(fvalue, path+prefix+f.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator_test.go
generated
vendored
Normal file
84
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator_test.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var service = func() *aws.Service {
|
||||||
|
s := &aws.Service{
|
||||||
|
Config: &aws.Config{},
|
||||||
|
ServiceName: "mock-service",
|
||||||
|
APIVersion: "2015-01-01",
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}()
|
||||||
|
|
||||||
|
type StructShape struct {
|
||||||
|
RequiredList []*ConditionalStructShape `required:"true"`
|
||||||
|
RequiredMap map[string]*ConditionalStructShape `required:"true"`
|
||||||
|
RequiredBool *bool `required:"true"`
|
||||||
|
OptionalStruct *ConditionalStructShape
|
||||||
|
|
||||||
|
hiddenParameter *string
|
||||||
|
|
||||||
|
metadataStructureShape
|
||||||
|
}
|
||||||
|
|
||||||
|
type metadataStructureShape struct {
|
||||||
|
SDKShapeTraits bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConditionalStructShape struct {
|
||||||
|
Name *string `required:"true"`
|
||||||
|
SDKShapeTraits bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoErrors(t *testing.T) {
|
||||||
|
input := &StructShape{
|
||||||
|
RequiredList: []*ConditionalStructShape{},
|
||||||
|
RequiredMap: map[string]*ConditionalStructShape{
|
||||||
|
"key1": {Name: aws.String("Name")},
|
||||||
|
"key2": {Name: aws.String("Name")},
|
||||||
|
},
|
||||||
|
RequiredBool: aws.Boolean(true),
|
||||||
|
OptionalStruct: &ConditionalStructShape{Name: aws.String("Name")},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := aws.NewRequest(service, &aws.Operation{}, input, nil)
|
||||||
|
aws.ValidateParameters(req)
|
||||||
|
assert.NoError(t, req.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingRequiredParameters(t *testing.T) {
|
||||||
|
input := &StructShape{}
|
||||||
|
req := aws.NewRequest(service, &aws.Operation{}, input, nil)
|
||||||
|
aws.ValidateParameters(req)
|
||||||
|
|
||||||
|
assert.Error(t, req.Error)
|
||||||
|
assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
|
||||||
|
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList\n- missing required parameter: RequiredMap\n- missing required parameter: RequiredBool", req.Error.(awserr.Error).Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedMissingRequiredParameters(t *testing.T) {
|
||||||
|
input := &StructShape{
|
||||||
|
RequiredList: []*ConditionalStructShape{{}},
|
||||||
|
RequiredMap: map[string]*ConditionalStructShape{
|
||||||
|
"key1": {Name: aws.String("Name")},
|
||||||
|
"key2": {},
|
||||||
|
},
|
||||||
|
RequiredBool: aws.Boolean(true),
|
||||||
|
OptionalStruct: &ConditionalStructShape{},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := aws.NewRequest(service, &aws.Operation{}, input, nil)
|
||||||
|
aws.ValidateParameters(req)
|
||||||
|
|
||||||
|
assert.Error(t, req.Error)
|
||||||
|
assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
|
||||||
|
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList[0].Name\n- missing required parameter: RequiredMap[\"key2\"].Name\n- missing required parameter: OptionalStruct.Name", req.Error.(awserr.Error).Message())
|
||||||
|
|
||||||
|
}
|
312
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request.go
generated
vendored
Normal file
312
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request.go
generated
vendored
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Request is the service request to be made.
|
||||||
|
type Request struct {
|
||||||
|
*Service
|
||||||
|
Handlers Handlers
|
||||||
|
Time time.Time
|
||||||
|
ExpireTime time.Duration
|
||||||
|
Operation *Operation
|
||||||
|
HTTPRequest *http.Request
|
||||||
|
HTTPResponse *http.Response
|
||||||
|
Body io.ReadSeeker
|
||||||
|
bodyStart int64 // offset from beginning of Body that the request body starts
|
||||||
|
Params interface{}
|
||||||
|
Error error
|
||||||
|
Data interface{}
|
||||||
|
RequestID string
|
||||||
|
RetryCount uint
|
||||||
|
Retryable SettableBool
|
||||||
|
RetryDelay time.Duration
|
||||||
|
|
||||||
|
built bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Operation is the service API operation to be made.
|
||||||
|
type Operation struct {
|
||||||
|
Name string
|
||||||
|
HTTPMethod string
|
||||||
|
HTTPPath string
|
||||||
|
*Paginator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paginator keeps track of pagination configuration for an API operation.
|
||||||
|
type Paginator struct {
|
||||||
|
InputTokens []string
|
||||||
|
OutputTokens []string
|
||||||
|
LimitToken string
|
||||||
|
TruncationToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest returns a new Request pointer for the service API
|
||||||
|
// operation and parameters.
|
||||||
|
//
|
||||||
|
// 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 NewRequest(service *Service, operation *Operation, params interface{}, data interface{}) *Request {
|
||||||
|
method := operation.HTTPMethod
|
||||||
|
if method == "" {
|
||||||
|
method = "POST"
|
||||||
|
}
|
||||||
|
p := operation.HTTPPath
|
||||||
|
if p == "" {
|
||||||
|
p = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, _ := http.NewRequest(method, "", nil)
|
||||||
|
httpReq.URL, _ = url.Parse(service.Endpoint + p)
|
||||||
|
|
||||||
|
r := &Request{
|
||||||
|
Service: service,
|
||||||
|
Handlers: service.Handlers.copy(),
|
||||||
|
Time: time.Now(),
|
||||||
|
ExpireTime: 0,
|
||||||
|
Operation: operation,
|
||||||
|
HTTPRequest: httpReq,
|
||||||
|
Body: nil,
|
||||||
|
Params: params,
|
||||||
|
Error: nil,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
r.SetBufferBody([]byte{})
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// WillRetry returns if the request's can be retried.
|
||||||
|
func (r *Request) WillRetry() bool {
|
||||||
|
return r.Error != nil && r.Retryable.Get() && r.RetryCount < r.Service.MaxRetries()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamsFilled returns if the request's parameters have been populated
|
||||||
|
// and the parameters are valid. False is returned if no parameters are
|
||||||
|
// provided or invalid.
|
||||||
|
func (r *Request) ParamsFilled() bool {
|
||||||
|
return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataFilled returns true if the request's data for response deserialization
|
||||||
|
// target has been set and is a valid. False is returned if data is not
|
||||||
|
// set, or is invalid.
|
||||||
|
func (r *Request) DataFilled() bool {
|
||||||
|
return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBufferBody will set the request's body bytes that will be sent to
|
||||||
|
// the service API.
|
||||||
|
func (r *Request) SetBufferBody(buf []byte) {
|
||||||
|
r.SetReaderBody(bytes.NewReader(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStringBody sets the body of the request to be backed by a string.
|
||||||
|
func (r *Request) SetStringBody(s string) {
|
||||||
|
r.SetReaderBody(strings.NewReader(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReaderBody will set the request's body reader.
|
||||||
|
func (r *Request) SetReaderBody(reader io.ReadSeeker) {
|
||||||
|
r.HTTPRequest.Body = ioutil.NopCloser(reader)
|
||||||
|
r.Body = reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Presign returns the request's signed URL. Error will be returned
|
||||||
|
// if the signing fails.
|
||||||
|
func (r *Request) Presign(expireTime time.Duration) (string, error) {
|
||||||
|
r.ExpireTime = expireTime
|
||||||
|
r.Sign()
|
||||||
|
if r.Error != nil {
|
||||||
|
return "", r.Error
|
||||||
|
}
|
||||||
|
return r.HTTPRequest.URL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build will build the request's object so it can be signed and sent
|
||||||
|
// to the service. Build will also validate all the request's parameters.
|
||||||
|
// Anny additional build Handlers set on this request will be run
|
||||||
|
// in the order they were set.
|
||||||
|
//
|
||||||
|
// The request will only be built once. Multiple calls to build will have
|
||||||
|
// no effect.
|
||||||
|
//
|
||||||
|
// If any Validate or Build errors occur the build will stop and the error
|
||||||
|
// which occurred will be returned.
|
||||||
|
func (r *Request) Build() error {
|
||||||
|
if !r.built {
|
||||||
|
r.Error = nil
|
||||||
|
r.Handlers.Validate.Run(r)
|
||||||
|
if r.Error != nil {
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
r.Handlers.Build.Run(r)
|
||||||
|
r.built = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign will sign the request retuning error if errors are encountered.
|
||||||
|
//
|
||||||
|
// Send will build the request prior to signing. All Sign Handlers will
|
||||||
|
// be executed in the order they were set.
|
||||||
|
func (r *Request) Sign() error {
|
||||||
|
r.Build()
|
||||||
|
if r.Error != nil {
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Handlers.Sign.Run(r)
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send will send the request returning error if errors are encountered.
|
||||||
|
//
|
||||||
|
// Send will sign the request prior to sending. All Send Handlers will
|
||||||
|
// be executed in the order they were set.
|
||||||
|
func (r *Request) Send() error {
|
||||||
|
for {
|
||||||
|
r.Sign()
|
||||||
|
if r.Error != nil {
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Retryable.Get() {
|
||||||
|
// Re-seek the body back to the original point in for a retry so that
|
||||||
|
// send will send the body's contents again in the upcoming request.
|
||||||
|
r.Body.Seek(r.bodyStart, 0)
|
||||||
|
}
|
||||||
|
r.Retryable.Reset()
|
||||||
|
|
||||||
|
r.Handlers.Send.Run(r)
|
||||||
|
if r.Error != nil {
|
||||||
|
r.Handlers.Retry.Run(r)
|
||||||
|
r.Handlers.AfterRetry.Run(r)
|
||||||
|
if r.Error != nil {
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Handlers.UnmarshalMeta.Run(r)
|
||||||
|
r.Handlers.ValidateResponse.Run(r)
|
||||||
|
if r.Error != nil {
|
||||||
|
r.Handlers.UnmarshalError.Run(r)
|
||||||
|
r.Handlers.Retry.Run(r)
|
||||||
|
r.Handlers.AfterRetry.Run(r)
|
||||||
|
if r.Error != nil {
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Handlers.Unmarshal.Run(r)
|
||||||
|
if r.Error != nil {
|
||||||
|
r.Handlers.Retry.Run(r)
|
||||||
|
r.Handlers.AfterRetry.Run(r)
|
||||||
|
if r.Error != nil {
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := NewRequest(r.Service, 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
|
||||||
|
}
|
305
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_pagination_test.go
generated
vendored
Normal file
305
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_pagination_test.go
generated
vendored
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = unit.Imported
|
||||||
|
|
||||||
|
// Use DynamoDB methods for simplicity
|
||||||
|
func TestPagination(t *testing.T) {
|
||||||
|
db := dynamodb.New(nil)
|
||||||
|
tokens, pages, numPages, gotToEnd := []string{}, []string{}, 0, false
|
||||||
|
|
||||||
|
reqNum := 0
|
||||||
|
resps := []*dynamodb.ListTablesOutput{
|
||||||
|
{TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")},
|
||||||
|
{TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")},
|
||||||
|
{TableNames: []*string{aws.String("Table5")}},
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Handlers.Send.Clear() // mock sending
|
||||||
|
db.Handlers.Unmarshal.Clear()
|
||||||
|
db.Handlers.UnmarshalMeta.Clear()
|
||||||
|
db.Handlers.ValidateResponse.Clear()
|
||||||
|
db.Handlers.Build.PushBack(func(r *aws.Request) {
|
||||||
|
in := r.Params.(*dynamodb.ListTablesInput)
|
||||||
|
if in == nil {
|
||||||
|
tokens = append(tokens, "")
|
||||||
|
} else if in.ExclusiveStartTableName != nil {
|
||||||
|
tokens = append(tokens, *in.ExclusiveStartTableName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
db.Handlers.Unmarshal.PushBack(func(r *aws.Request) {
|
||||||
|
r.Data = resps[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
|
||||||
|
params := &dynamodb.ListTablesInput{Limit: aws.Long(2)}
|
||||||
|
err := db.ListTablesPages(params, func(p *dynamodb.ListTablesOutput, last bool) bool {
|
||||||
|
numPages++
|
||||||
|
for _, t := range p.TableNames {
|
||||||
|
pages = append(pages, *t)
|
||||||
|
}
|
||||||
|
if last {
|
||||||
|
if gotToEnd {
|
||||||
|
assert.Fail(t, "last=true happened twice")
|
||||||
|
}
|
||||||
|
gotToEnd = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"Table2", "Table4"}, tokens)
|
||||||
|
assert.Equal(t, []string{"Table1", "Table2", "Table3", "Table4", "Table5"}, pages)
|
||||||
|
assert.Equal(t, 3, numPages)
|
||||||
|
assert.True(t, gotToEnd)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Nil(t, params.ExclusiveStartTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use DynamoDB methods for simplicity
|
||||||
|
func TestPaginationEachPage(t *testing.T) {
|
||||||
|
db := dynamodb.New(nil)
|
||||||
|
tokens, pages, numPages, gotToEnd := []string{}, []string{}, 0, false
|
||||||
|
|
||||||
|
reqNum := 0
|
||||||
|
resps := []*dynamodb.ListTablesOutput{
|
||||||
|
{TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")},
|
||||||
|
{TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")},
|
||||||
|
{TableNames: []*string{aws.String("Table5")}},
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Handlers.Send.Clear() // mock sending
|
||||||
|
db.Handlers.Unmarshal.Clear()
|
||||||
|
db.Handlers.UnmarshalMeta.Clear()
|
||||||
|
db.Handlers.ValidateResponse.Clear()
|
||||||
|
db.Handlers.Build.PushBack(func(r *aws.Request) {
|
||||||
|
in := r.Params.(*dynamodb.ListTablesInput)
|
||||||
|
if in == nil {
|
||||||
|
tokens = append(tokens, "")
|
||||||
|
} else if in.ExclusiveStartTableName != nil {
|
||||||
|
tokens = append(tokens, *in.ExclusiveStartTableName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
db.Handlers.Unmarshal.PushBack(func(r *aws.Request) {
|
||||||
|
r.Data = resps[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
|
||||||
|
params := &dynamodb.ListTablesInput{Limit: aws.Long(2)}
|
||||||
|
req, _ := db.ListTablesRequest(params)
|
||||||
|
err := req.EachPage(func(p interface{}, last bool) bool {
|
||||||
|
numPages++
|
||||||
|
for _, t := range p.(*dynamodb.ListTablesOutput).TableNames {
|
||||||
|
pages = append(pages, *t)
|
||||||
|
}
|
||||||
|
if last {
|
||||||
|
if gotToEnd {
|
||||||
|
assert.Fail(t, "last=true happened twice")
|
||||||
|
}
|
||||||
|
gotToEnd = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"Table2", "Table4"}, tokens)
|
||||||
|
assert.Equal(t, []string{"Table1", "Table2", "Table3", "Table4", "Table5"}, pages)
|
||||||
|
assert.Equal(t, 3, numPages)
|
||||||
|
assert.True(t, gotToEnd)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use DynamoDB methods for simplicity
|
||||||
|
func TestPaginationEarlyExit(t *testing.T) {
|
||||||
|
db := dynamodb.New(nil)
|
||||||
|
numPages, gotToEnd := 0, false
|
||||||
|
|
||||||
|
reqNum := 0
|
||||||
|
resps := []*dynamodb.ListTablesOutput{
|
||||||
|
{TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")},
|
||||||
|
{TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")},
|
||||||
|
{TableNames: []*string{aws.String("Table5")}},
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Handlers.Send.Clear() // mock sending
|
||||||
|
db.Handlers.Unmarshal.Clear()
|
||||||
|
db.Handlers.UnmarshalMeta.Clear()
|
||||||
|
db.Handlers.ValidateResponse.Clear()
|
||||||
|
db.Handlers.Unmarshal.PushBack(func(r *aws.Request) {
|
||||||
|
r.Data = resps[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
|
||||||
|
params := &dynamodb.ListTablesInput{Limit: aws.Long(2)}
|
||||||
|
err := db.ListTablesPages(params, func(p *dynamodb.ListTablesOutput, last bool) bool {
|
||||||
|
numPages++
|
||||||
|
if numPages == 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if last {
|
||||||
|
if gotToEnd {
|
||||||
|
assert.Fail(t, "last=true happened twice")
|
||||||
|
}
|
||||||
|
gotToEnd = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, 2, numPages)
|
||||||
|
assert.False(t, gotToEnd)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipPagination(t *testing.T) {
|
||||||
|
client := s3.New(nil)
|
||||||
|
client.Handlers.Send.Clear() // mock sending
|
||||||
|
client.Handlers.Unmarshal.Clear()
|
||||||
|
client.Handlers.UnmarshalMeta.Clear()
|
||||||
|
client.Handlers.ValidateResponse.Clear()
|
||||||
|
client.Handlers.Unmarshal.PushBack(func(r *aws.Request) {
|
||||||
|
r.Data = &s3.HeadBucketOutput{}
|
||||||
|
})
|
||||||
|
|
||||||
|
req, _ := client.HeadBucketRequest(&s3.HeadBucketInput{Bucket: aws.String("bucket")})
|
||||||
|
|
||||||
|
numPages, gotToEnd := 0, false
|
||||||
|
req.EachPage(func(p interface{}, last bool) bool {
|
||||||
|
numPages++
|
||||||
|
if last {
|
||||||
|
gotToEnd = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 1, numPages)
|
||||||
|
assert.True(t, gotToEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use S3 for simplicity
|
||||||
|
func TestPaginationTruncation(t *testing.T) {
|
||||||
|
count := 0
|
||||||
|
client := s3.New(nil)
|
||||||
|
|
||||||
|
reqNum := &count
|
||||||
|
resps := []*s3.ListObjectsOutput{
|
||||||
|
{IsTruncated: aws.Boolean(true), Contents: []*s3.Object{{Key: aws.String("Key1")}}},
|
||||||
|
{IsTruncated: aws.Boolean(true), Contents: []*s3.Object{{Key: aws.String("Key2")}}},
|
||||||
|
{IsTruncated: aws.Boolean(false), Contents: []*s3.Object{{Key: aws.String("Key3")}}},
|
||||||
|
{IsTruncated: aws.Boolean(true), Contents: []*s3.Object{{Key: aws.String("Key4")}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Handlers.Send.Clear() // mock sending
|
||||||
|
client.Handlers.Unmarshal.Clear()
|
||||||
|
client.Handlers.UnmarshalMeta.Clear()
|
||||||
|
client.Handlers.ValidateResponse.Clear()
|
||||||
|
client.Handlers.Unmarshal.PushBack(func(r *aws.Request) {
|
||||||
|
r.Data = resps[*reqNum]
|
||||||
|
*reqNum++
|
||||||
|
})
|
||||||
|
|
||||||
|
params := &s3.ListObjectsInput{Bucket: aws.String("bucket")}
|
||||||
|
|
||||||
|
results := []string{}
|
||||||
|
err := client.ListObjectsPages(params, func(p *s3.ListObjectsOutput, last bool) bool {
|
||||||
|
results = append(results, *p.Contents[0].Key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"Key1", "Key2", "Key3"}, results)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// Try again without truncation token at all
|
||||||
|
count = 0
|
||||||
|
resps[1].IsTruncated = nil
|
||||||
|
resps[2].IsTruncated = aws.Boolean(true)
|
||||||
|
results = []string{}
|
||||||
|
err = client.ListObjectsPages(params, func(p *s3.ListObjectsOutput, last bool) bool {
|
||||||
|
results = append(results, *p.Contents[0].Key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"Key1", "Key2"}, results)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks
|
||||||
|
var benchResps = []*dynamodb.ListTablesOutput{
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
|
||||||
|
{TableNames: []*string{aws.String("TABLE")}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var benchDb = func() *dynamodb.DynamoDB {
|
||||||
|
db := dynamodb.New(nil)
|
||||||
|
db.Handlers.Send.Clear() // mock sending
|
||||||
|
db.Handlers.Unmarshal.Clear()
|
||||||
|
db.Handlers.UnmarshalMeta.Clear()
|
||||||
|
db.Handlers.ValidateResponse.Clear()
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCodegenIterator(b *testing.B) {
|
||||||
|
reqNum := 0
|
||||||
|
db := benchDb()
|
||||||
|
db.Handlers.Unmarshal.PushBack(func(r *aws.Request) {
|
||||||
|
r.Data = benchResps[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
|
||||||
|
input := &dynamodb.ListTablesInput{Limit: aws.Long(2)}
|
||||||
|
iter := func(fn func(*dynamodb.ListTablesOutput, bool) bool) error {
|
||||||
|
page, _ := db.ListTablesRequest(input)
|
||||||
|
for ; page != nil; page = page.NextPage() {
|
||||||
|
page.Send()
|
||||||
|
out := page.Data.(*dynamodb.ListTablesOutput)
|
||||||
|
if result := fn(out, !page.HasNextPage()); page.Error != nil || !result {
|
||||||
|
return page.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
reqNum = 0
|
||||||
|
iter(func(p *dynamodb.ListTablesOutput, last bool) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEachPageIterator(b *testing.B) {
|
||||||
|
reqNum := 0
|
||||||
|
db := benchDb()
|
||||||
|
db.Handlers.Unmarshal.PushBack(func(r *aws.Request) {
|
||||||
|
r.Data = benchResps[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
|
||||||
|
input := &dynamodb.ListTablesInput{Limit: aws.Long(2)}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
reqNum = 0
|
||||||
|
req, _ := db.ListTablesRequest(input)
|
||||||
|
req.EachPage(func(p interface{}, last bool) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
219
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_test.go
generated
vendored
Normal file
219
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_test.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testData struct {
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
func body(str string) io.ReadCloser {
|
||||||
|
return ioutil.NopCloser(bytes.NewReader([]byte(str)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshal(req *Request) {
|
||||||
|
defer req.HTTPResponse.Body.Close()
|
||||||
|
if req.Data != nil {
|
||||||
|
json.NewDecoder(req.HTTPResponse.Body).Decode(req.Data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalError(req *Request) {
|
||||||
|
bodyBytes, err := ioutil.ReadAll(req.HTTPResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
req.Error = awserr.New("UnmarshaleError", req.HTTPResponse.Status, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(bodyBytes) == 0 {
|
||||||
|
req.Error = awserr.NewRequestFailure(
|
||||||
|
awserr.New("UnmarshaleError", req.HTTPResponse.Status, fmt.Errorf("empty body")),
|
||||||
|
req.HTTPResponse.StatusCode,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var jsonErr jsonErrorResponse
|
||||||
|
if err := json.Unmarshal(bodyBytes, &jsonErr); err != nil {
|
||||||
|
req.Error = awserr.New("UnmarshaleError", "JSON unmarshal", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Error = awserr.NewRequestFailure(
|
||||||
|
awserr.New(jsonErr.Code, jsonErr.Message, nil),
|
||||||
|
req.HTTPResponse.StatusCode,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonErrorResponse struct {
|
||||||
|
Code string `json:"__type"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that retries occur for 5xx status codes
|
||||||
|
func TestRequestRecoverRetry5xx(t *testing.T) {
|
||||||
|
reqNum := 0
|
||||||
|
reqs := []http.Response{
|
||||||
|
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||||
|
{StatusCode: 501, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||||
|
{StatusCode: 200, Body: body(`{"data":"valid"}`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := NewService(&Config{MaxRetries: 10})
|
||||||
|
s.Handlers.Validate.Clear()
|
||||||
|
s.Handlers.Unmarshal.PushBack(unmarshal)
|
||||||
|
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||||
|
s.Handlers.Send.Clear() // mock sending
|
||||||
|
s.Handlers.Send.PushBack(func(r *Request) {
|
||||||
|
r.HTTPResponse = &reqs[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
out := &testData{}
|
||||||
|
r := NewRequest(s, &Operation{Name: "Operation"}, nil, out)
|
||||||
|
err := r.Send()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 2, int(r.RetryCount))
|
||||||
|
assert.Equal(t, "valid", out.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that retries occur for 4xx status codes with a response type that can be retried - see `shouldRetry`
|
||||||
|
func TestRequestRecoverRetry4xxRetryable(t *testing.T) {
|
||||||
|
reqNum := 0
|
||||||
|
reqs := []http.Response{
|
||||||
|
{StatusCode: 400, Body: body(`{"__type":"Throttling","message":"Rate exceeded."}`)},
|
||||||
|
{StatusCode: 429, Body: body(`{"__type":"ProvisionedThroughputExceededException","message":"Rate exceeded."}`)},
|
||||||
|
{StatusCode: 200, Body: body(`{"data":"valid"}`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := NewService(&Config{MaxRetries: 10})
|
||||||
|
s.Handlers.Validate.Clear()
|
||||||
|
s.Handlers.Unmarshal.PushBack(unmarshal)
|
||||||
|
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||||
|
s.Handlers.Send.Clear() // mock sending
|
||||||
|
s.Handlers.Send.PushBack(func(r *Request) {
|
||||||
|
r.HTTPResponse = &reqs[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
out := &testData{}
|
||||||
|
r := NewRequest(s, &Operation{Name: "Operation"}, nil, out)
|
||||||
|
err := r.Send()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 2, int(r.RetryCount))
|
||||||
|
assert.Equal(t, "valid", out.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that retries don't occur for 4xx status codes with a response type that can't be retried
|
||||||
|
func TestRequest4xxUnretryable(t *testing.T) {
|
||||||
|
s := NewService(&Config{MaxRetries: 10})
|
||||||
|
s.Handlers.Validate.Clear()
|
||||||
|
s.Handlers.Unmarshal.PushBack(unmarshal)
|
||||||
|
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||||
|
s.Handlers.Send.Clear() // mock sending
|
||||||
|
s.Handlers.Send.PushBack(func(r *Request) {
|
||||||
|
r.HTTPResponse = &http.Response{StatusCode: 401, Body: body(`{"__type":"SignatureDoesNotMatch","message":"Signature does not match."}`)}
|
||||||
|
})
|
||||||
|
out := &testData{}
|
||||||
|
r := NewRequest(s, &Operation{Name: "Operation"}, nil, out)
|
||||||
|
err := r.Send()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
if e, ok := err.(awserr.RequestFailure); ok {
|
||||||
|
assert.Equal(t, 401, e.StatusCode())
|
||||||
|
} else {
|
||||||
|
assert.Fail(t, "Expected error to be a service failure")
|
||||||
|
}
|
||||||
|
assert.Equal(t, "SignatureDoesNotMatch", err.(awserr.Error).Code())
|
||||||
|
assert.Equal(t, "Signature does not match.", err.(awserr.Error).Message())
|
||||||
|
assert.Equal(t, 0, int(r.RetryCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestExhaustRetries(t *testing.T) {
|
||||||
|
delays := []time.Duration{}
|
||||||
|
sleepDelay = func(delay time.Duration) {
|
||||||
|
delays = append(delays, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqNum := 0
|
||||||
|
reqs := []http.Response{
|
||||||
|
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||||
|
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||||
|
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||||
|
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := NewService(&Config{MaxRetries: -1})
|
||||||
|
s.Handlers.Validate.Clear()
|
||||||
|
s.Handlers.Unmarshal.PushBack(unmarshal)
|
||||||
|
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||||
|
s.Handlers.Send.Clear() // mock sending
|
||||||
|
s.Handlers.Send.PushBack(func(r *Request) {
|
||||||
|
r.HTTPResponse = &reqs[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
r := NewRequest(s, &Operation{Name: "Operation"}, nil, nil)
|
||||||
|
err := r.Send()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
if e, ok := err.(awserr.RequestFailure); ok {
|
||||||
|
assert.Equal(t, 500, e.StatusCode())
|
||||||
|
} else {
|
||||||
|
assert.Fail(t, "Expected error to be a service failure")
|
||||||
|
}
|
||||||
|
assert.Equal(t, "UnknownError", err.(awserr.Error).Code())
|
||||||
|
assert.Equal(t, "An error occurred.", err.(awserr.Error).Message())
|
||||||
|
assert.Equal(t, 3, int(r.RetryCount))
|
||||||
|
assert.True(t, reflect.DeepEqual([]time.Duration{30 * time.Millisecond, 60 * time.Millisecond, 120 * time.Millisecond}, delays))
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that the request is retried after the credentials are expired.
|
||||||
|
func TestRequestRecoverExpiredCreds(t *testing.T) {
|
||||||
|
reqNum := 0
|
||||||
|
reqs := []http.Response{
|
||||||
|
{StatusCode: 400, Body: body(`{"__type":"ExpiredTokenException","message":"expired token"}`)},
|
||||||
|
{StatusCode: 200, Body: body(`{"data":"valid"}`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := NewService(&Config{MaxRetries: 10, Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "")})
|
||||||
|
s.Handlers.Validate.Clear()
|
||||||
|
s.Handlers.Unmarshal.PushBack(unmarshal)
|
||||||
|
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||||
|
|
||||||
|
credExpiredBeforeRetry := false
|
||||||
|
credExpiredAfterRetry := false
|
||||||
|
|
||||||
|
s.Handlers.AfterRetry.PushBack(func(r *Request) {
|
||||||
|
credExpiredAfterRetry = r.Config.Credentials.IsExpired()
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Handlers.Sign.Clear()
|
||||||
|
s.Handlers.Sign.PushBack(func(r *Request) {
|
||||||
|
r.Config.Credentials.Get()
|
||||||
|
})
|
||||||
|
s.Handlers.Send.Clear() // mock sending
|
||||||
|
s.Handlers.Send.PushBack(func(r *Request) {
|
||||||
|
r.HTTPResponse = &reqs[reqNum]
|
||||||
|
reqNum++
|
||||||
|
})
|
||||||
|
out := &testData{}
|
||||||
|
r := NewRequest(s, &Operation{Name: "Operation"}, nil, out)
|
||||||
|
err := r.Send()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.False(t, credExpiredBeforeRetry, "Expect valid creds before retry check")
|
||||||
|
assert.True(t, credExpiredAfterRetry, "Expect expired creds after retry check")
|
||||||
|
assert.False(t, s.Config.Credentials.IsExpired(), "Expect valid creds after cred expired recovery")
|
||||||
|
|
||||||
|
assert.Equal(t, 1, int(r.RetryCount))
|
||||||
|
assert.Equal(t, "valid", out.Data)
|
||||||
|
}
|
177
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/service.go
generated
vendored
Normal file
177
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/service.go
generated
vendored
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/endpoints"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Service implements the base service request and response handling
|
||||||
|
// used by all services.
|
||||||
|
type Service struct {
|
||||||
|
Config *Config
|
||||||
|
Handlers Handlers
|
||||||
|
ManualSend bool
|
||||||
|
ServiceName string
|
||||||
|
APIVersion string
|
||||||
|
Endpoint string
|
||||||
|
SigningName string
|
||||||
|
SigningRegion string
|
||||||
|
JSONVersion string
|
||||||
|
TargetPrefix string
|
||||||
|
RetryRules func(*Request) time.Duration
|
||||||
|
ShouldRetry func(*Request) bool
|
||||||
|
DefaultMaxRetries uint
|
||||||
|
}
|
||||||
|
|
||||||
|
var schemeRE = regexp.MustCompile("^([^:]+)://")
|
||||||
|
|
||||||
|
// NewService will return a pointer to a new Server object initialized.
|
||||||
|
func NewService(config *Config) *Service {
|
||||||
|
svc := &Service{Config: config}
|
||||||
|
svc.Initialize()
|
||||||
|
return svc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes the service.
|
||||||
|
func (s *Service) Initialize() {
|
||||||
|
if s.Config == nil {
|
||||||
|
s.Config = &Config{}
|
||||||
|
}
|
||||||
|
if s.Config.HTTPClient == nil {
|
||||||
|
s.Config.HTTPClient = http.DefaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.RetryRules == nil {
|
||||||
|
s.RetryRules = retryRules
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ShouldRetry == nil {
|
||||||
|
s.ShouldRetry = shouldRetry
|
||||||
|
}
|
||||||
|
|
||||||
|
s.DefaultMaxRetries = 3
|
||||||
|
s.Handlers.Validate.PushBack(ValidateEndpointHandler)
|
||||||
|
s.Handlers.Build.PushBack(UserAgentHandler)
|
||||||
|
s.Handlers.Sign.PushBack(BuildContentLength)
|
||||||
|
s.Handlers.Send.PushBack(SendHandler)
|
||||||
|
s.Handlers.AfterRetry.PushBack(AfterRetryHandler)
|
||||||
|
s.Handlers.ValidateResponse.PushBack(ValidateResponseHandler)
|
||||||
|
s.AddDebugHandlers()
|
||||||
|
s.buildEndpoint()
|
||||||
|
|
||||||
|
if !s.Config.DisableParamValidation {
|
||||||
|
s.Handlers.Validate.PushBack(ValidateParameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildEndpoint builds the endpoint values the service will use to make requests with.
|
||||||
|
func (s *Service) buildEndpoint() {
|
||||||
|
if s.Config.Endpoint != "" {
|
||||||
|
s.Endpoint = s.Config.Endpoint
|
||||||
|
} else {
|
||||||
|
s.Endpoint, s.SigningRegion =
|
||||||
|
endpoints.EndpointForRegion(s.ServiceName, s.Config.Region)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Endpoint != "" && !schemeRE.MatchString(s.Endpoint) {
|
||||||
|
scheme := "https"
|
||||||
|
if 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() {
|
||||||
|
out := s.Config.Logger
|
||||||
|
if s.Config.LogLevel == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Handlers.Send.PushFront(func(r *Request) {
|
||||||
|
logBody := r.Config.LogHTTPBody
|
||||||
|
dumpedBody, _ := httputil.DumpRequestOut(r.HTTPRequest, logBody)
|
||||||
|
|
||||||
|
fmt.Fprintf(out, "---[ REQUEST POST-SIGN ]-----------------------------\n")
|
||||||
|
fmt.Fprintf(out, "%s\n", string(dumpedBody))
|
||||||
|
fmt.Fprintf(out, "-----------------------------------------------------\n")
|
||||||
|
})
|
||||||
|
s.Handlers.Send.PushBack(func(r *Request) {
|
||||||
|
fmt.Fprintf(out, "---[ RESPONSE ]--------------------------------------\n")
|
||||||
|
if r.HTTPResponse != nil {
|
||||||
|
logBody := r.Config.LogHTTPBody
|
||||||
|
dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, logBody)
|
||||||
|
fmt.Fprintf(out, "%s\n", string(dumpedBody))
|
||||||
|
} else if r.Error != nil {
|
||||||
|
fmt.Fprintf(out, "%s\n", r.Error)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(out, "-----------------------------------------------------\n")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxRetries returns the number of maximum returns the service will use to make
|
||||||
|
// an individual API request.
|
||||||
|
func (s *Service) MaxRetries() uint {
|
||||||
|
if s.Config.MaxRetries < 0 {
|
||||||
|
return s.DefaultMaxRetries
|
||||||
|
}
|
||||||
|
return uint(s.Config.MaxRetries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryRules returns the delay duration before retrying this request again
|
||||||
|
func retryRules(r *Request) time.Duration {
|
||||||
|
delay := time.Duration(math.Pow(2, float64(r.RetryCount))) * 30
|
||||||
|
return delay * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryableCodes is a collection of service response codes which are retry-able
|
||||||
|
// without any further action.
|
||||||
|
var retryableCodes = map[string]struct{}{
|
||||||
|
"RequestError": {},
|
||||||
|
"ProvisionedThroughputExceededException": {},
|
||||||
|
"Throttling": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// credsExpiredCodes is a collection of error codes which signify the credentials
|
||||||
|
// need to be refreshed. Expired tokens require refreshing of credentials, and
|
||||||
|
// resigning before the request can be retried.
|
||||||
|
var credsExpiredCodes = map[string]struct{}{
|
||||||
|
"ExpiredToken": {},
|
||||||
|
"ExpiredTokenException": {},
|
||||||
|
"RequestExpired": {}, // EC2 Only
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCodeRetryable(code string) bool {
|
||||||
|
if _, ok := retryableCodes[code]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return isCodeExpiredCreds(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCodeExpiredCreds(code string) bool {
|
||||||
|
_, ok := credsExpiredCodes[code]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldRetry returns if the request should be retried.
|
||||||
|
func shouldRetry(r *Request) bool {
|
||||||
|
if r.HTTPResponse.StatusCode >= 500 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if r.Error != nil {
|
||||||
|
if err, ok := r.Error.(awserr.Error); ok {
|
||||||
|
return isCodeRetryable(err.Code())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
131
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/types.go
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/types.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// String converts a Go string into a string pointer.
|
||||||
|
func String(v string) *string {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean converts a Go bool into a boolean pointer.
|
||||||
|
func Boolean(v bool) *bool {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long converts a Go int64 into a long pointer.
|
||||||
|
func Long(v int64) *int64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double converts a Go float64 into a double pointer.
|
||||||
|
func Double(v float64) *float64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time converts a Go Time into a Time pointer
|
||||||
|
func Time(t time.Time) *time.Time {
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSeekCloser wraps a io.Reader returning a ReaderSeakerCloser
|
||||||
|
func ReadSeekCloser(r io.Reader) ReaderSeekerCloser {
|
||||||
|
return ReaderSeekerCloser{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReaderSeekerCloser represents a reader that can also delegate io.Seeker and
|
||||||
|
// io.Closer interfaces to the underlying object if they are available.
|
||||||
|
type ReaderSeekerCloser struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from the reader up to size of p. The number of bytes read, and
|
||||||
|
// error if it occurred will be returned.
|
||||||
|
//
|
||||||
|
// If the reader is not an io.Reader zero bytes read, and nil error will be returned.
|
||||||
|
//
|
||||||
|
// Performs the same functionality as io.Reader Read
|
||||||
|
func (r ReaderSeekerCloser) Read(p []byte) (int, error) {
|
||||||
|
switch t := r.r.(type) {
|
||||||
|
case io.Reader:
|
||||||
|
return t.Read(p)
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek sets the offset for the next Read to offset, interpreted according to
|
||||||
|
// whence: 0 means relative to the origin of the file, 1 means relative to the
|
||||||
|
// current offset, and 2 means relative to the end. Seek returns the new offset
|
||||||
|
// and an error, if any.
|
||||||
|
//
|
||||||
|
// If the ReaderSeekerCloser is not an io.Seeker nothing will be done.
|
||||||
|
func (r ReaderSeekerCloser) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
switch t := r.r.(type) {
|
||||||
|
case io.Seeker:
|
||||||
|
return t.Seek(offset, whence)
|
||||||
|
}
|
||||||
|
return int64(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the ReaderSeekerCloser.
|
||||||
|
//
|
||||||
|
// If the ReaderSeekerCloser is not an io.Closer nothing will be done.
|
||||||
|
func (r ReaderSeekerCloser) Close() error {
|
||||||
|
switch t := r.r.(type) {
|
||||||
|
case io.Closer:
|
||||||
|
return t.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SettableBool provides a boolean value which includes the state if
|
||||||
|
// the value was set or unset. The set state is in addition to the value's
|
||||||
|
// value(true|false)
|
||||||
|
type SettableBool struct {
|
||||||
|
value bool
|
||||||
|
set bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBool returns a SettableBool with a value set
|
||||||
|
func SetBool(value bool) SettableBool {
|
||||||
|
return SettableBool{value: value, set: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value. Will always be false if the SettableBool was not set.
|
||||||
|
func (b *SettableBool) Get() bool {
|
||||||
|
if !b.set {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return b.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value and updates the state that the value has been set.
|
||||||
|
func (b *SettableBool) Set(value bool) {
|
||||||
|
b.value = value
|
||||||
|
b.set = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns if the value has been set
|
||||||
|
func (b *SettableBool) IsSet() bool {
|
||||||
|
return b.set
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the state and value of the SettableBool to its initial default
|
||||||
|
// state of not set and zero value.
|
||||||
|
func (b *SettableBool) Reset() {
|
||||||
|
b.value = false
|
||||||
|
b.set = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the value if set. Zero if not set.
|
||||||
|
func (b *SettableBool) String() string {
|
||||||
|
return fmt.Sprintf("%t", b.Get())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString returns the string representation of the SettableBool value and state
|
||||||
|
func (b *SettableBool) GoString() string {
|
||||||
|
return fmt.Sprintf("Bool{value:%t, set:%t}", b.value, b.set)
|
||||||
|
}
|
8
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go
generated
vendored
Normal file
8
Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Package aws provides core functionality for making requests to AWS services.
|
||||||
|
package aws
|
||||||
|
|
||||||
|
// SDKName is the name of this AWS SDK
|
||||||
|
const SDKName = "aws-sdk-go"
|
||||||
|
|
||||||
|
// SDKVersion is the version of this SDK
|
||||||
|
const SDKVersion = "0.6.4"
|
31
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.go
generated
vendored
Normal file
31
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Package endpoints validates regional endpoints for services.
|
||||||
|
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"
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
derivedKeys := []string{
|
||||||
|
region + "/" + svcName,
|
||||||
|
region + "/*",
|
||||||
|
"*/" + svcName,
|
||||||
|
"*/*",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range derivedKeys {
|
||||||
|
if val, ok := endpointsMap.Endpoints[key]; ok {
|
||||||
|
ep := val.Endpoint
|
||||||
|
ep = strings.Replace(ep, "{region}", region, -1)
|
||||||
|
ep = strings.Replace(ep, "{service}", svcName, -1)
|
||||||
|
|
||||||
|
endpoint = ep
|
||||||
|
signingRegion = val.SigningRegion
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
77
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.json
generated
vendored
Normal file
77
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.json
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"endpoints": {
|
||||||
|
"*/*": {
|
||||||
|
"endpoint": "{service}.{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"cn-north-1/*": {
|
||||||
|
"endpoint": "{service}.{region}.amazonaws.com.cn",
|
||||||
|
"signatureVersion": "v4"
|
||||||
|
},
|
||||||
|
"us-gov-west-1/iam": {
|
||||||
|
"endpoint": "iam.us-gov.amazonaws.com"
|
||||||
|
},
|
||||||
|
"us-gov-west-1/sts": {
|
||||||
|
"endpoint": "sts.us-gov-west-1.amazonaws.com"
|
||||||
|
},
|
||||||
|
"us-gov-west-1/s3": {
|
||||||
|
"endpoint": "s3-{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"*/cloudfront": {
|
||||||
|
"endpoint": "cloudfront.amazonaws.com",
|
||||||
|
"signingRegion": "us-east-1"
|
||||||
|
},
|
||||||
|
"*/cloudsearchdomain": {
|
||||||
|
"endpoint": "",
|
||||||
|
"signingRegion": "us-east-1"
|
||||||
|
},
|
||||||
|
"*/iam": {
|
||||||
|
"endpoint": "iam.amazonaws.com",
|
||||||
|
"signingRegion": "us-east-1"
|
||||||
|
},
|
||||||
|
"*/importexport": {
|
||||||
|
"endpoint": "importexport.amazonaws.com",
|
||||||
|
"signingRegion": "us-east-1"
|
||||||
|
},
|
||||||
|
"*/route53": {
|
||||||
|
"endpoint": "route53.amazonaws.com",
|
||||||
|
"signingRegion": "us-east-1"
|
||||||
|
},
|
||||||
|
"*/sts": {
|
||||||
|
"endpoint": "sts.amazonaws.com",
|
||||||
|
"signingRegion": "us-east-1"
|
||||||
|
},
|
||||||
|
"us-east-1/sdb": {
|
||||||
|
"endpoint": "sdb.amazonaws.com",
|
||||||
|
"signingRegion": "us-east-1"
|
||||||
|
},
|
||||||
|
"us-east-1/s3": {
|
||||||
|
"endpoint": "s3.amazonaws.com"
|
||||||
|
},
|
||||||
|
"us-west-1/s3": {
|
||||||
|
"endpoint": "s3-{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"us-west-2/s3": {
|
||||||
|
"endpoint": "s3-{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"eu-west-1/s3": {
|
||||||
|
"endpoint": "s3-{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"ap-southeast-1/s3": {
|
||||||
|
"endpoint": "s3-{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"ap-southeast-2/s3": {
|
||||||
|
"endpoint": "s3-{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"ap-northeast-1/s3": {
|
||||||
|
"endpoint": "s3-{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"sa-east-1/s3": {
|
||||||
|
"endpoint": "s3-{region}.amazonaws.com"
|
||||||
|
},
|
||||||
|
"eu-central-1/s3": {
|
||||||
|
"endpoint": "{service}.{region}.amazonaws.com",
|
||||||
|
"signatureVersion": "v4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_map.go
generated
vendored
Normal file
89
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_map.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||||
|
|
||||||
|
type endpointStruct struct {
|
||||||
|
Version int
|
||||||
|
Endpoints map[string]endpointEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type endpointEntry struct {
|
||||||
|
Endpoint string
|
||||||
|
SigningRegion string
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpointsMap = endpointStruct{
|
||||||
|
Version: 2,
|
||||||
|
Endpoints: map[string]endpointEntry{
|
||||||
|
"*/*": {
|
||||||
|
Endpoint: "{service}.{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"*/cloudfront": {
|
||||||
|
Endpoint: "cloudfront.amazonaws.com",
|
||||||
|
SigningRegion: "us-east-1",
|
||||||
|
},
|
||||||
|
"*/cloudsearchdomain": {
|
||||||
|
Endpoint: "",
|
||||||
|
SigningRegion: "us-east-1",
|
||||||
|
},
|
||||||
|
"*/iam": {
|
||||||
|
Endpoint: "iam.amazonaws.com",
|
||||||
|
SigningRegion: "us-east-1",
|
||||||
|
},
|
||||||
|
"*/importexport": {
|
||||||
|
Endpoint: "importexport.amazonaws.com",
|
||||||
|
SigningRegion: "us-east-1",
|
||||||
|
},
|
||||||
|
"*/route53": {
|
||||||
|
Endpoint: "route53.amazonaws.com",
|
||||||
|
SigningRegion: "us-east-1",
|
||||||
|
},
|
||||||
|
"*/sts": {
|
||||||
|
Endpoint: "sts.amazonaws.com",
|
||||||
|
SigningRegion: "us-east-1",
|
||||||
|
},
|
||||||
|
"ap-northeast-1/s3": {
|
||||||
|
Endpoint: "s3-{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"ap-southeast-1/s3": {
|
||||||
|
Endpoint: "s3-{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"ap-southeast-2/s3": {
|
||||||
|
Endpoint: "s3-{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"cn-north-1/*": {
|
||||||
|
Endpoint: "{service}.{region}.amazonaws.com.cn",
|
||||||
|
},
|
||||||
|
"eu-central-1/s3": {
|
||||||
|
Endpoint: "{service}.{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"eu-west-1/s3": {
|
||||||
|
Endpoint: "s3-{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"sa-east-1/s3": {
|
||||||
|
Endpoint: "s3-{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"us-east-1/s3": {
|
||||||
|
Endpoint: "s3.amazonaws.com",
|
||||||
|
},
|
||||||
|
"us-east-1/sdb": {
|
||||||
|
Endpoint: "sdb.amazonaws.com",
|
||||||
|
SigningRegion: "us-east-1",
|
||||||
|
},
|
||||||
|
"us-gov-west-1/iam": {
|
||||||
|
Endpoint: "iam.us-gov.amazonaws.com",
|
||||||
|
},
|
||||||
|
"us-gov-west-1/s3": {
|
||||||
|
Endpoint: "s3-{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"us-gov-west-1/sts": {
|
||||||
|
Endpoint: "sts.us-gov-west-1.amazonaws.com",
|
||||||
|
},
|
||||||
|
"us-west-1/s3": {
|
||||||
|
Endpoint: "s3-{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
"us-west-2/s3": {
|
||||||
|
Endpoint: "s3-{region}.amazonaws.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
28
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_test.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_test.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGlobalEndpoints(t *testing.T) {
|
||||||
|
region := "mock-region-1"
|
||||||
|
svcs := []string{"cloudfront", "iam", "importexport", "route53", "sts"}
|
||||||
|
|
||||||
|
for _, name := range svcs {
|
||||||
|
ep, sr := EndpointForRegion(name, region)
|
||||||
|
assert.Equal(t, name+".amazonaws.com", ep)
|
||||||
|
assert.Equal(t, "us-east-1", sr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServicesInCN(t *testing.T) {
|
||||||
|
region := "cn-north-1"
|
||||||
|
svcs := []string{"cloudfront", "iam", "importexport", "route53", "sts", "s3"}
|
||||||
|
|
||||||
|
for _, name := range svcs {
|
||||||
|
ep, _ := EndpointForRegion(name, region)
|
||||||
|
assert.Equal(t, name+"."+region+".amazonaws.com.cn", ep)
|
||||||
|
}
|
||||||
|
}
|
33
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build.go
generated
vendored
Normal file
33
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Package query provides serialisation of AWS query requests, and responses.
|
||||||
|
package query
|
||||||
|
|
||||||
|
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/input/query.json build_test.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/protocol/query/queryutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build builds a request for an AWS Query service.
|
||||||
|
func Build(r *aws.Request) {
|
||||||
|
body := url.Values{
|
||||||
|
"Action": {r.Operation.Name},
|
||||||
|
"Version": {r.Service.APIVersion},
|
||||||
|
}
|
||||||
|
if err := queryutil.Parse(body, r.Params, false); err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed encoding Query request", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ExpireTime == 0 {
|
||||||
|
r.HTTPRequest.Method = "POST"
|
||||||
|
r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||||
|
r.SetBufferBody([]byte(body.Encode()))
|
||||||
|
} else { // This is a pre-signed request
|
||||||
|
r.HTTPRequest.Method = "GET"
|
||||||
|
r.HTTPRequest.URL.RawQuery = body.Encode()
|
||||||
|
}
|
||||||
|
}
|
1491
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build_test.go
generated
vendored
Normal file
1491
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
223
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/queryutil/queryutil.go
generated
vendored
Normal file
223
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/queryutil/queryutil.go
generated
vendored
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
package queryutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse parses an object i and fills a url.Values object. The isEC2 flag
|
||||||
|
// indicates if this is the EC2 Query sub-protocol.
|
||||||
|
func Parse(body url.Values, i interface{}, isEC2 bool) error {
|
||||||
|
q := queryParser{isEC2: isEC2}
|
||||||
|
return q.parseValue(body, reflect.ValueOf(i), "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func elemOf(value reflect.Value) reflect.Value {
|
||||||
|
for value.Kind() == reflect.Ptr {
|
||||||
|
value = value.Elem()
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
type queryParser struct {
|
||||||
|
isEC2 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queryParser) parseValue(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
|
||||||
|
value = elemOf(value)
|
||||||
|
|
||||||
|
// no need to handle zero values
|
||||||
|
if !value.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tag.Get("type")
|
||||||
|
if t == "" {
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
t = "structure"
|
||||||
|
case reflect.Slice:
|
||||||
|
t = "list"
|
||||||
|
case reflect.Map:
|
||||||
|
t = "map"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case "structure":
|
||||||
|
return q.parseStruct(v, value, prefix)
|
||||||
|
case "list":
|
||||||
|
return q.parseList(v, value, prefix, tag)
|
||||||
|
case "map":
|
||||||
|
return q.parseMap(v, value, prefix, tag)
|
||||||
|
default:
|
||||||
|
return q.parseScalar(v, value, prefix, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix string) error {
|
||||||
|
if !value.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t := value.Type()
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c {
|
||||||
|
continue // ignore unexported fields
|
||||||
|
}
|
||||||
|
|
||||||
|
value := elemOf(value.Field(i))
|
||||||
|
field := t.Field(i)
|
||||||
|
var name string
|
||||||
|
|
||||||
|
if q.isEC2 {
|
||||||
|
name = field.Tag.Get("queryName")
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" {
|
||||||
|
name = field.Tag.Get("locationNameList")
|
||||||
|
} else if locName := field.Tag.Get("locationName"); locName != "" {
|
||||||
|
name = locName
|
||||||
|
}
|
||||||
|
if name != "" && q.isEC2 {
|
||||||
|
name = strings.ToUpper(name[0:1]) + name[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
name = field.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix != "" {
|
||||||
|
name = prefix + "." + name
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := q.parseValue(v, value, name, field.Tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queryParser) parseList(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
|
||||||
|
// If it's empty, generate an empty value
|
||||||
|
if !value.IsNil() && value.Len() == 0 {
|
||||||
|
v.Set(prefix, "")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for unflattened list member
|
||||||
|
if !q.isEC2 && tag.Get("flattened") == "" {
|
||||||
|
prefix += ".member"
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < value.Len(); i++ {
|
||||||
|
slicePrefix := prefix
|
||||||
|
if slicePrefix == "" {
|
||||||
|
slicePrefix = strconv.Itoa(i + 1)
|
||||||
|
} else {
|
||||||
|
slicePrefix = slicePrefix + "." + strconv.Itoa(i+1)
|
||||||
|
}
|
||||||
|
if err := q.parseValue(v, value.Index(i), slicePrefix, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queryParser) parseMap(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
|
||||||
|
// If it's empty, generate an empty value
|
||||||
|
if !value.IsNil() && value.Len() == 0 {
|
||||||
|
v.Set(prefix, "")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for unflattened list member
|
||||||
|
if !q.isEC2 && tag.Get("flattened") == "" {
|
||||||
|
prefix += ".entry"
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort keys for improved serialization consistency.
|
||||||
|
// this is not strictly necessary for protocol support.
|
||||||
|
mapKeyValues := value.MapKeys()
|
||||||
|
mapKeys := map[string]reflect.Value{}
|
||||||
|
mapKeyNames := make([]string, len(mapKeyValues))
|
||||||
|
for i, mapKey := range mapKeyValues {
|
||||||
|
name := mapKey.String()
|
||||||
|
mapKeys[name] = mapKey
|
||||||
|
mapKeyNames[i] = name
|
||||||
|
}
|
||||||
|
sort.Strings(mapKeyNames)
|
||||||
|
|
||||||
|
for i, mapKeyName := range mapKeyNames {
|
||||||
|
mapKey := mapKeys[mapKeyName]
|
||||||
|
mapValue := value.MapIndex(mapKey)
|
||||||
|
|
||||||
|
kname := tag.Get("locationNameKey")
|
||||||
|
if kname == "" {
|
||||||
|
kname = "key"
|
||||||
|
}
|
||||||
|
vname := tag.Get("locationNameValue")
|
||||||
|
if vname == "" {
|
||||||
|
vname = "value"
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize key
|
||||||
|
var keyName string
|
||||||
|
if prefix == "" {
|
||||||
|
keyName = strconv.Itoa(i+1) + "." + kname
|
||||||
|
} else {
|
||||||
|
keyName = prefix + "." + strconv.Itoa(i+1) + "." + kname
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := q.parseValue(v, mapKey, keyName, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize value
|
||||||
|
var valueName string
|
||||||
|
if prefix == "" {
|
||||||
|
valueName = strconv.Itoa(i+1) + "." + vname
|
||||||
|
} else {
|
||||||
|
valueName = prefix + "." + strconv.Itoa(i+1) + "." + vname
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := q.parseValue(v, mapValue, valueName, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queryParser) parseScalar(v url.Values, r reflect.Value, name string, tag reflect.StructTag) error {
|
||||||
|
switch value := r.Interface().(type) {
|
||||||
|
case string:
|
||||||
|
v.Set(name, value)
|
||||||
|
case []byte:
|
||||||
|
if !r.IsNil() {
|
||||||
|
v.Set(name, base64.StdEncoding.EncodeToString(value))
|
||||||
|
}
|
||||||
|
case bool:
|
||||||
|
v.Set(name, strconv.FormatBool(value))
|
||||||
|
case int64:
|
||||||
|
v.Set(name, strconv.FormatInt(value, 10))
|
||||||
|
case int:
|
||||||
|
v.Set(name, strconv.Itoa(value))
|
||||||
|
case float64:
|
||||||
|
v.Set(name, strconv.FormatFloat(value, 'f', -1, 64))
|
||||||
|
case float32:
|
||||||
|
v.Set(name, strconv.FormatFloat(float64(value), 'f', -1, 32))
|
||||||
|
case time.Time:
|
||||||
|
const ISO8601UTC = "2006-01-02T15:04:05Z"
|
||||||
|
v.Set(name, value.UTC().Format(ISO8601UTC))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported value for param %s: %v (%s)", name, r.Interface(), r.Type().Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
29
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal.go
generated
vendored
Normal file
29
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/output/query.json unmarshal_test.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unmarshal unmarshals a response for an AWS Query service.
|
||||||
|
func Unmarshal(r *aws.Request) {
|
||||||
|
defer r.HTTPResponse.Body.Close()
|
||||||
|
if r.DataFilled() {
|
||||||
|
decoder := xml.NewDecoder(r.HTTPResponse.Body)
|
||||||
|
err := xmlutil.UnmarshalXML(r.Data, decoder, r.Operation.Name+"Result")
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed decoding Query response", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMeta unmarshals header response values for an AWS Query service.
|
||||||
|
func UnmarshalMeta(r *aws.Request) {
|
||||||
|
// TODO implement unmarshaling of request IDs
|
||||||
|
}
|
33
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_error.go
generated
vendored
Normal file
33
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_error.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xmlErrorResponse struct {
|
||||||
|
XMLName xml.Name `xml:"ErrorResponse"`
|
||||||
|
Code string `xml:"Error>Code"`
|
||||||
|
Message string `xml:"Error>Message"`
|
||||||
|
RequestID string `xml:"RequestId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalError unmarshals an error response for an AWS Query service.
|
||||||
|
func UnmarshalError(r *aws.Request) {
|
||||||
|
defer r.HTTPResponse.Body.Close()
|
||||||
|
|
||||||
|
resp := &xmlErrorResponse{}
|
||||||
|
err := xml.NewDecoder(r.HTTPResponse.Body).Decode(resp)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to decode query XML error response", err)
|
||||||
|
} else {
|
||||||
|
r.Error = awserr.NewRequestFailure(
|
||||||
|
awserr.New(resp.Code, resp.Message, nil),
|
||||||
|
r.HTTPResponse.StatusCode,
|
||||||
|
resp.RequestID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
1432
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_test.go
generated
vendored
Normal file
1432
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
212
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/build.go
generated
vendored
Normal file
212
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/build.go
generated
vendored
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
// Package rest provides RESTful serialisation of AWS requests and responses.
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RFC822 returns an RFC822 formatted timestamp for AWS protocols
|
||||||
|
const RFC822 = "Mon, 2 Jan 2006 15:04:05 GMT"
|
||||||
|
|
||||||
|
// Whether the byte value can be sent without escaping in AWS URLs
|
||||||
|
var noEscape [256]bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for i := 0; i < len(noEscape); i++ {
|
||||||
|
// AWS expects every character except these to be escaped
|
||||||
|
noEscape[i] = (i >= 'A' && i <= 'Z') ||
|
||||||
|
(i >= 'a' && i <= 'z') ||
|
||||||
|
(i >= '0' && i <= '9') ||
|
||||||
|
i == '-' ||
|
||||||
|
i == '.' ||
|
||||||
|
i == '_' ||
|
||||||
|
i == '~'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build builds the REST component of a service request.
|
||||||
|
func Build(r *aws.Request) {
|
||||||
|
if r.ParamsFilled() {
|
||||||
|
v := reflect.ValueOf(r.Params).Elem()
|
||||||
|
buildLocationElements(r, v)
|
||||||
|
buildBody(r, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLocationElements(r *aws.Request, v reflect.Value) {
|
||||||
|
query := r.HTTPRequest.URL.Query()
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
m := v.Field(i)
|
||||||
|
if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.IsValid() {
|
||||||
|
field := v.Type().Field(i)
|
||||||
|
name := field.Tag.Get("locationName")
|
||||||
|
if name == "" {
|
||||||
|
name = field.Name
|
||||||
|
}
|
||||||
|
if m.Kind() == reflect.Ptr {
|
||||||
|
m = m.Elem()
|
||||||
|
}
|
||||||
|
if !m.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch field.Tag.Get("location") {
|
||||||
|
case "headers": // header maps
|
||||||
|
buildHeaderMap(r, m, field.Tag.Get("locationName"))
|
||||||
|
case "header":
|
||||||
|
buildHeader(r, m, name)
|
||||||
|
case "uri":
|
||||||
|
buildURI(r, m, name)
|
||||||
|
case "querystring":
|
||||||
|
buildQueryString(r, m, name, query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.HTTPRequest.URL.RawQuery = query.Encode()
|
||||||
|
updatePath(r.HTTPRequest.URL, r.HTTPRequest.URL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBody(r *aws.Request, v reflect.Value) {
|
||||||
|
if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok {
|
||||||
|
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
||||||
|
pfield, _ := v.Type().FieldByName(payloadName)
|
||||||
|
if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
|
||||||
|
payload := reflect.Indirect(v.FieldByName(payloadName))
|
||||||
|
if payload.IsValid() && payload.Interface() != nil {
|
||||||
|
switch reader := payload.Interface().(type) {
|
||||||
|
case io.ReadSeeker:
|
||||||
|
r.SetReaderBody(reader)
|
||||||
|
case []byte:
|
||||||
|
r.SetBufferBody(reader)
|
||||||
|
case string:
|
||||||
|
r.SetStringBody(reader)
|
||||||
|
default:
|
||||||
|
r.Error = awserr.New("SerializationError",
|
||||||
|
"failed to encode REST request",
|
||||||
|
fmt.Errorf("unknown payload type %s", payload.Type()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildHeader(r *aws.Request, v reflect.Value, name string) {
|
||||||
|
str, err := convertType(v)
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to encode REST request", err)
|
||||||
|
} else if str != nil {
|
||||||
|
r.HTTPRequest.Header.Add(name, *str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildHeaderMap(r *aws.Request, v reflect.Value, prefix string) {
|
||||||
|
for _, key := range v.MapKeys() {
|
||||||
|
str, err := convertType(v.MapIndex(key))
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to encode REST request", err)
|
||||||
|
} else if str != nil {
|
||||||
|
r.HTTPRequest.Header.Add(prefix+key.String(), *str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildURI(r *aws.Request, v reflect.Value, name string) {
|
||||||
|
value, err := convertType(v)
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to encode REST request", err)
|
||||||
|
} else if value != nil {
|
||||||
|
uri := r.HTTPRequest.URL.Path
|
||||||
|
uri = strings.Replace(uri, "{"+name+"}", EscapePath(*value, true), -1)
|
||||||
|
uri = strings.Replace(uri, "{"+name+"+}", EscapePath(*value, false), -1)
|
||||||
|
r.HTTPRequest.URL.Path = uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildQueryString(r *aws.Request, v reflect.Value, name string, query url.Values) {
|
||||||
|
str, err := convertType(v)
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to encode REST request", err)
|
||||||
|
} else if str != nil {
|
||||||
|
query.Set(name, *str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePath(url *url.URL, urlPath string) {
|
||||||
|
scheme, query := url.Scheme, url.RawQuery
|
||||||
|
|
||||||
|
// clean up path
|
||||||
|
urlPath = path.Clean(urlPath)
|
||||||
|
|
||||||
|
// get formatted URL minus scheme so we can build this into Opaque
|
||||||
|
url.Scheme, url.Path, url.RawQuery = "", "", ""
|
||||||
|
s := url.String()
|
||||||
|
url.Scheme = scheme
|
||||||
|
url.RawQuery = query
|
||||||
|
|
||||||
|
// build opaque URI
|
||||||
|
url.Opaque = s + urlPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// EscapePath escapes part of a URL path in Amazon style
|
||||||
|
func EscapePath(path string, encodeSep bool) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
c := path[i]
|
||||||
|
if noEscape[c] || (c == '/' && !encodeSep) {
|
||||||
|
buf.WriteByte(c)
|
||||||
|
} else {
|
||||||
|
buf.WriteByte('%')
|
||||||
|
buf.WriteString(strings.ToUpper(strconv.FormatUint(uint64(c), 16)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertType(v reflect.Value) (*string, error) {
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
if !v.IsValid() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var str string
|
||||||
|
switch value := v.Interface().(type) {
|
||||||
|
case string:
|
||||||
|
str = value
|
||||||
|
case []byte:
|
||||||
|
str = base64.StdEncoding.EncodeToString(value)
|
||||||
|
case bool:
|
||||||
|
str = strconv.FormatBool(value)
|
||||||
|
case int64:
|
||||||
|
str = strconv.FormatInt(value, 10)
|
||||||
|
case float64:
|
||||||
|
str = strconv.FormatFloat(value, 'f', -1, 64)
|
||||||
|
case time.Time:
|
||||||
|
str = value.UTC().Format(RFC822)
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("Unsupported value for param %v (%s)", v.Interface(), v.Type())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &str, nil
|
||||||
|
}
|
45
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/payload.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/payload.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
// PayloadMember returns the payload field member of i if there is one, or nil.
|
||||||
|
func PayloadMember(i interface{}) interface{} {
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(i).Elem()
|
||||||
|
if !v.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok {
|
||||||
|
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
||||||
|
field, _ := v.Type().FieldByName(payloadName)
|
||||||
|
if field.Tag.Get("type") != "structure" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := v.FieldByName(payloadName)
|
||||||
|
if payload.IsValid() || (payload.Kind() == reflect.Ptr && !payload.IsNil()) {
|
||||||
|
return payload.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayloadType returns the type of a payload field member of i if there is one, or "".
|
||||||
|
func PayloadType(i interface{}) string {
|
||||||
|
v := reflect.Indirect(reflect.ValueOf(i))
|
||||||
|
if !v.IsValid() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok {
|
||||||
|
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
||||||
|
if member, ok := v.Type().FieldByName(payloadName); ok {
|
||||||
|
return member.Tag.Get("type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
174
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/unmarshal.go
generated
vendored
Normal file
174
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/unmarshal.go
generated
vendored
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unmarshal unmarshals the REST component of a response in a REST service.
|
||||||
|
func Unmarshal(r *aws.Request) {
|
||||||
|
if r.DataFilled() {
|
||||||
|
v := reflect.Indirect(reflect.ValueOf(r.Data))
|
||||||
|
unmarshalBody(r, v)
|
||||||
|
unmarshalLocationElements(r, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalBody(r *aws.Request, v reflect.Value) {
|
||||||
|
if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok {
|
||||||
|
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
||||||
|
pfield, _ := v.Type().FieldByName(payloadName)
|
||||||
|
if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
|
||||||
|
payload := v.FieldByName(payloadName)
|
||||||
|
if payload.IsValid() {
|
||||||
|
switch payload.Interface().(type) {
|
||||||
|
case []byte:
|
||||||
|
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
|
||||||
|
} else {
|
||||||
|
payload.Set(reflect.ValueOf(b))
|
||||||
|
}
|
||||||
|
case *string:
|
||||||
|
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
|
||||||
|
} else {
|
||||||
|
str := string(b)
|
||||||
|
payload.Set(reflect.ValueOf(&str))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
switch payload.Type().String() {
|
||||||
|
case "io.ReadSeeker":
|
||||||
|
payload.Set(reflect.ValueOf(aws.ReadSeekCloser(r.HTTPResponse.Body)))
|
||||||
|
case "aws.ReadSeekCloser", "io.ReadCloser":
|
||||||
|
payload.Set(reflect.ValueOf(r.HTTPResponse.Body))
|
||||||
|
default:
|
||||||
|
r.Error = awserr.New("SerializationError",
|
||||||
|
"failed to decode REST response",
|
||||||
|
fmt.Errorf("unknown payload type %s", payload.Type()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalLocationElements(r *aws.Request, v reflect.Value) {
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
m, field := v.Field(i), v.Type().Field(i)
|
||||||
|
if n := field.Name; n[0:1] == strings.ToLower(n[0:1]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.IsValid() {
|
||||||
|
name := field.Tag.Get("locationName")
|
||||||
|
if name == "" {
|
||||||
|
name = field.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
switch field.Tag.Get("location") {
|
||||||
|
case "statusCode":
|
||||||
|
unmarshalStatusCode(m, r.HTTPResponse.StatusCode)
|
||||||
|
case "header":
|
||||||
|
err := unmarshalHeader(m, r.HTTPResponse.Header.Get(name))
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "headers":
|
||||||
|
prefix := field.Tag.Get("locationName")
|
||||||
|
err := unmarshalHeaderMap(m, r.HTTPResponse.Header, prefix)
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalStatusCode(v reflect.Value, statusCode int) {
|
||||||
|
if !v.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Interface().(type) {
|
||||||
|
case *int64:
|
||||||
|
s := int64(statusCode)
|
||||||
|
v.Set(reflect.ValueOf(&s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalHeaderMap(r reflect.Value, headers http.Header, prefix string) error {
|
||||||
|
switch r.Interface().(type) {
|
||||||
|
case map[string]*string: // we only support string map value types
|
||||||
|
out := map[string]*string{}
|
||||||
|
for k, v := range headers {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
if strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) {
|
||||||
|
out[k[len(prefix):]] = &v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.Set(reflect.ValueOf(out))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalHeader(v reflect.Value, header string) error {
|
||||||
|
if !v.IsValid() || (header == "" && v.Elem().Kind() != reflect.String) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Interface().(type) {
|
||||||
|
case *string:
|
||||||
|
v.Set(reflect.ValueOf(&header))
|
||||||
|
case []byte:
|
||||||
|
b, err := base64.StdEncoding.DecodeString(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(&b))
|
||||||
|
case *bool:
|
||||||
|
b, err := strconv.ParseBool(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(&b))
|
||||||
|
case *int64:
|
||||||
|
i, err := strconv.ParseInt(header, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(&i))
|
||||||
|
case *float64:
|
||||||
|
f, err := strconv.ParseFloat(header, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(&f))
|
||||||
|
case *time.Time:
|
||||||
|
t, err := time.Parse(RFC822, header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(&t))
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("Unsupported value for param %v (%s)", v.Interface(), v.Type())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
2736
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/restxml/build_test.go
generated
vendored
Normal file
2736
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/restxml/build_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
55
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/restxml/restxml.go
generated
vendored
Normal file
55
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/restxml/restxml.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Package restxml provides RESTful XML serialisation of AWS
|
||||||
|
// requests and responses.
|
||||||
|
package restxml
|
||||||
|
|
||||||
|
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/input/rest-xml.json build_test.go
|
||||||
|
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/output/rest-xml.json unmarshal_test.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/protocol/query"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/protocol/rest"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build builds a request payload for the REST XML protocol.
|
||||||
|
func Build(r *aws.Request) {
|
||||||
|
rest.Build(r)
|
||||||
|
|
||||||
|
if t := rest.PayloadType(r.Params); t == "structure" || t == "" {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := xmlutil.BuildXML(r.Params, xml.NewEncoder(&buf))
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to enode rest XML request", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.SetBufferBody(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal unmarshals a payload response for the REST XML protocol.
|
||||||
|
func Unmarshal(r *aws.Request) {
|
||||||
|
if t := rest.PayloadType(r.Data); t == "structure" || t == "" {
|
||||||
|
defer r.HTTPResponse.Body.Close()
|
||||||
|
decoder := xml.NewDecoder(r.HTTPResponse.Body)
|
||||||
|
err := xmlutil.UnmarshalXML(r.Data, decoder, "")
|
||||||
|
if err != nil {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to decode REST XML response", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMeta unmarshals response headers for the REST XML protocol.
|
||||||
|
func UnmarshalMeta(r *aws.Request) {
|
||||||
|
rest.Unmarshal(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalError unmarshals a response error for the REST XML protocol.
|
||||||
|
func UnmarshalError(r *aws.Request) {
|
||||||
|
query.UnmarshalError(r)
|
||||||
|
}
|
1322
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/restxml/unmarshal_test.go
generated
vendored
Normal file
1322
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/restxml/unmarshal_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
287
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/build.go
generated
vendored
Normal file
287
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/build.go
generated
vendored
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
// Package xmlutil provides XML serialisation of AWS requests and responses.
|
||||||
|
package xmlutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildXML will serialize params into an xml.Encoder.
|
||||||
|
// Error will be returned if the serialization of any of the params or nested values fails.
|
||||||
|
func BuildXML(params interface{}, e *xml.Encoder) error {
|
||||||
|
b := xmlBuilder{encoder: e, namespaces: map[string]string{}}
|
||||||
|
root := NewXMLElement(xml.Name{})
|
||||||
|
if err := b.buildValue(reflect.ValueOf(params), root, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, c := range root.Children {
|
||||||
|
for _, v := range c {
|
||||||
|
return StructToXML(e, v, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the reflection element of a value, if it is a pointer.
|
||||||
|
func elemOf(value reflect.Value) reflect.Value {
|
||||||
|
for value.Kind() == reflect.Ptr {
|
||||||
|
value = value.Elem()
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// A xmlBuilder serializes values from Go code to XML
|
||||||
|
type xmlBuilder struct {
|
||||||
|
encoder *xml.Encoder
|
||||||
|
namespaces map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildValue generic XMLNode builder for any type. Will build value for their specific type
|
||||||
|
// struct, list, map, scalar.
|
||||||
|
//
|
||||||
|
// Also takes a "type" tag value to set what type a value should be converted to XMLNode as. If
|
||||||
|
// type is not provided reflect will be used to determine the value's type.
|
||||||
|
func (b *xmlBuilder) buildValue(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||||
|
value = elemOf(value)
|
||||||
|
if !value.IsValid() { // no need to handle zero values
|
||||||
|
return nil
|
||||||
|
} else if tag.Get("location") != "" { // don't handle non-body location values
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tag.Get("type")
|
||||||
|
if t == "" {
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
t = "structure"
|
||||||
|
case reflect.Slice:
|
||||||
|
t = "list"
|
||||||
|
case reflect.Map:
|
||||||
|
t = "map"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case "structure":
|
||||||
|
if field, ok := value.Type().FieldByName("SDKShapeTraits"); ok {
|
||||||
|
tag = tag + reflect.StructTag(" ") + field.Tag
|
||||||
|
}
|
||||||
|
return b.buildStruct(value, current, tag)
|
||||||
|
case "list":
|
||||||
|
return b.buildList(value, current, tag)
|
||||||
|
case "map":
|
||||||
|
return b.buildMap(value, current, tag)
|
||||||
|
default:
|
||||||
|
return b.buildScalar(value, current, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildStruct adds a struct and its fields to the current XMLNode. All fields any any nested
|
||||||
|
// types are converted to XMLNodes also.
|
||||||
|
func (b *xmlBuilder) buildStruct(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||||
|
if !value.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldAdded := false
|
||||||
|
|
||||||
|
// unwrap payloads
|
||||||
|
if payload := tag.Get("payload"); payload != "" {
|
||||||
|
field, _ := value.Type().FieldByName(payload)
|
||||||
|
tag = field.Tag
|
||||||
|
value = elemOf(value.FieldByName(payload))
|
||||||
|
|
||||||
|
if !value.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
|
||||||
|
|
||||||
|
// there is an xmlNamespace associated with this struct
|
||||||
|
if prefix, uri := tag.Get("xmlPrefix"), tag.Get("xmlURI"); uri != "" {
|
||||||
|
ns := xml.Attr{
|
||||||
|
Name: xml.Name{Local: "xmlns"},
|
||||||
|
Value: uri,
|
||||||
|
}
|
||||||
|
if prefix != "" {
|
||||||
|
b.namespaces[prefix] = uri // register the namespace
|
||||||
|
ns.Name.Local = "xmlns:" + prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
child.Attr = append(child.Attr, ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := value.Type()
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c {
|
||||||
|
continue // ignore unexported fields
|
||||||
|
}
|
||||||
|
|
||||||
|
member := elemOf(value.Field(i))
|
||||||
|
field := t.Field(i)
|
||||||
|
mTag := field.Tag
|
||||||
|
|
||||||
|
if mTag.Get("location") != "" { // skip non-body members
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
memberName := mTag.Get("locationName")
|
||||||
|
if memberName == "" {
|
||||||
|
memberName = field.Name
|
||||||
|
mTag = reflect.StructTag(string(mTag) + ` locationName:"` + memberName + `"`)
|
||||||
|
}
|
||||||
|
if err := b.buildValue(member, child, mTag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldAdded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldAdded { // only append this child if we have one ore more valid members
|
||||||
|
current.AddChild(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildList adds the value's list items to the current XMLNode as children nodes. All
|
||||||
|
// nested values in the list are converted to XMLNodes also.
|
||||||
|
func (b *xmlBuilder) buildList(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||||
|
if value.IsNil() { // don't build omitted lists
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for unflattened list member
|
||||||
|
flattened := tag.Get("flattened") != ""
|
||||||
|
|
||||||
|
xname := xml.Name{Local: tag.Get("locationName")}
|
||||||
|
if flattened {
|
||||||
|
for i := 0; i < value.Len(); i++ {
|
||||||
|
child := NewXMLElement(xname)
|
||||||
|
current.AddChild(child)
|
||||||
|
if err := b.buildValue(value.Index(i), child, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list := NewXMLElement(xname)
|
||||||
|
current.AddChild(list)
|
||||||
|
|
||||||
|
for i := 0; i < value.Len(); i++ {
|
||||||
|
iname := tag.Get("locationNameList")
|
||||||
|
if iname == "" {
|
||||||
|
iname = "member"
|
||||||
|
}
|
||||||
|
|
||||||
|
child := NewXMLElement(xml.Name{Local: iname})
|
||||||
|
list.AddChild(child)
|
||||||
|
if err := b.buildValue(value.Index(i), child, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildMap adds the value's key/value pairs to the current XMLNode as children nodes. All
|
||||||
|
// nested values in the map are converted to XMLNodes also.
|
||||||
|
//
|
||||||
|
// Error will be returned if it is unable to build the map's values into XMLNodes
|
||||||
|
func (b *xmlBuilder) buildMap(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||||
|
if value.IsNil() { // don't build omitted maps
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
maproot := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
|
||||||
|
current.AddChild(maproot)
|
||||||
|
current = maproot
|
||||||
|
|
||||||
|
kname, vname := "key", "value"
|
||||||
|
if n := tag.Get("locationNameKey"); n != "" {
|
||||||
|
kname = n
|
||||||
|
}
|
||||||
|
if n := tag.Get("locationNameValue"); n != "" {
|
||||||
|
vname = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// sorting is not required for compliance, but it makes testing easier
|
||||||
|
keys := make([]string, value.Len())
|
||||||
|
for i, k := range value.MapKeys() {
|
||||||
|
keys[i] = k.String()
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
v := value.MapIndex(reflect.ValueOf(k))
|
||||||
|
|
||||||
|
mapcur := current
|
||||||
|
if tag.Get("flattened") == "" { // add "entry" tag to non-flat maps
|
||||||
|
child := NewXMLElement(xml.Name{Local: "entry"})
|
||||||
|
mapcur.AddChild(child)
|
||||||
|
mapcur = child
|
||||||
|
}
|
||||||
|
|
||||||
|
kchild := NewXMLElement(xml.Name{Local: kname})
|
||||||
|
kchild.Text = k
|
||||||
|
vchild := NewXMLElement(xml.Name{Local: vname})
|
||||||
|
mapcur.AddChild(kchild)
|
||||||
|
mapcur.AddChild(vchild)
|
||||||
|
|
||||||
|
if err := b.buildValue(v, vchild, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildScalar will convert the value into a string and append it as a attribute or child
|
||||||
|
// of the current XMLNode.
|
||||||
|
//
|
||||||
|
// The value will be added as an attribute if tag contains a "xmlAttribute" attribute value.
|
||||||
|
//
|
||||||
|
// Error will be returned if the value type is unsupported.
|
||||||
|
func (b *xmlBuilder) buildScalar(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||||
|
var str string
|
||||||
|
switch converted := value.Interface().(type) {
|
||||||
|
case string:
|
||||||
|
str = converted
|
||||||
|
case []byte:
|
||||||
|
if !value.IsNil() {
|
||||||
|
str = base64.StdEncoding.EncodeToString(converted)
|
||||||
|
}
|
||||||
|
case bool:
|
||||||
|
str = strconv.FormatBool(converted)
|
||||||
|
case int64:
|
||||||
|
str = strconv.FormatInt(converted, 10)
|
||||||
|
case int:
|
||||||
|
str = strconv.Itoa(converted)
|
||||||
|
case float64:
|
||||||
|
str = strconv.FormatFloat(converted, 'f', -1, 64)
|
||||||
|
case float32:
|
||||||
|
str = strconv.FormatFloat(float64(converted), 'f', -1, 32)
|
||||||
|
case time.Time:
|
||||||
|
const ISO8601UTC = "2006-01-02T15:04:05Z"
|
||||||
|
str = converted.UTC().Format(ISO8601UTC)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported value for param %s: %v (%s)",
|
||||||
|
tag.Get("locationName"), value.Interface(), value.Type().Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
xname := xml.Name{Local: tag.Get("locationName")}
|
||||||
|
if tag.Get("xmlAttribute") != "" { // put into current node's attribute list
|
||||||
|
attr := xml.Attr{Name: xname, Value: str}
|
||||||
|
current.Attr = append(current.Attr, attr)
|
||||||
|
} else { // regular text node
|
||||||
|
current.AddChild(&XMLNode{Name: xname, Text: str})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
260
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/unmarshal.go
generated
vendored
Normal file
260
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/unmarshal.go
generated
vendored
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
package xmlutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalXML deserializes an xml.Decoder into the container v. V
|
||||||
|
// needs to match the shape of the XML expected to be decoded.
|
||||||
|
// If the shape doesn't match unmarshaling will fail.
|
||||||
|
func UnmarshalXML(v interface{}, d *xml.Decoder, wrapper string) error {
|
||||||
|
n, _ := XMLToStruct(d, nil)
|
||||||
|
if n.Children != nil {
|
||||||
|
for _, root := range n.Children {
|
||||||
|
for _, c := range root {
|
||||||
|
if wrappedChild, ok := c.Children[wrapper]; ok {
|
||||||
|
c = wrappedChild[0] // pull out wrapped element
|
||||||
|
}
|
||||||
|
|
||||||
|
err := parse(reflect.ValueOf(v), c, "")
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse deserializes any value from the XMLNode. The type tag is used to infer the type, or reflect
|
||||||
|
// will be used to determine the type from r.
|
||||||
|
func parse(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||||
|
rtype := r.Type()
|
||||||
|
if rtype.Kind() == reflect.Ptr {
|
||||||
|
rtype = rtype.Elem() // check kind of actual element type
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tag.Get("type")
|
||||||
|
if t == "" {
|
||||||
|
switch rtype.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
t = "structure"
|
||||||
|
case reflect.Slice:
|
||||||
|
t = "list"
|
||||||
|
case reflect.Map:
|
||||||
|
t = "map"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case "structure":
|
||||||
|
if field, ok := rtype.FieldByName("SDKShapeTraits"); ok {
|
||||||
|
tag = field.Tag
|
||||||
|
}
|
||||||
|
return parseStruct(r, node, tag)
|
||||||
|
case "list":
|
||||||
|
return parseList(r, node, tag)
|
||||||
|
case "map":
|
||||||
|
return parseMap(r, node, tag)
|
||||||
|
default:
|
||||||
|
return parseScalar(r, node, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseStruct deserializes a structure and its fields from an XMLNode. Any nested
|
||||||
|
// types in the structure will also be deserialized.
|
||||||
|
func parseStruct(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||||
|
t := r.Type()
|
||||||
|
if r.Kind() == reflect.Ptr {
|
||||||
|
if r.IsNil() { // create the structure if it's nil
|
||||||
|
s := reflect.New(r.Type().Elem())
|
||||||
|
r.Set(s)
|
||||||
|
r = s
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r.Elem()
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap any payloads
|
||||||
|
if payload := tag.Get("payload"); payload != "" {
|
||||||
|
field, _ := t.FieldByName(payload)
|
||||||
|
return parseStruct(r.FieldByName(payload), node, field.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
if c := field.Name[0:1]; strings.ToLower(c) == c {
|
||||||
|
continue // ignore unexported fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out what this field is called
|
||||||
|
name := field.Name
|
||||||
|
if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" {
|
||||||
|
name = field.Tag.Get("locationNameList")
|
||||||
|
} else if locName := field.Tag.Get("locationName"); locName != "" {
|
||||||
|
name = locName
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to find the field by name in elements
|
||||||
|
elems := node.Children[name]
|
||||||
|
|
||||||
|
if elems == nil { // try to find the field in attributes
|
||||||
|
for _, a := range node.Attr {
|
||||||
|
if name == a.Name.Local {
|
||||||
|
// turn this into a text node for de-serializing
|
||||||
|
elems = []*XMLNode{{Text: a.Value}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
member := r.FieldByName(field.Name)
|
||||||
|
for _, elem := range elems {
|
||||||
|
err := parse(member, elem, field.Tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseList deserializes a list of values from an XML node. Each list entry
|
||||||
|
// will also be deserialized.
|
||||||
|
func parseList(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||||
|
t := r.Type()
|
||||||
|
|
||||||
|
if tag.Get("flattened") == "" { // look at all item entries
|
||||||
|
mname := "member"
|
||||||
|
if name := tag.Get("locationNameList"); name != "" {
|
||||||
|
mname = name
|
||||||
|
}
|
||||||
|
|
||||||
|
if Children, ok := node.Children[mname]; ok {
|
||||||
|
if r.IsNil() {
|
||||||
|
r.Set(reflect.MakeSlice(t, len(Children), len(Children)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range Children {
|
||||||
|
err := parse(r.Index(i), c, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // flattened list means this is a single element
|
||||||
|
if r.IsNil() {
|
||||||
|
r.Set(reflect.MakeSlice(t, 0, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
childR := reflect.Zero(t.Elem())
|
||||||
|
r.Set(reflect.Append(r, childR))
|
||||||
|
err := parse(r.Index(r.Len()-1), node, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMap deserializes a map from an XMLNode. The direct children of the XMLNode
|
||||||
|
// will also be deserialized as map entries.
|
||||||
|
func parseMap(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||||
|
if r.IsNil() {
|
||||||
|
r.Set(reflect.MakeMap(r.Type()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag.Get("flattened") == "" { // look at all child entries
|
||||||
|
for _, entry := range node.Children["entry"] {
|
||||||
|
parseMapEntry(r, entry, tag)
|
||||||
|
}
|
||||||
|
} else { // this element is itself an entry
|
||||||
|
parseMapEntry(r, node, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMapEntry deserializes a map entry from a XML node.
|
||||||
|
func parseMapEntry(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||||
|
kname, vname := "key", "value"
|
||||||
|
if n := tag.Get("locationNameKey"); n != "" {
|
||||||
|
kname = n
|
||||||
|
}
|
||||||
|
if n := tag.Get("locationNameValue"); n != "" {
|
||||||
|
vname = n
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, ok := node.Children[kname]
|
||||||
|
values := node.Children[vname]
|
||||||
|
if ok {
|
||||||
|
for i, key := range keys {
|
||||||
|
keyR := reflect.ValueOf(key.Text)
|
||||||
|
value := values[i]
|
||||||
|
valueR := reflect.New(r.Type().Elem()).Elem()
|
||||||
|
|
||||||
|
parse(valueR, value, "")
|
||||||
|
r.SetMapIndex(keyR, valueR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseScaller deserializes an XMLNode value into a concrete type based on the
|
||||||
|
// interface type of r.
|
||||||
|
//
|
||||||
|
// Error is returned if the deserialization fails due to invalid type conversion,
|
||||||
|
// or unsupported interface type.
|
||||||
|
func parseScalar(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||||
|
switch r.Interface().(type) {
|
||||||
|
case *string:
|
||||||
|
r.Set(reflect.ValueOf(&node.Text))
|
||||||
|
return nil
|
||||||
|
case []byte:
|
||||||
|
b, err := base64.StdEncoding.DecodeString(node.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Set(reflect.ValueOf(b))
|
||||||
|
case *bool:
|
||||||
|
v, err := strconv.ParseBool(node.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Set(reflect.ValueOf(&v))
|
||||||
|
case *int64:
|
||||||
|
v, err := strconv.ParseInt(node.Text, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Set(reflect.ValueOf(&v))
|
||||||
|
case *float64:
|
||||||
|
v, err := strconv.ParseFloat(node.Text, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Set(reflect.ValueOf(&v))
|
||||||
|
case *time.Time:
|
||||||
|
const ISO8601UTC = "2006-01-02T15:04:05Z"
|
||||||
|
t, err := time.Parse(ISO8601UTC, node.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Set(reflect.ValueOf(&t))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported value: %v (%s)", r.Interface(), r.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
105
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/xml_to_struct.go
generated
vendored
Normal file
105
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/xml_to_struct.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package xmlutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A XMLNode contains the values to be encoded or decoded.
|
||||||
|
type XMLNode struct {
|
||||||
|
Name xml.Name `json:",omitempty"`
|
||||||
|
Children map[string][]*XMLNode `json:",omitempty"`
|
||||||
|
Text string `json:",omitempty"`
|
||||||
|
Attr []xml.Attr `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewXMLElement returns a pointer to a new XMLNode initialized to default values.
|
||||||
|
func NewXMLElement(name xml.Name) *XMLNode {
|
||||||
|
return &XMLNode{
|
||||||
|
Name: name,
|
||||||
|
Children: map[string][]*XMLNode{},
|
||||||
|
Attr: []xml.Attr{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddChild adds child to the XMLNode.
|
||||||
|
func (n *XMLNode) AddChild(child *XMLNode) {
|
||||||
|
if _, ok := n.Children[child.Name.Local]; !ok {
|
||||||
|
n.Children[child.Name.Local] = []*XMLNode{}
|
||||||
|
}
|
||||||
|
n.Children[child.Name.Local] = append(n.Children[child.Name.Local], child)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XMLToStruct converts a xml.Decoder stream to XMLNode with nested values.
|
||||||
|
func XMLToStruct(d *xml.Decoder, s *xml.StartElement) (*XMLNode, error) {
|
||||||
|
out := &XMLNode{}
|
||||||
|
for {
|
||||||
|
tok, err := d.Token()
|
||||||
|
if tok == nil || err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typed := tok.(type) {
|
||||||
|
case xml.CharData:
|
||||||
|
out.Text = string(typed.Copy())
|
||||||
|
case xml.StartElement:
|
||||||
|
el := typed.Copy()
|
||||||
|
out.Attr = el.Attr
|
||||||
|
if out.Children == nil {
|
||||||
|
out.Children = map[string][]*XMLNode{}
|
||||||
|
}
|
||||||
|
|
||||||
|
name := typed.Name.Local
|
||||||
|
slice := out.Children[name]
|
||||||
|
if slice == nil {
|
||||||
|
slice = []*XMLNode{}
|
||||||
|
}
|
||||||
|
node, e := XMLToStruct(d, &el)
|
||||||
|
if e != nil {
|
||||||
|
return out, e
|
||||||
|
}
|
||||||
|
node.Name = typed.Name
|
||||||
|
slice = append(slice, node)
|
||||||
|
out.Children[name] = slice
|
||||||
|
case xml.EndElement:
|
||||||
|
if s != nil && s.Name.Local == typed.Name.Local { // matching end token
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructToXML writes an XMLNode to a xml.Encoder as tokens.
|
||||||
|
func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error {
|
||||||
|
e.EncodeToken(xml.StartElement{Name: node.Name, Attr: node.Attr})
|
||||||
|
|
||||||
|
if node.Text != "" {
|
||||||
|
e.EncodeToken(xml.CharData([]byte(node.Text)))
|
||||||
|
} else if sorted {
|
||||||
|
sortedNames := []string{}
|
||||||
|
for k := range node.Children {
|
||||||
|
sortedNames = append(sortedNames, k)
|
||||||
|
}
|
||||||
|
sort.Strings(sortedNames)
|
||||||
|
|
||||||
|
for _, k := range sortedNames {
|
||||||
|
for _, v := range node.Children[k] {
|
||||||
|
StructToXML(e, v, sorted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, c := range node.Children {
|
||||||
|
for _, v := range c {
|
||||||
|
StructToXML(e, v, sorted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.EncodeToken(xml.EndElement{Name: node.Name})
|
||||||
|
return e.Flush()
|
||||||
|
}
|
43
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/functional_test.go
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/functional_test.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package v4_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = unit.Imported
|
||||||
|
|
||||||
|
func TestPresignHandler(t *testing.T) {
|
||||||
|
svc := s3.New(nil)
|
||||||
|
req, _ := svc.PutObjectRequest(&s3.PutObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
Key: aws.String("key"),
|
||||||
|
ContentDisposition: aws.String("a+b c$d"),
|
||||||
|
ACL: aws.String("public-read"),
|
||||||
|
})
|
||||||
|
req.Time = time.Unix(0, 0)
|
||||||
|
urlstr, err := req.Presign(5 * time.Minute)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
expectedDate := "19700101T000000Z"
|
||||||
|
expectedHeaders := "host;x-amz-acl"
|
||||||
|
expectedSig := "7edcb4e3a1bf12f4989018d75acbe3a7f03df24bd6f3112602d59fc551f0e4e2"
|
||||||
|
expectedCred := "AKID/19700101/mock-region/s3/aws4_request"
|
||||||
|
|
||||||
|
u, _ := url.Parse(urlstr)
|
||||||
|
urlQ := u.Query()
|
||||||
|
assert.Equal(t, expectedSig, urlQ.Get("X-Amz-Signature"))
|
||||||
|
assert.Equal(t, expectedCred, urlQ.Get("X-Amz-Credential"))
|
||||||
|
assert.Equal(t, expectedHeaders, urlQ.Get("X-Amz-SignedHeaders"))
|
||||||
|
assert.Equal(t, expectedDate, urlQ.Get("X-Amz-Date"))
|
||||||
|
assert.Equal(t, "300", urlQ.Get("X-Amz-Expires"))
|
||||||
|
|
||||||
|
assert.NotContains(t, urlstr, "+") // + encoded as %20
|
||||||
|
}
|
360
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4.go
generated
vendored
Normal file
360
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4.go
generated
vendored
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
// Package v4 implements signing for AWS V4 signer
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/protocol/rest"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
authHeaderPrefix = "AWS4-HMAC-SHA256"
|
||||||
|
timeFormat = "20060102T150405Z"
|
||||||
|
shortTimeFormat = "20060102"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ignoredHeaders = map[string]bool{
|
||||||
|
"Authorization": true,
|
||||||
|
"Content-Type": true,
|
||||||
|
"Content-Length": true,
|
||||||
|
"User-Agent": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
type signer struct {
|
||||||
|
Request *http.Request
|
||||||
|
Time time.Time
|
||||||
|
ExpireTime time.Duration
|
||||||
|
ServiceName string
|
||||||
|
Region string
|
||||||
|
CredValues credentials.Value
|
||||||
|
Credentials *credentials.Credentials
|
||||||
|
Query url.Values
|
||||||
|
Body io.ReadSeeker
|
||||||
|
Debug uint
|
||||||
|
Logger io.Writer
|
||||||
|
|
||||||
|
isPresign bool
|
||||||
|
formattedTime string
|
||||||
|
formattedShortTime string
|
||||||
|
|
||||||
|
signedHeaders string
|
||||||
|
canonicalHeaders string
|
||||||
|
canonicalString string
|
||||||
|
credentialString string
|
||||||
|
stringToSign string
|
||||||
|
signature string
|
||||||
|
authorization string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign requests with signature version 4.
|
||||||
|
//
|
||||||
|
// Will sign the requests with the service config's Credentials object
|
||||||
|
// Signing is skipped if the credentials is the credentials.AnonymousCredentials
|
||||||
|
// object.
|
||||||
|
func Sign(req *aws.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 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
region := req.Service.SigningRegion
|
||||||
|
if region == "" {
|
||||||
|
region = req.Service.Config.Region
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.Service.SigningName
|
||||||
|
if name == "" {
|
||||||
|
name = req.Service.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
s := signer{
|
||||||
|
Request: req.HTTPRequest,
|
||||||
|
Time: req.Time,
|
||||||
|
ExpireTime: req.ExpireTime,
|
||||||
|
Query: req.HTTPRequest.URL.Query(),
|
||||||
|
Body: req.Body,
|
||||||
|
ServiceName: name,
|
||||||
|
Region: region,
|
||||||
|
Credentials: req.Service.Config.Credentials,
|
||||||
|
Debug: req.Service.Config.LogLevel,
|
||||||
|
Logger: req.Service.Config.Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Error = s.sign()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) sign() error {
|
||||||
|
if v4.ExpireTime != 0 {
|
||||||
|
v4.isPresign = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v4.isRequestSigned() {
|
||||||
|
if !v4.Credentials.IsExpired() {
|
||||||
|
// If the request is already signed, and the credentials have not
|
||||||
|
// expired yet ignore the signing request.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The credentials have expired for this request. The current signing
|
||||||
|
// is invalid, and needs to be request because the request will fail.
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.removePresign()
|
||||||
|
// Update the request's query string to ensure the values stays in
|
||||||
|
// sync in the case retrieving the new credentials fails.
|
||||||
|
v4.Request.URL.RawQuery = v4.Query.Encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
v4.CredValues, err = v4.Credentials.Get()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
|
||||||
|
if v4.CredValues.SessionToken != "" {
|
||||||
|
v4.Query.Set("X-Amz-Security-Token", v4.CredValues.SessionToken)
|
||||||
|
} else {
|
||||||
|
v4.Query.Del("X-Amz-Security-Token")
|
||||||
|
}
|
||||||
|
} else if v4.CredValues.SessionToken != "" {
|
||||||
|
v4.Request.Header.Set("X-Amz-Security-Token", v4.CredValues.SessionToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4.build()
|
||||||
|
|
||||||
|
if v4.Debug > 0 {
|
||||||
|
v4.logSigningInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) logSigningInfo() {
|
||||||
|
out := v4.Logger
|
||||||
|
fmt.Fprintf(out, "---[ CANONICAL STRING ]-----------------------------\n")
|
||||||
|
fmt.Fprintln(out, v4.canonicalString)
|
||||||
|
fmt.Fprintf(out, "---[ STRING TO SIGN ]--------------------------------\n")
|
||||||
|
fmt.Fprintln(out, v4.stringToSign)
|
||||||
|
if v4.isPresign {
|
||||||
|
fmt.Fprintf(out, "---[ SIGNED URL ]--------------------------------\n")
|
||||||
|
fmt.Fprintln(out, v4.Request.URL)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(out, "-----------------------------------------------------\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) build() {
|
||||||
|
|
||||||
|
v4.buildTime() // no depends
|
||||||
|
v4.buildCredentialString() // no depends
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.buildQuery() // no depends
|
||||||
|
}
|
||||||
|
v4.buildCanonicalHeaders() // depends on cred string
|
||||||
|
v4.buildCanonicalString() // depends on canon headers / signed headers
|
||||||
|
v4.buildStringToSign() // depends on canon string
|
||||||
|
v4.buildSignature() // depends on string to sign
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.Request.URL.RawQuery += "&X-Amz-Signature=" + v4.signature
|
||||||
|
} else {
|
||||||
|
parts := []string{
|
||||||
|
authHeaderPrefix + " Credential=" + v4.CredValues.AccessKeyID + "/" + v4.credentialString,
|
||||||
|
"SignedHeaders=" + v4.signedHeaders,
|
||||||
|
"Signature=" + v4.signature,
|
||||||
|
}
|
||||||
|
v4.Request.Header.Set("Authorization", strings.Join(parts, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildTime() {
|
||||||
|
v4.formattedTime = v4.Time.UTC().Format(timeFormat)
|
||||||
|
v4.formattedShortTime = v4.Time.UTC().Format(shortTimeFormat)
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
duration := int64(v4.ExpireTime / time.Second)
|
||||||
|
v4.Query.Set("X-Amz-Date", v4.formattedTime)
|
||||||
|
v4.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
|
||||||
|
} else {
|
||||||
|
v4.Request.Header.Set("X-Amz-Date", v4.formattedTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildCredentialString() {
|
||||||
|
v4.credentialString = strings.Join([]string{
|
||||||
|
v4.formattedShortTime,
|
||||||
|
v4.Region,
|
||||||
|
v4.ServiceName,
|
||||||
|
"aws4_request",
|
||||||
|
}, "/")
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.Query.Set("X-Amz-Credential", v4.CredValues.AccessKeyID+"/"+v4.credentialString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildQuery() {
|
||||||
|
for k, h := range v4.Request.Header {
|
||||||
|
if strings.HasPrefix(http.CanonicalHeaderKey(k), "X-Amz-") {
|
||||||
|
continue // never hoist x-amz-* headers, they must be signed
|
||||||
|
}
|
||||||
|
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||||
|
continue // never hoist ignored headers
|
||||||
|
}
|
||||||
|
|
||||||
|
v4.Request.Header.Del(k)
|
||||||
|
v4.Query.Del(k)
|
||||||
|
for _, v := range h {
|
||||||
|
v4.Query.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildCanonicalHeaders() {
|
||||||
|
var headers []string
|
||||||
|
headers = append(headers, "host")
|
||||||
|
for k := range v4.Request.Header {
|
||||||
|
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||||
|
continue // ignored header
|
||||||
|
}
|
||||||
|
headers = append(headers, strings.ToLower(k))
|
||||||
|
}
|
||||||
|
sort.Strings(headers)
|
||||||
|
|
||||||
|
v4.signedHeaders = strings.Join(headers, ";")
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.Query.Set("X-Amz-SignedHeaders", v4.signedHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
headerValues := make([]string, len(headers))
|
||||||
|
for i, k := range headers {
|
||||||
|
if k == "host" {
|
||||||
|
headerValues[i] = "host:" + v4.Request.URL.Host
|
||||||
|
} else {
|
||||||
|
headerValues[i] = k + ":" +
|
||||||
|
strings.Join(v4.Request.Header[http.CanonicalHeaderKey(k)], ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v4.canonicalHeaders = strings.Join(headerValues, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildCanonicalString() {
|
||||||
|
v4.Request.URL.RawQuery = strings.Replace(v4.Query.Encode(), "+", "%20", -1)
|
||||||
|
uri := v4.Request.URL.Opaque
|
||||||
|
if uri != "" {
|
||||||
|
uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
|
||||||
|
} else {
|
||||||
|
uri = v4.Request.URL.Path
|
||||||
|
}
|
||||||
|
if uri == "" {
|
||||||
|
uri = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
if v4.ServiceName != "s3" {
|
||||||
|
uri = rest.EscapePath(uri, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4.canonicalString = strings.Join([]string{
|
||||||
|
v4.Request.Method,
|
||||||
|
uri,
|
||||||
|
v4.Request.URL.RawQuery,
|
||||||
|
v4.canonicalHeaders + "\n",
|
||||||
|
v4.signedHeaders,
|
||||||
|
v4.bodyDigest(),
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildStringToSign() {
|
||||||
|
v4.stringToSign = strings.Join([]string{
|
||||||
|
authHeaderPrefix,
|
||||||
|
v4.formattedTime,
|
||||||
|
v4.credentialString,
|
||||||
|
hex.EncodeToString(makeSha256([]byte(v4.canonicalString))),
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildSignature() {
|
||||||
|
secret := v4.CredValues.SecretAccessKey
|
||||||
|
date := makeHmac([]byte("AWS4"+secret), []byte(v4.formattedShortTime))
|
||||||
|
region := makeHmac(date, []byte(v4.Region))
|
||||||
|
service := makeHmac(region, []byte(v4.ServiceName))
|
||||||
|
credentials := makeHmac(service, []byte("aws4_request"))
|
||||||
|
signature := makeHmac(credentials, []byte(v4.stringToSign))
|
||||||
|
v4.signature = hex.EncodeToString(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) bodyDigest() string {
|
||||||
|
hash := v4.Request.Header.Get("X-Amz-Content-Sha256")
|
||||||
|
if hash == "" {
|
||||||
|
if v4.isPresign && v4.ServiceName == "s3" {
|
||||||
|
hash = "UNSIGNED-PAYLOAD"
|
||||||
|
} else if v4.Body == nil {
|
||||||
|
hash = hex.EncodeToString(makeSha256([]byte{}))
|
||||||
|
} else {
|
||||||
|
hash = hex.EncodeToString(makeSha256Reader(v4.Body))
|
||||||
|
}
|
||||||
|
v4.Request.Header.Add("X-Amz-Content-Sha256", hash)
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRequestSigned returns if the request is currently signed or presigned
|
||||||
|
func (v4 *signer) isRequestSigned() bool {
|
||||||
|
if v4.isPresign && v4.Query.Get("X-Amz-Signature") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if v4.Request.Header.Get("Authorization") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsign removes signing flags for both signed and presigned requests.
|
||||||
|
func (v4 *signer) removePresign() {
|
||||||
|
v4.Query.Del("X-Amz-Algorithm")
|
||||||
|
v4.Query.Del("X-Amz-Signature")
|
||||||
|
v4.Query.Del("X-Amz-Security-Token")
|
||||||
|
v4.Query.Del("X-Amz-Date")
|
||||||
|
v4.Query.Del("X-Amz-Expires")
|
||||||
|
v4.Query.Del("X-Amz-Credential")
|
||||||
|
v4.Query.Del("X-Amz-SignedHeaders")
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHmac(key []byte, data []byte) []byte {
|
||||||
|
hash := hmac.New(sha256.New, key)
|
||||||
|
hash.Write(data)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSha256(data []byte) []byte {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write(data)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSha256Reader(reader io.ReadSeeker) []byte {
|
||||||
|
hash := sha256.New()
|
||||||
|
start, _ := reader.Seek(0, 1)
|
||||||
|
defer reader.Seek(start, 0)
|
||||||
|
|
||||||
|
io.Copy(hash, reader)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
245
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4_test.go
generated
vendored
Normal file
245
Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4_test.go
generated
vendored
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildSigner(serviceName string, region string, signTime time.Time, expireTime time.Duration, body string) signer {
|
||||||
|
endpoint := "https://" + serviceName + "." + region + ".amazonaws.com"
|
||||||
|
reader := strings.NewReader(body)
|
||||||
|
req, _ := http.NewRequest("POST", endpoint, reader)
|
||||||
|
req.URL.Opaque = "//example.org/bucket/key-._~,!@#$%^&*()"
|
||||||
|
req.Header.Add("X-Amz-Target", "prefix.Operation")
|
||||||
|
req.Header.Add("Content-Type", "application/x-amz-json-1.0")
|
||||||
|
req.Header.Add("Content-Length", string(len(body)))
|
||||||
|
req.Header.Add("X-Amz-Meta-Other-Header", "some-value=!@#$%^&* (+)")
|
||||||
|
|
||||||
|
return signer{
|
||||||
|
Request: req,
|
||||||
|
Time: signTime,
|
||||||
|
ExpireTime: expireTime,
|
||||||
|
Query: req.URL.Query(),
|
||||||
|
Body: reader,
|
||||||
|
ServiceName: serviceName,
|
||||||
|
Region: region,
|
||||||
|
Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeWS(text string) string {
|
||||||
|
text = strings.Replace(text, " ", "", -1)
|
||||||
|
text = strings.Replace(text, "\n", "", -1)
|
||||||
|
text = strings.Replace(text, "\t", "", -1)
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEqual(t *testing.T, expected, given string) {
|
||||||
|
if removeWS(expected) != removeWS(given) {
|
||||||
|
t.Errorf("\nExpected: %s\nGiven: %s", expected, given)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPresignRequest(t *testing.T) {
|
||||||
|
signer := buildSigner("dynamodb", "us-east-1", time.Unix(0, 0), 300*time.Second, "{}")
|
||||||
|
signer.sign()
|
||||||
|
|
||||||
|
expectedDate := "19700101T000000Z"
|
||||||
|
expectedHeaders := "host;x-amz-meta-other-header;x-amz-target"
|
||||||
|
expectedSig := "5eeedebf6f995145ce56daa02902d10485246d3defb34f97b973c1f40ab82d36"
|
||||||
|
expectedCred := "AKID/19700101/us-east-1/dynamodb/aws4_request"
|
||||||
|
|
||||||
|
q := signer.Request.URL.Query()
|
||||||
|
assert.Equal(t, expectedSig, q.Get("X-Amz-Signature"))
|
||||||
|
assert.Equal(t, expectedCred, q.Get("X-Amz-Credential"))
|
||||||
|
assert.Equal(t, expectedHeaders, q.Get("X-Amz-SignedHeaders"))
|
||||||
|
assert.Equal(t, expectedDate, q.Get("X-Amz-Date"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignRequest(t *testing.T) {
|
||||||
|
signer := buildSigner("dynamodb", "us-east-1", time.Unix(0, 0), 0, "{}")
|
||||||
|
signer.sign()
|
||||||
|
|
||||||
|
expectedDate := "19700101T000000Z"
|
||||||
|
expectedSig := "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-meta-other-header;x-amz-security-token;x-amz-target, Signature=69ada33fec48180dab153576e4dd80c4e04124f80dda3eccfed8a67c2b91ed5e"
|
||||||
|
|
||||||
|
q := signer.Request.Header
|
||||||
|
assert.Equal(t, expectedSig, q.Get("Authorization"))
|
||||||
|
assert.Equal(t, expectedDate, q.Get("X-Amz-Date"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignEmptyBody(t *testing.T) {
|
||||||
|
signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "")
|
||||||
|
signer.Body = nil
|
||||||
|
signer.sign()
|
||||||
|
hash := signer.Request.Header.Get("X-Amz-Content-Sha256")
|
||||||
|
assert.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignBody(t *testing.T) {
|
||||||
|
signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "hello")
|
||||||
|
signer.sign()
|
||||||
|
hash := signer.Request.Header.Get("X-Amz-Content-Sha256")
|
||||||
|
assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignSeekedBody(t *testing.T) {
|
||||||
|
signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, " hello")
|
||||||
|
signer.Body.Read(make([]byte, 3)) // consume first 3 bytes so body is now "hello"
|
||||||
|
signer.sign()
|
||||||
|
hash := signer.Request.Header.Get("X-Amz-Content-Sha256")
|
||||||
|
assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash)
|
||||||
|
|
||||||
|
start, _ := signer.Body.Seek(0, 1)
|
||||||
|
assert.Equal(t, int64(3), start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPresignEmptyBodyS3(t *testing.T) {
|
||||||
|
signer := buildSigner("s3", "us-east-1", time.Now(), 5*time.Minute, "hello")
|
||||||
|
signer.sign()
|
||||||
|
hash := signer.Request.Header.Get("X-Amz-Content-Sha256")
|
||||||
|
assert.Equal(t, "UNSIGNED-PAYLOAD", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignPrecomputedBodyChecksum(t *testing.T) {
|
||||||
|
signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "hello")
|
||||||
|
signer.Request.Header.Set("X-Amz-Content-Sha256", "PRECOMPUTED")
|
||||||
|
signer.sign()
|
||||||
|
hash := signer.Request.Header.Get("X-Amz-Content-Sha256")
|
||||||
|
assert.Equal(t, "PRECOMPUTED", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymousCredentials(t *testing.T) {
|
||||||
|
r := aws.NewRequest(
|
||||||
|
aws.NewService(&aws.Config{Credentials: credentials.AnonymousCredentials}),
|
||||||
|
&aws.Operation{
|
||||||
|
Name: "BatchGetItem",
|
||||||
|
HTTPMethod: "POST",
|
||||||
|
HTTPPath: "/",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
Sign(r)
|
||||||
|
|
||||||
|
urlQ := r.HTTPRequest.URL.Query()
|
||||||
|
assert.Empty(t, urlQ.Get("X-Amz-Signature"))
|
||||||
|
assert.Empty(t, urlQ.Get("X-Amz-Credential"))
|
||||||
|
assert.Empty(t, urlQ.Get("X-Amz-SignedHeaders"))
|
||||||
|
assert.Empty(t, urlQ.Get("X-Amz-Date"))
|
||||||
|
|
||||||
|
hQ := r.HTTPRequest.Header
|
||||||
|
assert.Empty(t, hQ.Get("Authorization"))
|
||||||
|
assert.Empty(t, hQ.Get("X-Amz-Date"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIgnoreResignRequestWithValidCreds(t *testing.T) {
|
||||||
|
r := aws.NewRequest(
|
||||||
|
aws.NewService(&aws.Config{
|
||||||
|
Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
|
||||||
|
Region: "us-west-2",
|
||||||
|
}),
|
||||||
|
&aws.Operation{
|
||||||
|
Name: "BatchGetItem",
|
||||||
|
HTTPMethod: "POST",
|
||||||
|
HTTPPath: "/",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
Sign(r)
|
||||||
|
sig := r.HTTPRequest.Header.Get("Authorization")
|
||||||
|
|
||||||
|
Sign(r)
|
||||||
|
assert.Equal(t, sig, r.HTTPRequest.Header.Get("Authorization"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIgnorePreResignRequestWithValidCreds(t *testing.T) {
|
||||||
|
r := aws.NewRequest(
|
||||||
|
aws.NewService(&aws.Config{
|
||||||
|
Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
|
||||||
|
Region: "us-west-2",
|
||||||
|
}),
|
||||||
|
&aws.Operation{
|
||||||
|
Name: "BatchGetItem",
|
||||||
|
HTTPMethod: "POST",
|
||||||
|
HTTPPath: "/",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
r.ExpireTime = time.Minute * 10
|
||||||
|
|
||||||
|
Sign(r)
|
||||||
|
sig := r.HTTPRequest.Header.Get("X-Amz-Signature")
|
||||||
|
|
||||||
|
Sign(r)
|
||||||
|
assert.Equal(t, sig, r.HTTPRequest.Header.Get("X-Amz-Signature"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResignRequestExpiredCreds(t *testing.T) {
|
||||||
|
creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
|
||||||
|
r := aws.NewRequest(
|
||||||
|
aws.NewService(&aws.Config{Credentials: creds}),
|
||||||
|
&aws.Operation{
|
||||||
|
Name: "BatchGetItem",
|
||||||
|
HTTPMethod: "POST",
|
||||||
|
HTTPPath: "/",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
Sign(r)
|
||||||
|
querySig := r.HTTPRequest.Header.Get("Authorization")
|
||||||
|
|
||||||
|
creds.Expire()
|
||||||
|
|
||||||
|
Sign(r)
|
||||||
|
assert.NotEqual(t, querySig, r.HTTPRequest.Header.Get("Authorization"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreResignRequestExpiredCreds(t *testing.T) {
|
||||||
|
provider := &credentials.StaticProvider{credentials.Value{"AKID", "SECRET", "SESSION"}}
|
||||||
|
creds := credentials.NewCredentials(provider)
|
||||||
|
r := aws.NewRequest(
|
||||||
|
aws.NewService(&aws.Config{Credentials: creds}),
|
||||||
|
&aws.Operation{
|
||||||
|
Name: "BatchGetItem",
|
||||||
|
HTTPMethod: "POST",
|
||||||
|
HTTPPath: "/",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
r.ExpireTime = time.Minute * 10
|
||||||
|
|
||||||
|
Sign(r)
|
||||||
|
querySig := r.HTTPRequest.URL.Query().Get("X-Amz-Signature")
|
||||||
|
|
||||||
|
creds.Expire()
|
||||||
|
r.Time = time.Now().Add(time.Hour * 48)
|
||||||
|
|
||||||
|
Sign(r)
|
||||||
|
assert.NotEqual(t, querySig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPresignRequest(b *testing.B) {
|
||||||
|
signer := buildSigner("dynamodb", "us-east-1", time.Now(), 300*time.Second, "{}")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
signer.sign()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSignRequest(b *testing.B) {
|
||||||
|
signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "{}")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
signer.sign()
|
||||||
|
}
|
||||||
|
}
|
4804
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/api.go
generated
vendored
Normal file
4804
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/api.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
42
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/bucket_location.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/bucket_location.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var reBucketLocation = regexp.MustCompile(`>([^<>]+)<\/Location`)
|
||||||
|
|
||||||
|
func buildGetBucketLocation(r *aws.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 *aws.Request) {
|
||||||
|
if r.ParamsFilled() && 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/bucket_location_test.go
generated
vendored
Normal file
75
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/bucket_location_test.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package s3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = unit.Imported
|
||||||
|
var s3LocationTests = []struct {
|
||||||
|
body string
|
||||||
|
loc string
|
||||||
|
}{
|
||||||
|
{`<?xml version="1.0" encoding="UTF-8"?><LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>`, ``},
|
||||||
|
{`<?xml version="1.0" encoding="UTF-8"?><LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">EU</LocationConstraint>`, `EU`},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBucketLocation(t *testing.T) {
|
||||||
|
for _, test := range s3LocationTests {
|
||||||
|
s := s3.New(nil)
|
||||||
|
s.Handlers.Send.Clear()
|
||||||
|
s.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
reader := ioutil.NopCloser(bytes.NewReader([]byte(test.body)))
|
||||||
|
r.HTTPResponse = &http.Response{StatusCode: 200, Body: reader}
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, err := s.GetBucketLocation(&s3.GetBucketLocationInput{Bucket: aws.String("bucket")})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if test.loc == "" {
|
||||||
|
assert.Nil(t, resp.LocationConstraint)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, test.loc, *resp.LocationConstraint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPopulateLocationConstraint(t *testing.T) {
|
||||||
|
s := s3.New(nil)
|
||||||
|
in := &s3.CreateBucketInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
}
|
||||||
|
req, _ := s.CreateBucketRequest(in)
|
||||||
|
err := req.Build()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "mock-region", awsutil.ValuesAtPath(req.Params, "CreateBucketConfiguration.LocationConstraint")[0])
|
||||||
|
assert.Nil(t, in.CreateBucketConfiguration) // don't modify original params
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoPopulateLocationConstraintIfProvided(t *testing.T) {
|
||||||
|
s := s3.New(nil)
|
||||||
|
req, _ := s.CreateBucketRequest(&s3.CreateBucketInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
CreateBucketConfiguration: &s3.CreateBucketConfiguration{},
|
||||||
|
})
|
||||||
|
err := req.Build()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(awsutil.ValuesAtPath(req.Params, "CreateBucketConfiguration.LocationConstraint")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoPopulateLocationConstraintIfClassic(t *testing.T) {
|
||||||
|
s := s3.New(&aws.Config{Region: "us-east-1"})
|
||||||
|
req, _ := s.CreateBucketRequest(&s3.CreateBucketInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
})
|
||||||
|
err := req.Build()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(awsutil.ValuesAtPath(req.Params, "CreateBucketConfiguration.LocationConstraint")))
|
||||||
|
}
|
36
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/content_md5.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/content_md5.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// contentMD5 computes and sets the HTTP Content-MD5 header for requests that
|
||||||
|
// require it.
|
||||||
|
func contentMD5(r *aws.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))
|
||||||
|
}
|
32
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/customizations.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/customizations.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import "github.com/aws/aws-sdk-go/aws"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
initService = func(s *aws.Service) {
|
||||||
|
// Support building custom host-style bucket endpoints
|
||||||
|
s.Handlers.Build.PushFront(updateHostWithBucket)
|
||||||
|
|
||||||
|
// Require SSL when using SSE keys
|
||||||
|
s.Handlers.Validate.PushBack(validateSSERequiresSSL)
|
||||||
|
s.Handlers.Build.PushBack(computeSSEKeys)
|
||||||
|
|
||||||
|
// S3 uses custom error unmarshaling logic
|
||||||
|
s.Handlers.UnmarshalError.Clear()
|
||||||
|
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
initRequest = func(r *aws.Request) {
|
||||||
|
switch r.Operation.Name {
|
||||||
|
case opPutBucketCORS, opPutBucketLifecycle, opPutBucketPolicy, opPutBucketTagging, opDeleteObjects:
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/customizations_test.go
generated
vendored
Normal file
90
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/customizations_test.go
generated
vendored
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package s3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = unit.Imported
|
||||||
|
|
||||||
|
func assertMD5(t *testing.T, req *aws.Request) {
|
||||||
|
err := req.Build()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
b, _ := ioutil.ReadAll(req.HTTPRequest.Body)
|
||||||
|
out := md5.Sum(b)
|
||||||
|
assert.NotEmpty(t, b)
|
||||||
|
assert.Equal(t, base64.StdEncoding.EncodeToString(out[:]), req.HTTPRequest.Header.Get("Content-MD5"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMD5InPutBucketCORS(t *testing.T) {
|
||||||
|
svc := s3.New(nil)
|
||||||
|
req, _ := svc.PutBucketCORSRequest(&s3.PutBucketCORSInput{
|
||||||
|
Bucket: aws.String("bucketname"),
|
||||||
|
CORSConfiguration: &s3.CORSConfiguration{
|
||||||
|
CORSRules: []*s3.CORSRule{
|
||||||
|
{AllowedMethods: []*string{aws.String("GET")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assertMD5(t, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMD5InPutBucketLifecycle(t *testing.T) {
|
||||||
|
svc := s3.New(nil)
|
||||||
|
req, _ := svc.PutBucketLifecycleRequest(&s3.PutBucketLifecycleInput{
|
||||||
|
Bucket: aws.String("bucketname"),
|
||||||
|
LifecycleConfiguration: &s3.LifecycleConfiguration{
|
||||||
|
Rules: []*s3.LifecycleRule{
|
||||||
|
{
|
||||||
|
ID: aws.String("ID"),
|
||||||
|
Prefix: aws.String("Prefix"),
|
||||||
|
Status: aws.String("Enabled"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assertMD5(t, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMD5InPutBucketPolicy(t *testing.T) {
|
||||||
|
svc := s3.New(nil)
|
||||||
|
req, _ := svc.PutBucketPolicyRequest(&s3.PutBucketPolicyInput{
|
||||||
|
Bucket: aws.String("bucketname"),
|
||||||
|
Policy: aws.String("{}"),
|
||||||
|
})
|
||||||
|
assertMD5(t, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMD5InPutBucketTagging(t *testing.T) {
|
||||||
|
svc := s3.New(nil)
|
||||||
|
req, _ := svc.PutBucketTaggingRequest(&s3.PutBucketTaggingInput{
|
||||||
|
Bucket: aws.String("bucketname"),
|
||||||
|
Tagging: &s3.Tagging{
|
||||||
|
TagSet: []*s3.Tag{
|
||||||
|
{Key: aws.String("KEY"), Value: aws.String("VALUE")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assertMD5(t, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMD5InDeleteObjects(t *testing.T) {
|
||||||
|
svc := s3.New(nil)
|
||||||
|
req, _ := svc.DeleteObjectsRequest(&s3.DeleteObjectsInput{
|
||||||
|
Bucket: aws.String("bucketname"),
|
||||||
|
Delete: &s3.Delete{
|
||||||
|
Objects: []*s3.ObjectIdentifier{
|
||||||
|
{Key: aws.String("key")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assertMD5(t, req)
|
||||||
|
}
|
1928
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/examples_test.go
generated
vendored
Normal file
1928
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/examples_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
53
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/host_style_bucket.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/host_style_bucket.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
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, "..")
|
||||||
|
}
|
||||||
|
|
||||||
|
// hostStyleBucketName 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 hostStyleBucketName(r *aws.Request, bucket string) bool {
|
||||||
|
if r.Config.S3ForcePathStyle {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bucket might be DNS compatible but dots in the hostname will fail
|
||||||
|
// certificate validation, so do not use host-style.
|
||||||
|
if r.HTTPRequest.URL.Scheme == "https" && strings.Contains(bucket, ".") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use host-style if the bucket is DNS compatible
|
||||||
|
return dnsCompatibleBucketName(bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateHostWithBucket(r *aws.Request) {
|
||||||
|
b := awsutil.ValuesAtPath(r.Params, "Bucket")
|
||||||
|
if len(b) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if bucket := b[0].(string); bucket != "" && hostStyleBucketName(r, bucket) {
|
||||||
|
r.HTTPRequest.URL.Host = bucket + "." + r.HTTPRequest.URL.Host
|
||||||
|
r.HTTPRequest.URL.Path = strings.Replace(r.HTTPRequest.URL.Path, "/{Bucket}", "", -1)
|
||||||
|
if r.HTTPRequest.URL.Path == "" {
|
||||||
|
r.HTTPRequest.URL.Path = "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/host_style_bucket_test.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/host_style_bucket_test.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package s3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type s3BucketTest struct {
|
||||||
|
bucket string
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = unit.Imported
|
||||||
|
|
||||||
|
sslTests = []s3BucketTest{
|
||||||
|
{"abc", "https://abc.s3.mock-region.amazonaws.com/"},
|
||||||
|
{"a$b$c", "https://s3.mock-region.amazonaws.com/a%24b%24c"},
|
||||||
|
{"a.b.c", "https://s3.mock-region.amazonaws.com/a.b.c"},
|
||||||
|
{"a..bc", "https://s3.mock-region.amazonaws.com/a..bc"},
|
||||||
|
}
|
||||||
|
|
||||||
|
nosslTests = []s3BucketTest{
|
||||||
|
{"a.b.c", "http://a.b.c.s3.mock-region.amazonaws.com/"},
|
||||||
|
{"a..bc", "http://s3.mock-region.amazonaws.com/a..bc"},
|
||||||
|
}
|
||||||
|
|
||||||
|
forcepathTests = []s3BucketTest{
|
||||||
|
{"abc", "https://s3.mock-region.amazonaws.com/abc"},
|
||||||
|
{"a$b$c", "https://s3.mock-region.amazonaws.com/a%24b%24c"},
|
||||||
|
{"a.b.c", "https://s3.mock-region.amazonaws.com/a.b.c"},
|
||||||
|
{"a..bc", "https://s3.mock-region.amazonaws.com/a..bc"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func runTests(t *testing.T, svc *s3.S3, tests []s3BucketTest) {
|
||||||
|
for _, test := range tests {
|
||||||
|
req, _ := svc.ListObjectsRequest(&s3.ListObjectsInput{Bucket: &test.bucket})
|
||||||
|
req.Build()
|
||||||
|
assert.Equal(t, test.url, req.HTTPRequest.URL.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostStyleBucketBuild(t *testing.T) {
|
||||||
|
s := s3.New(nil)
|
||||||
|
runTests(t, s, sslTests)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostStyleBucketBuildNoSSL(t *testing.T) {
|
||||||
|
s := s3.New(&aws.Config{DisableSSL: true})
|
||||||
|
runTests(t, s, nosslTests)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathStyleBucketBuild(t *testing.T) {
|
||||||
|
s := s3.New(&aws.Config{S3ForcePathStyle: true})
|
||||||
|
runTests(t, s, forcepathTests)
|
||||||
|
}
|
119
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3iface/interface.go
generated
vendored
Normal file
119
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3iface/interface.go
generated
vendored
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package s3iface provides an interface for the Amazon Simple Storage Service.
|
||||||
|
package s3iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// S3API is the interface type for s3.S3.
|
||||||
|
type S3API interface {
|
||||||
|
AbortMultipartUpload(*s3.AbortMultipartUploadInput) (*s3.AbortMultipartUploadOutput, error)
|
||||||
|
|
||||||
|
CompleteMultipartUpload(*s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error)
|
||||||
|
|
||||||
|
CopyObject(*s3.CopyObjectInput) (*s3.CopyObjectOutput, error)
|
||||||
|
|
||||||
|
CreateBucket(*s3.CreateBucketInput) (*s3.CreateBucketOutput, error)
|
||||||
|
|
||||||
|
CreateMultipartUpload(*s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error)
|
||||||
|
|
||||||
|
DeleteBucket(*s3.DeleteBucketInput) (*s3.DeleteBucketOutput, error)
|
||||||
|
|
||||||
|
DeleteBucketCORS(*s3.DeleteBucketCORSInput) (*s3.DeleteBucketCORSOutput, error)
|
||||||
|
|
||||||
|
DeleteBucketLifecycle(*s3.DeleteBucketLifecycleInput) (*s3.DeleteBucketLifecycleOutput, error)
|
||||||
|
|
||||||
|
DeleteBucketPolicy(*s3.DeleteBucketPolicyInput) (*s3.DeleteBucketPolicyOutput, error)
|
||||||
|
|
||||||
|
DeleteBucketReplication(*s3.DeleteBucketReplicationInput) (*s3.DeleteBucketReplicationOutput, error)
|
||||||
|
|
||||||
|
DeleteBucketTagging(*s3.DeleteBucketTaggingInput) (*s3.DeleteBucketTaggingOutput, error)
|
||||||
|
|
||||||
|
DeleteBucketWebsite(*s3.DeleteBucketWebsiteInput) (*s3.DeleteBucketWebsiteOutput, error)
|
||||||
|
|
||||||
|
DeleteObject(*s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error)
|
||||||
|
|
||||||
|
DeleteObjects(*s3.DeleteObjectsInput) (*s3.DeleteObjectsOutput, error)
|
||||||
|
|
||||||
|
GetBucketACL(*s3.GetBucketACLInput) (*s3.GetBucketACLOutput, error)
|
||||||
|
|
||||||
|
GetBucketCORS(*s3.GetBucketCORSInput) (*s3.GetBucketCORSOutput, error)
|
||||||
|
|
||||||
|
GetBucketLifecycle(*s3.GetBucketLifecycleInput) (*s3.GetBucketLifecycleOutput, error)
|
||||||
|
|
||||||
|
GetBucketLocation(*s3.GetBucketLocationInput) (*s3.GetBucketLocationOutput, error)
|
||||||
|
|
||||||
|
GetBucketLogging(*s3.GetBucketLoggingInput) (*s3.GetBucketLoggingOutput, error)
|
||||||
|
|
||||||
|
GetBucketNotification(*s3.GetBucketNotificationConfigurationRequest) (*s3.NotificationConfigurationDeprecated, error)
|
||||||
|
|
||||||
|
GetBucketNotificationConfiguration(*s3.GetBucketNotificationConfigurationRequest) (*s3.NotificationConfiguration, error)
|
||||||
|
|
||||||
|
GetBucketPolicy(*s3.GetBucketPolicyInput) (*s3.GetBucketPolicyOutput, error)
|
||||||
|
|
||||||
|
GetBucketReplication(*s3.GetBucketReplicationInput) (*s3.GetBucketReplicationOutput, error)
|
||||||
|
|
||||||
|
GetBucketRequestPayment(*s3.GetBucketRequestPaymentInput) (*s3.GetBucketRequestPaymentOutput, error)
|
||||||
|
|
||||||
|
GetBucketTagging(*s3.GetBucketTaggingInput) (*s3.GetBucketTaggingOutput, error)
|
||||||
|
|
||||||
|
GetBucketVersioning(*s3.GetBucketVersioningInput) (*s3.GetBucketVersioningOutput, error)
|
||||||
|
|
||||||
|
GetBucketWebsite(*s3.GetBucketWebsiteInput) (*s3.GetBucketWebsiteOutput, error)
|
||||||
|
|
||||||
|
GetObject(*s3.GetObjectInput) (*s3.GetObjectOutput, error)
|
||||||
|
|
||||||
|
GetObjectACL(*s3.GetObjectACLInput) (*s3.GetObjectACLOutput, error)
|
||||||
|
|
||||||
|
GetObjectTorrent(*s3.GetObjectTorrentInput) (*s3.GetObjectTorrentOutput, error)
|
||||||
|
|
||||||
|
HeadBucket(*s3.HeadBucketInput) (*s3.HeadBucketOutput, error)
|
||||||
|
|
||||||
|
HeadObject(*s3.HeadObjectInput) (*s3.HeadObjectOutput, error)
|
||||||
|
|
||||||
|
ListBuckets(*s3.ListBucketsInput) (*s3.ListBucketsOutput, error)
|
||||||
|
|
||||||
|
ListMultipartUploads(*s3.ListMultipartUploadsInput) (*s3.ListMultipartUploadsOutput, error)
|
||||||
|
|
||||||
|
ListObjectVersions(*s3.ListObjectVersionsInput) (*s3.ListObjectVersionsOutput, error)
|
||||||
|
|
||||||
|
ListObjects(*s3.ListObjectsInput) (*s3.ListObjectsOutput, error)
|
||||||
|
|
||||||
|
ListParts(*s3.ListPartsInput) (*s3.ListPartsOutput, error)
|
||||||
|
|
||||||
|
PutBucketACL(*s3.PutBucketACLInput) (*s3.PutBucketACLOutput, error)
|
||||||
|
|
||||||
|
PutBucketCORS(*s3.PutBucketCORSInput) (*s3.PutBucketCORSOutput, error)
|
||||||
|
|
||||||
|
PutBucketLifecycle(*s3.PutBucketLifecycleInput) (*s3.PutBucketLifecycleOutput, error)
|
||||||
|
|
||||||
|
PutBucketLogging(*s3.PutBucketLoggingInput) (*s3.PutBucketLoggingOutput, error)
|
||||||
|
|
||||||
|
PutBucketNotification(*s3.PutBucketNotificationInput) (*s3.PutBucketNotificationOutput, error)
|
||||||
|
|
||||||
|
PutBucketNotificationConfiguration(*s3.PutBucketNotificationConfigurationInput) (*s3.PutBucketNotificationConfigurationOutput, error)
|
||||||
|
|
||||||
|
PutBucketPolicy(*s3.PutBucketPolicyInput) (*s3.PutBucketPolicyOutput, error)
|
||||||
|
|
||||||
|
PutBucketReplication(*s3.PutBucketReplicationInput) (*s3.PutBucketReplicationOutput, error)
|
||||||
|
|
||||||
|
PutBucketRequestPayment(*s3.PutBucketRequestPaymentInput) (*s3.PutBucketRequestPaymentOutput, error)
|
||||||
|
|
||||||
|
PutBucketTagging(*s3.PutBucketTaggingInput) (*s3.PutBucketTaggingOutput, error)
|
||||||
|
|
||||||
|
PutBucketVersioning(*s3.PutBucketVersioningInput) (*s3.PutBucketVersioningOutput, error)
|
||||||
|
|
||||||
|
PutBucketWebsite(*s3.PutBucketWebsiteInput) (*s3.PutBucketWebsiteOutput, error)
|
||||||
|
|
||||||
|
PutObject(*s3.PutObjectInput) (*s3.PutObjectOutput, error)
|
||||||
|
|
||||||
|
PutObjectACL(*s3.PutObjectACLInput) (*s3.PutObjectACLOutput, error)
|
||||||
|
|
||||||
|
RestoreObject(*s3.RestoreObjectInput) (*s3.RestoreObjectOutput, error)
|
||||||
|
|
||||||
|
UploadPart(*s3.UploadPartInput) (*s3.UploadPartOutput, error)
|
||||||
|
|
||||||
|
UploadPartCopy(*s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error)
|
||||||
|
}
|
15
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3iface/interface_test.go
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3iface/interface_test.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||||
|
|
||||||
|
package s3iface_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3/s3iface"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
assert.Implements(t, (*s3iface.S3API)(nil), s3.New(nil))
|
||||||
|
}
|
257
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3manager/download.go
generated
vendored
Normal file
257
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3manager/download.go
generated
vendored
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
package s3manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The default range of bytes to get at a time when using Download().
|
||||||
|
var DefaultDownloadPartSize int64 = 1024 * 1024 * 5
|
||||||
|
|
||||||
|
// The default number of goroutines to spin up when using Download().
|
||||||
|
var DefaultDownloadConcurrency = 5
|
||||||
|
|
||||||
|
// The default set of options used when opts is nil in Download().
|
||||||
|
var DefaultDownloadOptions = &DownloadOptions{
|
||||||
|
PartSize: DefaultDownloadPartSize,
|
||||||
|
Concurrency: DefaultDownloadConcurrency,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadOptions keeps tracks of extra options to pass to an Download() call.
|
||||||
|
type DownloadOptions struct {
|
||||||
|
// The buffer size (in bytes) to use when buffering data into chunks and
|
||||||
|
// sending them as parts to S3. The minimum allowed part size is 5MB, and
|
||||||
|
// if this value is set to zero, the DefaultPartSize value will be used.
|
||||||
|
PartSize int64
|
||||||
|
|
||||||
|
// The number of goroutines to spin up in parallel when sending parts.
|
||||||
|
// If this is set to zero, the DefaultConcurrency value will be used.
|
||||||
|
Concurrency int
|
||||||
|
|
||||||
|
// An S3 client to use when performing downloads. Leave this as nil to use
|
||||||
|
// a default client.
|
||||||
|
S3 *s3.S3
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDownloader creates a new Downloader structure that downloads an object
|
||||||
|
// from S3 in concurrent chunks. Pass in an optional DownloadOptions struct
|
||||||
|
// to customize the downloader behavior.
|
||||||
|
func NewDownloader(opts *DownloadOptions) *Downloader {
|
||||||
|
if opts == nil {
|
||||||
|
opts = DefaultDownloadOptions
|
||||||
|
}
|
||||||
|
return &Downloader{opts: opts}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Downloader structure that calls Download(). It is safe to call Download()
|
||||||
|
// on this structure for multiple objects and across concurrent goroutines.
|
||||||
|
type Downloader struct {
|
||||||
|
opts *DownloadOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download downloads an object in S3 and writes the payload into w using
|
||||||
|
// concurrent GET requests.
|
||||||
|
//
|
||||||
|
// It is safe to call this method for multiple objects and across concurrent
|
||||||
|
// goroutines.
|
||||||
|
func (d *Downloader) Download(w io.WriterAt, input *s3.GetObjectInput) (n int64, err error) {
|
||||||
|
impl := downloader{w: w, in: input, opts: *d.opts}
|
||||||
|
return impl.download()
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloader is the implementation structure used internally by Downloader.
|
||||||
|
type downloader struct {
|
||||||
|
opts DownloadOptions
|
||||||
|
in *s3.GetObjectInput
|
||||||
|
w io.WriterAt
|
||||||
|
|
||||||
|
wg sync.WaitGroup
|
||||||
|
m sync.Mutex
|
||||||
|
|
||||||
|
pos int64
|
||||||
|
totalBytes int64
|
||||||
|
written int64
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes the downloader with default options.
|
||||||
|
func (d *downloader) init() {
|
||||||
|
d.totalBytes = -1
|
||||||
|
|
||||||
|
if d.opts.Concurrency == 0 {
|
||||||
|
d.opts.Concurrency = DefaultDownloadConcurrency
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.opts.PartSize == 0 {
|
||||||
|
d.opts.PartSize = DefaultDownloadPartSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.opts.S3 == nil {
|
||||||
|
d.opts.S3 = s3.New(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// download performs the implementation of the object download across ranged
|
||||||
|
// GETs.
|
||||||
|
func (d *downloader) download() (n int64, err error) {
|
||||||
|
d.init()
|
||||||
|
|
||||||
|
// Spin up workers
|
||||||
|
ch := make(chan dlchunk, d.opts.Concurrency)
|
||||||
|
for i := 0; i < d.opts.Concurrency; i++ {
|
||||||
|
d.wg.Add(1)
|
||||||
|
go d.downloadPart(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign work
|
||||||
|
for d.geterr() == nil {
|
||||||
|
if d.pos != 0 {
|
||||||
|
// This is not the first chunk, let's wait until we know the total
|
||||||
|
// size of the payload so we can see if we have read the entire
|
||||||
|
// object.
|
||||||
|
total := d.getTotalBytes()
|
||||||
|
|
||||||
|
if total < 0 {
|
||||||
|
// Total has not yet been set, so sleep and loop around while
|
||||||
|
// waiting for our first worker to resolve this value.
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
} else if d.pos >= total {
|
||||||
|
break // We're finished queueing chunks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue the next range of bytes to read.
|
||||||
|
ch <- dlchunk{w: d.w, start: d.pos, size: d.opts.PartSize}
|
||||||
|
d.pos += d.opts.PartSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for completion
|
||||||
|
close(ch)
|
||||||
|
d.wg.Wait()
|
||||||
|
|
||||||
|
// Return error
|
||||||
|
return d.written, d.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadPart is an individual goroutine worker reading from the ch channel
|
||||||
|
// and performing a GetObject request on the data with a given byte range.
|
||||||
|
//
|
||||||
|
// If this is the first worker, this operation also resolves the total number
|
||||||
|
// of bytes to be read so that the worker manager knows when it is finished.
|
||||||
|
func (d *downloader) downloadPart(ch chan dlchunk) {
|
||||||
|
defer d.wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
chunk, ok := <-ch
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.geterr() == nil {
|
||||||
|
// Get the next byte range of data
|
||||||
|
in := &s3.GetObjectInput{}
|
||||||
|
awsutil.Copy(in, d.in)
|
||||||
|
rng := fmt.Sprintf("bytes=%d-%d",
|
||||||
|
chunk.start, chunk.start+chunk.size-1)
|
||||||
|
in.Range = &rng
|
||||||
|
|
||||||
|
resp, err := d.opts.S3.GetObject(in)
|
||||||
|
if err != nil {
|
||||||
|
d.seterr(err)
|
||||||
|
} else {
|
||||||
|
d.setTotalBytes(resp) // Set total if not yet set.
|
||||||
|
|
||||||
|
n, err := io.Copy(&chunk, resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
d.seterr(err)
|
||||||
|
}
|
||||||
|
d.incrwritten(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTotalBytes is a thread-safe getter for retrieving the total byte status.
|
||||||
|
func (d *downloader) getTotalBytes() int64 {
|
||||||
|
d.m.Lock()
|
||||||
|
defer d.m.Unlock()
|
||||||
|
|
||||||
|
return d.totalBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTotalBytes is a thread-safe setter for setting the total byte status.
|
||||||
|
func (d *downloader) setTotalBytes(resp *s3.GetObjectOutput) {
|
||||||
|
d.m.Lock()
|
||||||
|
defer d.m.Unlock()
|
||||||
|
|
||||||
|
if d.totalBytes >= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(*resp.ContentRange, "/")
|
||||||
|
total, err := strconv.ParseInt(parts[len(parts)-1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
d.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.totalBytes = total
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *downloader) incrwritten(n int64) {
|
||||||
|
d.m.Lock()
|
||||||
|
defer d.m.Unlock()
|
||||||
|
|
||||||
|
d.written += n
|
||||||
|
}
|
||||||
|
|
||||||
|
// geterr is a thread-safe getter for the error object
|
||||||
|
func (d *downloader) geterr() error {
|
||||||
|
d.m.Lock()
|
||||||
|
defer d.m.Unlock()
|
||||||
|
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// seterr is a thread-safe setter for the error object
|
||||||
|
func (d *downloader) seterr(e error) {
|
||||||
|
d.m.Lock()
|
||||||
|
defer d.m.Unlock()
|
||||||
|
|
||||||
|
d.err = e
|
||||||
|
}
|
||||||
|
|
||||||
|
// dlchunk represents a single chunk of data to write by the worker routine.
|
||||||
|
// This structure also implements an io.SectionReader style interface for
|
||||||
|
// io.WriterAt, effectively making it an io.SectionWriter (which does not
|
||||||
|
// exist).
|
||||||
|
type dlchunk struct {
|
||||||
|
w io.WriterAt
|
||||||
|
start int64
|
||||||
|
size int64
|
||||||
|
cur int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write wraps io.WriterAt for the dlchunk, writing from the dlchunk's start
|
||||||
|
// position to its end (or EOF).
|
||||||
|
func (c *dlchunk) Write(p []byte) (n int, err error) {
|
||||||
|
if c.cur >= c.size {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = c.w.WriteAt(p, c.start+c.cur)
|
||||||
|
c.cur += int64(n)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
165
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3manager/download_test.go
generated
vendored
Normal file
165
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3manager/download_test.go
generated
vendored
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
package s3manager_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = unit.Imported
|
||||||
|
|
||||||
|
func dlLoggingSvc(data []byte) (*s3.S3, *[]string, *[]string) {
|
||||||
|
var m sync.Mutex
|
||||||
|
names := []string{}
|
||||||
|
ranges := []string{}
|
||||||
|
|
||||||
|
svc := s3.New(nil)
|
||||||
|
svc.Handlers.Send.Clear()
|
||||||
|
svc.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
names = append(names, r.Operation.Name)
|
||||||
|
ranges = append(ranges, *r.Params.(*s3.GetObjectInput).Range)
|
||||||
|
|
||||||
|
rerng := regexp.MustCompile(`bytes=(\d+)-(\d+)`)
|
||||||
|
rng := rerng.FindStringSubmatch(r.HTTPRequest.Header.Get("Range"))
|
||||||
|
start, _ := strconv.ParseInt(rng[1], 10, 64)
|
||||||
|
fin, _ := strconv.ParseInt(rng[2], 10, 64)
|
||||||
|
fin++
|
||||||
|
|
||||||
|
if fin > int64(len(data)) {
|
||||||
|
fin = int64(len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
r.HTTPResponse = &http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Body: ioutil.NopCloser(bytes.NewReader(data[start:fin])),
|
||||||
|
Header: http.Header{},
|
||||||
|
}
|
||||||
|
r.HTTPResponse.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d",
|
||||||
|
start, fin, len(data)))
|
||||||
|
})
|
||||||
|
|
||||||
|
return svc, &names, &ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlwriter struct {
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDLWriter(size int) *dlwriter {
|
||||||
|
return &dlwriter{buf: make([]byte, size)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dlwriter) WriteAt(p []byte, pos int64) (n int, err error) {
|
||||||
|
if pos > int64(len(d.buf)) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
written := 0
|
||||||
|
for i, b := range p {
|
||||||
|
if i >= len(d.buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.buf[pos+int64(i)] = b
|
||||||
|
written++
|
||||||
|
}
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadOrder(t *testing.T) {
|
||||||
|
s, names, ranges := dlLoggingSvc(buf12MB)
|
||||||
|
|
||||||
|
opts := &s3manager.DownloadOptions{S3: s, Concurrency: 1}
|
||||||
|
d := s3manager.NewDownloader(opts)
|
||||||
|
w := newDLWriter(len(buf12MB))
|
||||||
|
n, err := d.Download(w, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
Key: aws.String("key"),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(len(buf12MB)), n)
|
||||||
|
assert.Equal(t, []string{"GetObject", "GetObject", "GetObject"}, *names)
|
||||||
|
assert.Equal(t, []string{"bytes=0-5242879", "bytes=5242880-10485759", "bytes=10485760-15728639"}, *ranges)
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for _, b := range w.buf {
|
||||||
|
count += int(b)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadZero(t *testing.T) {
|
||||||
|
s, names, ranges := dlLoggingSvc([]byte{})
|
||||||
|
|
||||||
|
opts := &s3manager.DownloadOptions{S3: s}
|
||||||
|
d := s3manager.NewDownloader(opts)
|
||||||
|
w := newDLWriter(0)
|
||||||
|
n, err := d.Download(w, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
Key: aws.String("key"),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(0), n)
|
||||||
|
assert.Equal(t, []string{"GetObject"}, *names)
|
||||||
|
assert.Equal(t, []string{"bytes=0-5242879"}, *ranges)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadSetPartSize(t *testing.T) {
|
||||||
|
s, names, ranges := dlLoggingSvc([]byte{1, 2, 3})
|
||||||
|
|
||||||
|
opts := &s3manager.DownloadOptions{S3: s, PartSize: 1, Concurrency: 1}
|
||||||
|
d := s3manager.NewDownloader(opts)
|
||||||
|
w := newDLWriter(3)
|
||||||
|
n, err := d.Download(w, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
Key: aws.String("key"),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(3), n)
|
||||||
|
assert.Equal(t, []string{"GetObject", "GetObject", "GetObject"}, *names)
|
||||||
|
assert.Equal(t, []string{"bytes=0-0", "bytes=1-1", "bytes=2-2"}, *ranges)
|
||||||
|
assert.Equal(t, []byte{1, 2, 3}, w.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadError(t *testing.T) {
|
||||||
|
s, names, _ := dlLoggingSvc([]byte{1, 2, 3})
|
||||||
|
opts := &s3manager.DownloadOptions{S3: s, PartSize: 1, Concurrency: 1}
|
||||||
|
|
||||||
|
num := 0
|
||||||
|
s.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
num++
|
||||||
|
if num > 1 {
|
||||||
|
r.HTTPResponse.StatusCode = 400
|
||||||
|
r.HTTPResponse.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
d := s3manager.NewDownloader(opts)
|
||||||
|
w := newDLWriter(3)
|
||||||
|
n, err := d.Download(w, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
Key: aws.String("key"),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, int64(1), n)
|
||||||
|
assert.Equal(t, []string{"GetObject", "GetObject"}, *names)
|
||||||
|
assert.Equal(t, []byte{1, 0, 0}, w.buf)
|
||||||
|
}
|
562
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3manager/upload.go
generated
vendored
Normal file
562
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3manager/upload.go
generated
vendored
Normal file
|
@ -0,0 +1,562 @@
|
||||||
|
package s3manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The maximum allowed number of parts in a multi-part upload on Amazon S3.
|
||||||
|
var MaxUploadParts = 10000
|
||||||
|
|
||||||
|
// The minimum allowed part size when uploading a part to Amazon S3.
|
||||||
|
var MinUploadPartSize int64 = 1024 * 1024 * 5
|
||||||
|
|
||||||
|
// The default part size to buffer chunks of a payload into.
|
||||||
|
var DefaultUploadPartSize = MinUploadPartSize
|
||||||
|
|
||||||
|
// The default number of goroutines to spin up when using Upload().
|
||||||
|
var DefaultUploadConcurrency = 5
|
||||||
|
|
||||||
|
// The default set of options used when opts is nil in Upload().
|
||||||
|
var DefaultUploadOptions = &UploadOptions{
|
||||||
|
PartSize: DefaultUploadPartSize,
|
||||||
|
Concurrency: DefaultUploadConcurrency,
|
||||||
|
LeavePartsOnError: false,
|
||||||
|
S3: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A MultiUploadFailure wraps a failed S3 multipart upload. An error returned
|
||||||
|
// will satisfy this interface when a multi part upload failed to upload all
|
||||||
|
// chucks to S3. In the case of a failure the UploadID is needed to operate on
|
||||||
|
// the chunks, if any, which were uploaded.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// u := s3manager.NewUploader(opts)
|
||||||
|
// output, err := u.upload(input)
|
||||||
|
// if err != nil {
|
||||||
|
// if multierr, ok := err.(MultiUploadFailure); ok {
|
||||||
|
// // Process error and its associated uploadID
|
||||||
|
// fmt.Println("Error:", multierr.Code(), multierr.Message(), multierr.UploadID())
|
||||||
|
// } else {
|
||||||
|
// // Process error generically
|
||||||
|
// fmt.Println("Error:", err.Error())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type MultiUploadFailure interface {
|
||||||
|
awserr.Error
|
||||||
|
|
||||||
|
// Returns the upload id for the S3 multipart upload that failed.
|
||||||
|
UploadID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// So that the Error interface type can be included as an anonymous field
|
||||||
|
// in the multiUploadError struct and not conflict with the error.Error() method.
|
||||||
|
type awsError awserr.Error
|
||||||
|
|
||||||
|
// A multiUploadError wraps the upload ID of a failed s3 multipart upload.
|
||||||
|
// Composed of BaseError for code, message, and original error
|
||||||
|
//
|
||||||
|
// Should be used for an error that occurred failing a S3 multipart upload,
|
||||||
|
// and a upload ID is available. If an uploadID is not available a more relevant
|
||||||
|
type multiUploadError struct {
|
||||||
|
awsError
|
||||||
|
|
||||||
|
// ID for multipart upload which failed.
|
||||||
|
uploadID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the string representation of the error.
|
||||||
|
//
|
||||||
|
// See apierr.BaseError ErrorWithExtra for output format
|
||||||
|
//
|
||||||
|
// Satisfies the error interface.
|
||||||
|
func (m multiUploadError) Error() string {
|
||||||
|
extra := fmt.Sprintf("upload id: %s", m.uploadID)
|
||||||
|
return awserr.SprintError(m.Code(), m.Message(), extra, m.OrigErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the error.
|
||||||
|
// Alias for Error to satisfy the stringer interface.
|
||||||
|
func (m multiUploadError) String() string {
|
||||||
|
return m.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadID returns the id of the S3 upload which failed.
|
||||||
|
func (m multiUploadError) UploadID() string {
|
||||||
|
return m.uploadID
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadInput contains all input for upload requests to Amazon S3.
|
||||||
|
type UploadInput struct {
|
||||||
|
// The canned ACL to apply to the object.
|
||||||
|
ACL *string `location:"header" locationName:"x-amz-acl" type:"string"`
|
||||||
|
|
||||||
|
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
|
||||||
|
|
||||||
|
// Specifies caching behavior along the request/reply chain.
|
||||||
|
CacheControl *string `location:"header" locationName:"Cache-Control" type:"string"`
|
||||||
|
|
||||||
|
// Specifies presentational information for the object.
|
||||||
|
ContentDisposition *string `location:"header" locationName:"Content-Disposition" type:"string"`
|
||||||
|
|
||||||
|
// Specifies what content encodings have been applied to the object and thus
|
||||||
|
// what decoding mechanisms must be applied to obtain the media-type referenced
|
||||||
|
// by the Content-Type header field.
|
||||||
|
ContentEncoding *string `location:"header" locationName:"Content-Encoding" type:"string"`
|
||||||
|
|
||||||
|
// The language the content is in.
|
||||||
|
ContentLanguage *string `location:"header" locationName:"Content-Language" type:"string"`
|
||||||
|
|
||||||
|
// A standard MIME type describing the format of the object data.
|
||||||
|
ContentType *string `location:"header" locationName:"Content-Type" type:"string"`
|
||||||
|
|
||||||
|
// The date and time at which the object is no longer cacheable.
|
||||||
|
Expires *time.Time `location:"header" locationName:"Expires" type:"timestamp" timestampFormat:"rfc822"`
|
||||||
|
|
||||||
|
// Gives the grantee READ, READ_ACP, and WRITE_ACP permissions on the object.
|
||||||
|
GrantFullControl *string `location:"header" locationName:"x-amz-grant-full-control" type:"string"`
|
||||||
|
|
||||||
|
// Allows grantee to read the object data and its metadata.
|
||||||
|
GrantRead *string `location:"header" locationName:"x-amz-grant-read" type:"string"`
|
||||||
|
|
||||||
|
// Allows grantee to read the object ACL.
|
||||||
|
GrantReadACP *string `location:"header" locationName:"x-amz-grant-read-acp" type:"string"`
|
||||||
|
|
||||||
|
// Allows grantee to write the ACL for the applicable object.
|
||||||
|
GrantWriteACP *string `location:"header" locationName:"x-amz-grant-write-acp" type:"string"`
|
||||||
|
|
||||||
|
Key *string `location:"uri" locationName:"Key" type:"string" required:"true"`
|
||||||
|
|
||||||
|
// A map of metadata to store with the object in S3.
|
||||||
|
Metadata map[string]*string `location:"headers" locationName:"x-amz-meta-" type:"map"`
|
||||||
|
|
||||||
|
// Confirms that the requester knows that she or he will be charged for the
|
||||||
|
// request. Bucket owners need not specify this parameter in their requests.
|
||||||
|
// Documentation on downloading objects from requester pays buckets can be found
|
||||||
|
// at http://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectsinRequesterPaysBuckets.html
|
||||||
|
RequestPayer *string `location:"header" locationName:"x-amz-request-payer" type:"string"`
|
||||||
|
|
||||||
|
// Specifies the algorithm to use to when encrypting the object (e.g., AES256,
|
||||||
|
// aws:kms).
|
||||||
|
SSECustomerAlgorithm *string `location:"header" locationName:"x-amz-server-side-encryption-customer-algorithm" type:"string"`
|
||||||
|
|
||||||
|
// Specifies the customer-provided encryption key for Amazon S3 to use in encrypting
|
||||||
|
// data. This value is used to store the object and then it is discarded; Amazon
|
||||||
|
// does not store the encryption key. The key must be appropriate for use with
|
||||||
|
// the algorithm specified in the x-amz-server-side-encryption-customer-algorithm
|
||||||
|
// header.
|
||||||
|
SSECustomerKey *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key" type:"string"`
|
||||||
|
|
||||||
|
// Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321.
|
||||||
|
// Amazon S3 uses this header for a message integrity check to ensure the encryption
|
||||||
|
// key was transmitted without error.
|
||||||
|
SSECustomerKeyMD5 *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key-MD5" type:"string"`
|
||||||
|
|
||||||
|
// Specifies the AWS KMS key ID to use for object encryption. All GET and PUT
|
||||||
|
// requests for an object protected by AWS KMS will fail if not made via SSL
|
||||||
|
// or using SigV4. Documentation on configuring any of the officially supported
|
||||||
|
// AWS SDKs and CLI can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingAWSSDK.html#specify-signature-version
|
||||||
|
SSEKMSKeyID *string `location:"header" locationName:"x-amz-server-side-encryption-aws-kms-key-id" type:"string"`
|
||||||
|
|
||||||
|
// The Server-side encryption algorithm used when storing this object in S3
|
||||||
|
// (e.g., AES256, aws:kms).
|
||||||
|
ServerSideEncryption *string `location:"header" locationName:"x-amz-server-side-encryption" type:"string"`
|
||||||
|
|
||||||
|
// The type of storage to use for the object. Defaults to 'STANDARD'.
|
||||||
|
StorageClass *string `location:"header" locationName:"x-amz-storage-class" type:"string"`
|
||||||
|
|
||||||
|
// If the bucket is configured as a website, redirects requests for this object
|
||||||
|
// to another object in the same bucket or to an external URL. Amazon S3 stores
|
||||||
|
// the value of this header in the object metadata.
|
||||||
|
WebsiteRedirectLocation *string `location:"header" locationName:"x-amz-website-redirect-location" type:"string"`
|
||||||
|
|
||||||
|
// The readable body payload to send to S3.
|
||||||
|
Body io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadOutput represents a response from the Upload() call.
|
||||||
|
type UploadOutput struct {
|
||||||
|
// The URL where the object was uploaded to.
|
||||||
|
Location string
|
||||||
|
|
||||||
|
// The ID for a multipart upload to S3. In the case of an error the error
|
||||||
|
// can be cast to the MultiUploadFailure interface to extract the upload ID.
|
||||||
|
UploadID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadOptions keeps tracks of extra options to pass to an Upload() call.
|
||||||
|
type UploadOptions struct {
|
||||||
|
// The buffer size (in bytes) to use when buffering data into chunks and
|
||||||
|
// sending them as parts to S3. The minimum allowed part size is 5MB, and
|
||||||
|
// if this value is set to zero, the DefaultPartSize value will be used.
|
||||||
|
PartSize int64
|
||||||
|
|
||||||
|
// The number of goroutines to spin up in parallel when sending parts.
|
||||||
|
// If this is set to zero, the DefaultConcurrency value will be used.
|
||||||
|
Concurrency int
|
||||||
|
|
||||||
|
// Setting this value to true will cause the SDK to avoid calling
|
||||||
|
// AbortMultipartUpload on a failure, leaving all successfully uploaded
|
||||||
|
// parts on S3 for manual recovery.
|
||||||
|
//
|
||||||
|
// Note that storing parts of an incomplete multipart upload counts towards
|
||||||
|
// space usage on S3 and will add additional costs if not cleaned up.
|
||||||
|
LeavePartsOnError bool
|
||||||
|
|
||||||
|
// The client to use when uploading to S3. Leave this as nil to use the
|
||||||
|
// default S3 client.
|
||||||
|
S3 *s3.S3
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUploader creates a new Uploader object to upload data to S3. Pass in
|
||||||
|
// an optional opts structure to customize the uploader behavior.
|
||||||
|
func NewUploader(opts *UploadOptions) *Uploader {
|
||||||
|
if opts == nil {
|
||||||
|
opts = DefaultUploadOptions
|
||||||
|
}
|
||||||
|
return &Uploader{opts: opts}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Uploader structure that calls Upload(). It is safe to call Upload()
|
||||||
|
// on this structure for multiple objects and across concurrent goroutines.
|
||||||
|
type Uploader struct {
|
||||||
|
opts *UploadOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload uploads an object to S3, intelligently buffering large files into
|
||||||
|
// smaller chunks and sending them in parallel across multiple goroutines. You
|
||||||
|
// can configure the buffer size and concurrency through the opts parameter.
|
||||||
|
//
|
||||||
|
// If opts is set to nil, DefaultUploadOptions will be used.
|
||||||
|
//
|
||||||
|
// It is safe to call this method for multiple objects and across concurrent
|
||||||
|
// goroutines.
|
||||||
|
func (u *Uploader) Upload(input *UploadInput) (*UploadOutput, error) {
|
||||||
|
i := uploader{in: input, opts: *u.opts}
|
||||||
|
return i.upload()
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal structure to manage an upload to S3.
|
||||||
|
type uploader struct {
|
||||||
|
in *UploadInput
|
||||||
|
opts UploadOptions
|
||||||
|
|
||||||
|
readerPos int64 // current reader position
|
||||||
|
totalSize int64 // set to -1 if the size is not known
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal logic for deciding whether to upload a single part or use a
|
||||||
|
// multipart upload.
|
||||||
|
func (u *uploader) upload() (*UploadOutput, error) {
|
||||||
|
u.init()
|
||||||
|
|
||||||
|
if u.opts.PartSize < MinUploadPartSize {
|
||||||
|
msg := fmt.Sprintf("part size must be at least %d bytes", MinUploadPartSize)
|
||||||
|
return nil, awserr.New("ConfigError", msg, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do one read to determine if we have more than one part
|
||||||
|
buf, err := u.nextReader()
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF { // single part
|
||||||
|
return u.singlePart(buf)
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, awserr.New("ReadRequestBody", "read upload data failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mu := multiuploader{uploader: u}
|
||||||
|
return mu.upload(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// init will initialize all default options.
|
||||||
|
func (u *uploader) init() {
|
||||||
|
if u.opts.S3 == nil {
|
||||||
|
u.opts.S3 = s3.New(nil)
|
||||||
|
}
|
||||||
|
if u.opts.Concurrency == 0 {
|
||||||
|
u.opts.Concurrency = DefaultUploadConcurrency
|
||||||
|
}
|
||||||
|
if u.opts.PartSize == 0 {
|
||||||
|
u.opts.PartSize = DefaultUploadPartSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get the total size for some optimizations
|
||||||
|
u.initSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initSize tries to detect the total stream size, setting u.totalSize. If
|
||||||
|
// the size is not known, totalSize is set to -1.
|
||||||
|
func (u *uploader) initSize() {
|
||||||
|
u.totalSize = -1
|
||||||
|
|
||||||
|
switch r := u.in.Body.(type) {
|
||||||
|
case io.Seeker:
|
||||||
|
pos, _ := r.Seek(0, 1)
|
||||||
|
defer r.Seek(pos, 0)
|
||||||
|
|
||||||
|
n, err := r.Seek(0, 2)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.totalSize = n
|
||||||
|
|
||||||
|
// try to adjust partSize if it is too small
|
||||||
|
if u.totalSize/u.opts.PartSize >= int64(MaxUploadParts) {
|
||||||
|
u.opts.PartSize = u.totalSize / int64(MaxUploadParts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextReader returns a seekable reader representing the next packet of data.
|
||||||
|
// This operation increases the shared u.readerPos counter, but note that it
|
||||||
|
// does not need to be wrapped in a mutex because nextReader is only called
|
||||||
|
// from the main thread.
|
||||||
|
func (u *uploader) nextReader() (io.ReadSeeker, error) {
|
||||||
|
switch r := u.in.Body.(type) {
|
||||||
|
case io.ReaderAt:
|
||||||
|
var err error
|
||||||
|
|
||||||
|
n := u.opts.PartSize
|
||||||
|
if u.totalSize >= 0 {
|
||||||
|
bytesLeft := u.totalSize - u.readerPos
|
||||||
|
|
||||||
|
if bytesLeft == 0 {
|
||||||
|
err = io.EOF
|
||||||
|
} else if bytesLeft <= u.opts.PartSize {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
n = bytesLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := io.NewSectionReader(r, u.readerPos, n)
|
||||||
|
u.readerPos += n
|
||||||
|
|
||||||
|
return buf, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
packet := make([]byte, u.opts.PartSize)
|
||||||
|
n, err := io.ReadFull(u.in.Body, packet)
|
||||||
|
u.readerPos += int64(n)
|
||||||
|
|
||||||
|
return bytes.NewReader(packet[0:n]), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// singlePart contains upload logic for uploading a single chunk via
|
||||||
|
// a regular PutObject request. Multipart requests require at least two
|
||||||
|
// parts, or at least 5MB of data.
|
||||||
|
func (u *uploader) singlePart(buf io.ReadSeeker) (*UploadOutput, error) {
|
||||||
|
params := &s3.PutObjectInput{}
|
||||||
|
awsutil.Copy(params, u.in)
|
||||||
|
params.Body = buf
|
||||||
|
|
||||||
|
req, _ := u.opts.S3.PutObjectRequest(params)
|
||||||
|
if err := req.Send(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := req.HTTPRequest.URL.String()
|
||||||
|
return &UploadOutput{Location: url}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal structure to manage a specific multipart upload to S3.
|
||||||
|
type multiuploader struct {
|
||||||
|
*uploader
|
||||||
|
wg sync.WaitGroup
|
||||||
|
m sync.Mutex
|
||||||
|
err error
|
||||||
|
uploadID string
|
||||||
|
parts completedParts
|
||||||
|
}
|
||||||
|
|
||||||
|
// keeps track of a single chunk of data being sent to S3.
|
||||||
|
type chunk struct {
|
||||||
|
buf io.ReadSeeker
|
||||||
|
num int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// completedParts is a wrapper to make parts sortable by their part number,
|
||||||
|
// since S3 required this list to be sent in sorted order.
|
||||||
|
type completedParts []*s3.CompletedPart
|
||||||
|
|
||||||
|
func (a completedParts) Len() int { return len(a) }
|
||||||
|
func (a completedParts) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a completedParts) Less(i, j int) bool { return *a[i].PartNumber < *a[j].PartNumber }
|
||||||
|
|
||||||
|
// upload will perform a multipart upload using the firstBuf buffer containing
|
||||||
|
// the first chunk of data.
|
||||||
|
func (u *multiuploader) upload(firstBuf io.ReadSeeker) (*UploadOutput, error) {
|
||||||
|
params := &s3.CreateMultipartUploadInput{}
|
||||||
|
awsutil.Copy(params, u.in)
|
||||||
|
|
||||||
|
// Create the multipart
|
||||||
|
resp, err := u.opts.S3.CreateMultipartUpload(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.uploadID = *resp.UploadID
|
||||||
|
|
||||||
|
// Create the workers
|
||||||
|
ch := make(chan chunk, u.opts.Concurrency)
|
||||||
|
for i := 0; i < u.opts.Concurrency; i++ {
|
||||||
|
u.wg.Add(1)
|
||||||
|
go u.readChunk(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send part 1 to the workers
|
||||||
|
var num int64 = 1
|
||||||
|
ch <- chunk{buf: firstBuf, num: num}
|
||||||
|
|
||||||
|
// Read and queue the rest of the parts
|
||||||
|
for u.geterr() == nil {
|
||||||
|
// This upload exceeded maximum number of supported parts, error now.
|
||||||
|
if num > int64(MaxUploadParts) {
|
||||||
|
msg := fmt.Sprintf("exceeded total allowed parts (%d). "+
|
||||||
|
"Adjust PartSize to fit in this limit", MaxUploadParts)
|
||||||
|
u.seterr(awserr.New("TotalPartsExceeded", msg, nil))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
num++
|
||||||
|
|
||||||
|
buf, err := u.nextReader()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- chunk{buf: buf, num: num}
|
||||||
|
|
||||||
|
if err != nil && err != io.ErrUnexpectedEOF {
|
||||||
|
u.seterr(awserr.New(
|
||||||
|
"ReadRequestBody",
|
||||||
|
"read multipart upload data failed",
|
||||||
|
err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the channel, wait for workers, and complete upload
|
||||||
|
close(ch)
|
||||||
|
u.wg.Wait()
|
||||||
|
complete := u.complete()
|
||||||
|
|
||||||
|
if err := u.geterr(); err != nil {
|
||||||
|
return nil, &multiUploadError{
|
||||||
|
awsError: awserr.New(
|
||||||
|
"MultipartUpload",
|
||||||
|
"upload multipart failed",
|
||||||
|
err),
|
||||||
|
uploadID: u.uploadID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &UploadOutput{
|
||||||
|
Location: *complete.Location,
|
||||||
|
UploadID: u.uploadID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readChunk runs in worker goroutines to pull chunks off of the ch channel
|
||||||
|
// and send() them as UploadPart requests.
|
||||||
|
func (u *multiuploader) readChunk(ch chan chunk) {
|
||||||
|
defer u.wg.Done()
|
||||||
|
for {
|
||||||
|
data, ok := <-ch
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.geterr() == nil {
|
||||||
|
if err := u.send(data); err != nil {
|
||||||
|
u.seterr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send performs an UploadPart request and keeps track of the completed
|
||||||
|
// part information.
|
||||||
|
func (u *multiuploader) send(c chunk) error {
|
||||||
|
resp, err := u.opts.S3.UploadPart(&s3.UploadPartInput{
|
||||||
|
Bucket: u.in.Bucket,
|
||||||
|
Key: u.in.Key,
|
||||||
|
Body: c.buf,
|
||||||
|
UploadID: &u.uploadID,
|
||||||
|
PartNumber: &c.num,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := c.num
|
||||||
|
completed := &s3.CompletedPart{ETag: resp.ETag, PartNumber: &n}
|
||||||
|
|
||||||
|
u.m.Lock()
|
||||||
|
u.parts = append(u.parts, completed)
|
||||||
|
u.m.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// geterr is a thread-safe getter for the error object
|
||||||
|
func (u *multiuploader) geterr() error {
|
||||||
|
u.m.Lock()
|
||||||
|
defer u.m.Unlock()
|
||||||
|
|
||||||
|
return u.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// seterr is a thread-safe setter for the error object
|
||||||
|
func (u *multiuploader) seterr(e error) {
|
||||||
|
u.m.Lock()
|
||||||
|
defer u.m.Unlock()
|
||||||
|
|
||||||
|
u.err = e
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail will abort the multipart unless LeavePartsOnError is set to true.
|
||||||
|
func (u *multiuploader) fail() {
|
||||||
|
if u.opts.LeavePartsOnError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u.opts.S3.AbortMultipartUpload(&s3.AbortMultipartUploadInput{
|
||||||
|
Bucket: u.in.Bucket,
|
||||||
|
Key: u.in.Key,
|
||||||
|
UploadID: &u.uploadID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete successfully completes a multipart upload and returns the response.
|
||||||
|
func (u *multiuploader) complete() *s3.CompleteMultipartUploadOutput {
|
||||||
|
if u.geterr() != nil {
|
||||||
|
u.fail()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parts must be sorted in PartNumber order.
|
||||||
|
sort.Sort(u.parts)
|
||||||
|
|
||||||
|
resp, err := u.opts.S3.CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
|
||||||
|
Bucket: u.in.Bucket,
|
||||||
|
Key: u.in.Key,
|
||||||
|
UploadID: &u.uploadID,
|
||||||
|
MultipartUpload: &s3.CompletedMultipartUpload{Parts: u.parts},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
u.seterr(err)
|
||||||
|
u.fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
438
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3manager/upload_test.go
generated
vendored
Normal file
438
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/s3manager/upload_test.go
generated
vendored
Normal file
|
@ -0,0 +1,438 @@
|
||||||
|
package s3manager_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = unit.Imported
|
||||||
|
var buf12MB = make([]byte, 1024*1024*12)
|
||||||
|
var buf2MB = make([]byte, 1024*1024*2)
|
||||||
|
|
||||||
|
var emptyList = []string{}
|
||||||
|
|
||||||
|
func val(i interface{}, s string) interface{} {
|
||||||
|
return awsutil.ValuesAtPath(i, s)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(src []string, s string) bool {
|
||||||
|
for _, v := range src {
|
||||||
|
if s == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func loggingSvc(ignoreOps []string) (*s3.S3, *[]string, *[]interface{}) {
|
||||||
|
var m sync.Mutex
|
||||||
|
partNum := 0
|
||||||
|
names := []string{}
|
||||||
|
params := []interface{}{}
|
||||||
|
svc := s3.New(nil)
|
||||||
|
svc.Handlers.Unmarshal.Clear()
|
||||||
|
svc.Handlers.UnmarshalMeta.Clear()
|
||||||
|
svc.Handlers.UnmarshalError.Clear()
|
||||||
|
svc.Handlers.Send.Clear()
|
||||||
|
svc.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
if !contains(ignoreOps, r.Operation.Name) {
|
||||||
|
names = append(names, r.Operation.Name)
|
||||||
|
params = append(params, r.Params)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.HTTPResponse = &http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data := r.Data.(type) {
|
||||||
|
case *s3.CreateMultipartUploadOutput:
|
||||||
|
data.UploadID = aws.String("UPLOAD-ID")
|
||||||
|
case *s3.UploadPartOutput:
|
||||||
|
partNum++
|
||||||
|
data.ETag = aws.String(fmt.Sprintf("ETAG%d", partNum))
|
||||||
|
case *s3.CompleteMultipartUploadOutput:
|
||||||
|
data.Location = aws.String("https://location")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return svc, &names, ¶ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func buflen(i interface{}) int {
|
||||||
|
r := i.(io.Reader)
|
||||||
|
b, _ := ioutil.ReadAll(r)
|
||||||
|
return len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderMulti(t *testing.T) {
|
||||||
|
s, ops, args := loggingSvc(emptyList)
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s})
|
||||||
|
resp, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(buf12MB),
|
||||||
|
ServerSideEncryption: aws.String("AES256"),
|
||||||
|
ContentType: aws.String("content/type"),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "UploadPart", "UploadPart", "UploadPart", "CompleteMultipartUpload"}, *ops)
|
||||||
|
assert.Equal(t, "https://location", resp.Location)
|
||||||
|
assert.Equal(t, "UPLOAD-ID", resp.UploadID)
|
||||||
|
|
||||||
|
// Validate input values
|
||||||
|
|
||||||
|
// UploadPart
|
||||||
|
assert.Equal(t, "UPLOAD-ID", val((*args)[1], "UploadID"))
|
||||||
|
assert.Equal(t, "UPLOAD-ID", val((*args)[2], "UploadID"))
|
||||||
|
assert.Equal(t, "UPLOAD-ID", val((*args)[3], "UploadID"))
|
||||||
|
|
||||||
|
// CompleteMultipartUpload
|
||||||
|
assert.Equal(t, "UPLOAD-ID", val((*args)[4], "UploadID"))
|
||||||
|
assert.Equal(t, int64(1), val((*args)[4], "MultipartUpload.Parts[0].PartNumber"))
|
||||||
|
assert.Equal(t, int64(2), val((*args)[4], "MultipartUpload.Parts[1].PartNumber"))
|
||||||
|
assert.Equal(t, int64(3), val((*args)[4], "MultipartUpload.Parts[2].PartNumber"))
|
||||||
|
assert.Regexp(t, `^ETAG\d+$`, val((*args)[4], "MultipartUpload.Parts[0].ETag"))
|
||||||
|
assert.Regexp(t, `^ETAG\d+$`, val((*args)[4], "MultipartUpload.Parts[1].ETag"))
|
||||||
|
assert.Regexp(t, `^ETAG\d+$`, val((*args)[4], "MultipartUpload.Parts[2].ETag"))
|
||||||
|
|
||||||
|
// Custom headers
|
||||||
|
assert.Equal(t, "AES256", val((*args)[0], "ServerSideEncryption"))
|
||||||
|
assert.Equal(t, "content/type", val((*args)[0], "ContentType"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderMultiDifferentPartSize(t *testing.T) {
|
||||||
|
s, ops, args := loggingSvc(emptyList)
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{
|
||||||
|
S3: s,
|
||||||
|
PartSize: 1024 * 1024 * 7,
|
||||||
|
Concurrency: 1,
|
||||||
|
})
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(buf12MB),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "UploadPart", "UploadPart", "CompleteMultipartUpload"}, *ops)
|
||||||
|
|
||||||
|
// Part lengths
|
||||||
|
assert.Equal(t, 1024*1024*7, buflen(val((*args)[1], "Body")))
|
||||||
|
assert.Equal(t, 1024*1024*5, buflen(val((*args)[2], "Body")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadIncreasePartSize(t *testing.T) {
|
||||||
|
s3manager.MaxUploadParts = 2
|
||||||
|
defer func() { s3manager.MaxUploadParts = 10000 }()
|
||||||
|
|
||||||
|
s, ops, args := loggingSvc(emptyList)
|
||||||
|
opts := &s3manager.UploadOptions{S3: s, Concurrency: 1}
|
||||||
|
mgr := s3manager.NewUploader(opts)
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(buf12MB),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(0), opts.PartSize) // don't modify orig options
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "UploadPart", "UploadPart", "CompleteMultipartUpload"}, *ops)
|
||||||
|
|
||||||
|
// Part lengths
|
||||||
|
assert.Equal(t, 1024*1024*6, buflen(val((*args)[1], "Body")))
|
||||||
|
assert.Equal(t, 1024*1024*6, buflen(val((*args)[2], "Body")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadFailIfPartSizeTooSmall(t *testing.T) {
|
||||||
|
opts := &s3manager.UploadOptions{PartSize: 5}
|
||||||
|
mgr := s3manager.NewUploader(opts)
|
||||||
|
resp, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(buf12MB),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Nil(t, resp)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
aerr := err.(awserr.Error)
|
||||||
|
assert.Equal(t, "ConfigError", aerr.Code())
|
||||||
|
assert.Contains(t, aerr.Message(), "part size must be at least")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderSingle(t *testing.T) {
|
||||||
|
s, ops, args := loggingSvc(emptyList)
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s})
|
||||||
|
resp, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(buf2MB),
|
||||||
|
ServerSideEncryption: aws.String("AES256"),
|
||||||
|
ContentType: aws.String("content/type"),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"PutObject"}, *ops)
|
||||||
|
assert.NotEqual(t, "", resp.Location)
|
||||||
|
assert.Equal(t, "", resp.UploadID)
|
||||||
|
assert.Equal(t, "AES256", val((*args)[0], "ServerSideEncryption"))
|
||||||
|
assert.Equal(t, "content/type", val((*args)[0], "ContentType"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderSingleFailure(t *testing.T) {
|
||||||
|
s, ops, _ := loggingSvc(emptyList)
|
||||||
|
s.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
r.HTTPResponse.StatusCode = 400
|
||||||
|
})
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s})
|
||||||
|
resp, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(buf2MB),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, []string{"PutObject"}, *ops)
|
||||||
|
assert.Nil(t, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderZero(t *testing.T) {
|
||||||
|
s, ops, args := loggingSvc(emptyList)
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s})
|
||||||
|
resp, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(make([]byte, 0)),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"PutObject"}, *ops)
|
||||||
|
assert.NotEqual(t, "", resp.Location)
|
||||||
|
assert.Equal(t, "", resp.UploadID)
|
||||||
|
assert.Equal(t, 0, buflen(val((*args)[0], "Body")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderMultiFailure(t *testing.T) {
|
||||||
|
s, ops, _ := loggingSvc(emptyList)
|
||||||
|
s.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
switch t := r.Data.(type) {
|
||||||
|
case *s3.UploadPartOutput:
|
||||||
|
if *t.ETag == "ETAG2" {
|
||||||
|
r.HTTPResponse.StatusCode = 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s, Concurrency: 1})
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(buf12MB),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "UploadPart", "UploadPart", "AbortMultipartUpload"}, *ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderMultiFailureOnComplete(t *testing.T) {
|
||||||
|
s, ops, _ := loggingSvc(emptyList)
|
||||||
|
s.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
switch r.Data.(type) {
|
||||||
|
case *s3.CompleteMultipartUploadOutput:
|
||||||
|
r.HTTPResponse.StatusCode = 400
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s, Concurrency: 1})
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(buf12MB),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "UploadPart", "UploadPart",
|
||||||
|
"UploadPart", "CompleteMultipartUpload", "AbortMultipartUpload"}, *ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderMultiFailureOnCreate(t *testing.T) {
|
||||||
|
s, ops, _ := loggingSvc(emptyList)
|
||||||
|
s.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
switch r.Data.(type) {
|
||||||
|
case *s3.CreateMultipartUploadOutput:
|
||||||
|
r.HTTPResponse.StatusCode = 400
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s})
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(make([]byte, 1024*1024*12)),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload"}, *ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderMultiFailureLeaveParts(t *testing.T) {
|
||||||
|
s, ops, _ := loggingSvc(emptyList)
|
||||||
|
s.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
switch data := r.Data.(type) {
|
||||||
|
case *s3.UploadPartOutput:
|
||||||
|
if *data.ETag == "ETAG2" {
|
||||||
|
r.HTTPResponse.StatusCode = 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{
|
||||||
|
S3: s,
|
||||||
|
Concurrency: 1,
|
||||||
|
LeavePartsOnError: true,
|
||||||
|
})
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: bytes.NewReader(make([]byte, 1024*1024*12)),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "UploadPart", "UploadPart"}, *ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
type failreader struct {
|
||||||
|
times int
|
||||||
|
failCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *failreader) Read(b []byte) (int, error) {
|
||||||
|
f.failCount++
|
||||||
|
if f.failCount >= f.times {
|
||||||
|
return 0, fmt.Errorf("random failure")
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderReadFail1(t *testing.T) {
|
||||||
|
s, ops, _ := loggingSvc(emptyList)
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s})
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: &failreader{times: 1},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, "ReadRequestBody", err.(awserr.Error).Code())
|
||||||
|
assert.EqualError(t, err.(awserr.Error).OrigErr(), "random failure")
|
||||||
|
assert.Equal(t, []string{}, *ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderReadFail2(t *testing.T) {
|
||||||
|
s, ops, _ := loggingSvc([]string{"UploadPart"})
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s, Concurrency: 1})
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: &failreader{times: 2},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, "ReadRequestBody", err.(awserr.Error).Code())
|
||||||
|
assert.EqualError(t, err.(awserr.Error).OrigErr(), "random failure")
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "AbortMultipartUpload"}, *ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sizedReader struct {
|
||||||
|
size int
|
||||||
|
cur int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sizedReader) Read(p []byte) (n int, err error) {
|
||||||
|
if s.cur >= s.size {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
n = len(p)
|
||||||
|
s.cur += len(p)
|
||||||
|
if s.cur > s.size {
|
||||||
|
n -= s.cur - s.size
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderMultiBufferedReader(t *testing.T) {
|
||||||
|
s, ops, args := loggingSvc(emptyList)
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s})
|
||||||
|
_, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: &sizedReader{size: 1024 * 1024 * 12},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "UploadPart", "UploadPart", "UploadPart", "CompleteMultipartUpload"}, *ops)
|
||||||
|
|
||||||
|
// Part lengths
|
||||||
|
parts := []int{
|
||||||
|
buflen(val((*args)[1], "Body")),
|
||||||
|
buflen(val((*args)[2], "Body")),
|
||||||
|
buflen(val((*args)[3], "Body")),
|
||||||
|
}
|
||||||
|
sort.Ints(parts)
|
||||||
|
assert.Equal(t, []int{1024 * 1024 * 2, 1024 * 1024 * 5, 1024 * 1024 * 5}, parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderMultiBufferedReaderExceedTotalParts(t *testing.T) {
|
||||||
|
s3manager.MaxUploadParts = 2
|
||||||
|
defer func() { s3manager.MaxUploadParts = 10000 }()
|
||||||
|
s, ops, _ := loggingSvc([]string{"UploadPart"})
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s, Concurrency: 1})
|
||||||
|
resp, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: &sizedReader{size: 1024 * 1024 * 12},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, resp)
|
||||||
|
assert.Equal(t, []string{"CreateMultipartUpload", "AbortMultipartUpload"}, *ops)
|
||||||
|
|
||||||
|
aerr := err.(awserr.Error)
|
||||||
|
assert.Equal(t, "TotalPartsExceeded", aerr.Code())
|
||||||
|
assert.Contains(t, aerr.Message(), "exceeded total allowed parts (2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadOrderSingleBufferedReader(t *testing.T) {
|
||||||
|
s, ops, _ := loggingSvc(emptyList)
|
||||||
|
mgr := s3manager.NewUploader(&s3manager.UploadOptions{S3: s})
|
||||||
|
resp, err := mgr.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String("Bucket"),
|
||||||
|
Key: aws.String("Key"),
|
||||||
|
Body: &sizedReader{size: 1024 * 1024 * 2},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"PutObject"}, *ops)
|
||||||
|
assert.NotEqual(t, "", resp.Location)
|
||||||
|
assert.Equal(t, "", resp.UploadID)
|
||||||
|
}
|
57
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/service.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/service.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||||
|
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/protocol/restxml"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/signer/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// S3 is a client for Amazon S3.
|
||||||
|
type S3 struct {
|
||||||
|
*aws.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for custom service initialization logic
|
||||||
|
var initService func(*aws.Service)
|
||||||
|
|
||||||
|
// Used for custom request initialization logic
|
||||||
|
var initRequest func(*aws.Request)
|
||||||
|
|
||||||
|
// New returns a new S3 client.
|
||||||
|
func New(config *aws.Config) *S3 {
|
||||||
|
service := &aws.Service{
|
||||||
|
Config: aws.DefaultConfig.Merge(config),
|
||||||
|
ServiceName: "s3",
|
||||||
|
APIVersion: "2006-03-01",
|
||||||
|
}
|
||||||
|
service.Initialize()
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
service.Handlers.Sign.PushBack(v4.Sign)
|
||||||
|
service.Handlers.Build.PushBack(restxml.Build)
|
||||||
|
service.Handlers.Unmarshal.PushBack(restxml.Unmarshal)
|
||||||
|
service.Handlers.UnmarshalMeta.PushBack(restxml.UnmarshalMeta)
|
||||||
|
service.Handlers.UnmarshalError.PushBack(restxml.UnmarshalError)
|
||||||
|
|
||||||
|
// Run custom service initialization if present
|
||||||
|
if initService != nil {
|
||||||
|
initService(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &S3{service}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRequest creates a new request for a S3 operation and runs any
|
||||||
|
// custom request initialization.
|
||||||
|
func (c *S3) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
|
||||||
|
req := aws.NewRequest(c.Service, op, params, data)
|
||||||
|
|
||||||
|
// Run custom request initialization if present
|
||||||
|
if initRequest != nil {
|
||||||
|
initRequest(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
44
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/sse.go
generated
vendored
Normal file
44
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/sse.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errSSERequiresSSL = awserr.New("ConfigError", "cannot send SSE keys over HTTP.", nil)
|
||||||
|
|
||||||
|
func validateSSERequiresSSL(r *aws.Request) {
|
||||||
|
if r.HTTPRequest.URL.Scheme != "https" {
|
||||||
|
p := awsutil.ValuesAtPath(r.Params, "SSECustomerKey||CopySourceSSECustomerKey")
|
||||||
|
if len(p) > 0 {
|
||||||
|
r.Error = errSSERequiresSSL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeSSEKeys(r *aws.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/sse_test.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/sse_test.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package s3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = unit.Imported
|
||||||
|
|
||||||
|
func TestSSECustomerKeyOverHTTPError(t *testing.T) {
|
||||||
|
s := s3.New(&aws.Config{DisableSSL: true})
|
||||||
|
req, _ := s.CopyObjectRequest(&s3.CopyObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
CopySource: aws.String("bucket/source"),
|
||||||
|
Key: aws.String("dest"),
|
||||||
|
SSECustomerKey: aws.String("key"),
|
||||||
|
})
|
||||||
|
err := req.Build()
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "ConfigError", err.(awserr.Error).Code())
|
||||||
|
assert.Contains(t, err.(awserr.Error).Message(), "cannot send SSE keys over HTTP")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopySourceSSECustomerKeyOverHTTPError(t *testing.T) {
|
||||||
|
s := s3.New(&aws.Config{DisableSSL: true})
|
||||||
|
req, _ := s.CopyObjectRequest(&s3.CopyObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
CopySource: aws.String("bucket/source"),
|
||||||
|
Key: aws.String("dest"),
|
||||||
|
CopySourceSSECustomerKey: aws.String("key"),
|
||||||
|
})
|
||||||
|
err := req.Build()
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "ConfigError", err.(awserr.Error).Code())
|
||||||
|
assert.Contains(t, err.(awserr.Error).Message(), "cannot send SSE keys over HTTP")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeSSEKeys(t *testing.T) {
|
||||||
|
s := s3.New(nil)
|
||||||
|
req, _ := s.CopyObjectRequest(&s3.CopyObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
CopySource: aws.String("bucket/source"),
|
||||||
|
Key: aws.String("dest"),
|
||||||
|
SSECustomerKey: aws.String("key"),
|
||||||
|
CopySourceSSECustomerKey: aws.String("key"),
|
||||||
|
})
|
||||||
|
err := req.Build()
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "a2V5", req.HTTPRequest.Header.Get("x-amz-server-side-encryption-customer-key"))
|
||||||
|
assert.Equal(t, "a2V5", req.HTTPRequest.Header.Get("x-amz-copy-source-server-side-encryption-customer-key"))
|
||||||
|
assert.Equal(t, "PG4LipwVIkqCKLmpjKFTHQ==", req.HTTPRequest.Header.Get("x-amz-server-side-encryption-customer-key-md5"))
|
||||||
|
assert.Equal(t, "PG4LipwVIkqCKLmpjKFTHQ==", req.HTTPRequest.Header.Get("x-amz-copy-source-server-side-encryption-customer-key-md5"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeSSEKeysShortcircuit(t *testing.T) {
|
||||||
|
s := s3.New(nil)
|
||||||
|
req, _ := s.CopyObjectRequest(&s3.CopyObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
CopySource: aws.String("bucket/source"),
|
||||||
|
Key: aws.String("dest"),
|
||||||
|
SSECustomerKey: aws.String("key"),
|
||||||
|
CopySourceSSECustomerKey: aws.String("key"),
|
||||||
|
SSECustomerKeyMD5: aws.String("MD5"),
|
||||||
|
CopySourceSSECustomerKeyMD5: aws.String("MD5"),
|
||||||
|
})
|
||||||
|
err := req.Build()
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "a2V5", req.HTTPRequest.Header.Get("x-amz-server-side-encryption-customer-key"))
|
||||||
|
assert.Equal(t, "a2V5", req.HTTPRequest.Header.Get("x-amz-copy-source-server-side-encryption-customer-key"))
|
||||||
|
assert.Equal(t, "MD5", req.HTTPRequest.Header.Get("x-amz-server-side-encryption-customer-key-md5"))
|
||||||
|
assert.Equal(t, "MD5", req.HTTPRequest.Header.Get("x-amz-copy-source-server-side-encryption-customer-key-md5"))
|
||||||
|
}
|
42
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/unmarshal_error.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/unmarshal_error.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xmlErrorResponse struct {
|
||||||
|
XMLName xml.Name `xml:"Error"`
|
||||||
|
Code string `xml:"Code"`
|
||||||
|
Message string `xml:"Message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalError(r *aws.Request) {
|
||||||
|
defer r.HTTPResponse.Body.Close()
|
||||||
|
|
||||||
|
if r.HTTPResponse.ContentLength == int64(0) {
|
||||||
|
// No body, use status code to generate an awserr.Error
|
||||||
|
r.Error = awserr.NewRequestFailure(
|
||||||
|
awserr.New(strings.Replace(r.HTTPResponse.Status, " ", "", -1), r.HTTPResponse.Status, nil),
|
||||||
|
r.HTTPResponse.StatusCode,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &xmlErrorResponse{}
|
||||||
|
err := xml.NewDecoder(r.HTTPResponse.Body).Decode(resp)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
r.Error = awserr.New("SerializationError", "failed to decode S3 XML error response", nil)
|
||||||
|
} else {
|
||||||
|
r.Error = awserr.NewRequestFailure(
|
||||||
|
awserr.New(resp.Code, resp.Message, nil),
|
||||||
|
r.HTTPResponse.StatusCode,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
53
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/unmarshal_error_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/s3/unmarshal_error_test.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package s3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/test/unit"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = unit.Imported
|
||||||
|
|
||||||
|
var s3StatusCodeErrorTests = []struct {
|
||||||
|
scode int
|
||||||
|
status string
|
||||||
|
body string
|
||||||
|
code string
|
||||||
|
message string
|
||||||
|
}{
|
||||||
|
{301, "Moved Permanently", "", "MovedPermanently", "Moved Permanently"},
|
||||||
|
{403, "Forbidden", "", "Forbidden", "Forbidden"},
|
||||||
|
{400, "Bad Request", "", "BadRequest", "Bad Request"},
|
||||||
|
{404, "Not Found", "", "NotFound", "Not Found"},
|
||||||
|
{500, "Internal Error", "", "InternalError", "Internal Error"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusCodeError(t *testing.T) {
|
||||||
|
for _, test := range s3StatusCodeErrorTests {
|
||||||
|
s := s3.New(nil)
|
||||||
|
s.Handlers.Send.Clear()
|
||||||
|
s.Handlers.Send.PushBack(func(r *aws.Request) {
|
||||||
|
body := ioutil.NopCloser(bytes.NewReader([]byte(test.body)))
|
||||||
|
r.HTTPResponse = &http.Response{
|
||||||
|
ContentLength: int64(len(test.body)),
|
||||||
|
StatusCode: test.scode,
|
||||||
|
Status: test.status,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
_, err := s.PutBucketACL(&s3.PutBucketACLInput{
|
||||||
|
Bucket: aws.String("bucket"), ACL: aws.String("public-read"),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, test.code, err.(awserr.Error).Code())
|
||||||
|
assert.Equal(t, test.message, err.(awserr.Error).Message())
|
||||||
|
}
|
||||||
|
}
|
27
Godeps/_workspace/src/github.com/extemporalgenome/slug/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/extemporalgenome/slug/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
9
Godeps/_workspace/src/github.com/extemporalgenome/slug/README.md
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/extemporalgenome/slug/README.md
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# slug
|
||||||
|
|
||||||
|
See the [API docs](http://go.pkgdoc.org/github.com/extemporalgenome/slug).
|
||||||
|
|
||||||
|
Latin-ish inputs should have very stable output. All inputs are passed through
|
||||||
|
an NFKD transform, and anything still in the unicode Letter and Number
|
||||||
|
categories are passed through intact. Anything in the Mark or Lm/Sk categories
|
||||||
|
(modifiers) are skipped, and runs of characters from any other categories are
|
||||||
|
collapsed to a single hyphen.
|
110
Godeps/_workspace/src/github.com/extemporalgenome/slug/slug.go
generated
vendored
Normal file
110
Godeps/_workspace/src/github.com/extemporalgenome/slug/slug.go
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package slug transforms strings into a normalized form well suited for use in URLs.
|
||||||
|
package slug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.google.com/p/go.text/unicode/norm"
|
||||||
|
"encoding/hex"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var lat = []*unicode.RangeTable{unicode.Letter, unicode.Number}
|
||||||
|
var nop = []*unicode.RangeTable{unicode.Mark, unicode.Sk, unicode.Lm}
|
||||||
|
|
||||||
|
// Slug replaces each run of characters which are not unicode letters or
|
||||||
|
// numbers with a single hyphen, except for leading or trailing runs. Letters
|
||||||
|
// will be stripped of diacritical marks and lowercased. Letter or number
|
||||||
|
// codepoints that do not have combining marks or a lower-cased variant will
|
||||||
|
// be passed through unaltered.
|
||||||
|
func Slug(s string) string {
|
||||||
|
buf := make([]rune, 0, len(s))
|
||||||
|
dash := false
|
||||||
|
for _, r := range norm.NFKD.String(s) {
|
||||||
|
switch {
|
||||||
|
// unicode 'letters' like mandarin characters pass through
|
||||||
|
case unicode.IsOneOf(lat, r):
|
||||||
|
buf = append(buf, unicode.ToLower(r))
|
||||||
|
dash = true
|
||||||
|
case unicode.IsOneOf(nop, r):
|
||||||
|
// skip
|
||||||
|
case dash:
|
||||||
|
buf = append(buf, '-')
|
||||||
|
dash = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i := len(buf) - 1; i >= 0 && buf[i] == '-' {
|
||||||
|
buf = buf[:i]
|
||||||
|
}
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SlugAscii is identical to Slug, except that runs of one or more unicode
|
||||||
|
// letters or numbers that still fall outside the ASCII range will have their
|
||||||
|
// UTF-8 representation hex encoded and delimited by hyphens. As with Slug, in
|
||||||
|
// no case will hyphens appear at either end of the returned string.
|
||||||
|
func SlugAscii(s string) string {
|
||||||
|
const m = utf8.UTFMax
|
||||||
|
var (
|
||||||
|
ib [m * 3]byte
|
||||||
|
ob []byte
|
||||||
|
buf = make([]byte, 0, len(s))
|
||||||
|
dash = false
|
||||||
|
latin = true
|
||||||
|
)
|
||||||
|
for _, r := range norm.NFKD.String(s) {
|
||||||
|
switch {
|
||||||
|
case unicode.IsOneOf(lat, r):
|
||||||
|
r = unicode.ToLower(r)
|
||||||
|
n := utf8.EncodeRune(ib[:m], r)
|
||||||
|
if r >= 128 {
|
||||||
|
if latin && dash {
|
||||||
|
buf = append(buf, '-')
|
||||||
|
}
|
||||||
|
n = hex.Encode(ib[m:], ib[:n])
|
||||||
|
ob = ib[m : m+n]
|
||||||
|
latin = false
|
||||||
|
} else {
|
||||||
|
if !latin {
|
||||||
|
buf = append(buf, '-')
|
||||||
|
}
|
||||||
|
ob = ib[:n]
|
||||||
|
latin = true
|
||||||
|
}
|
||||||
|
dash = true
|
||||||
|
buf = append(buf, ob...)
|
||||||
|
case unicode.IsOneOf(nop, r):
|
||||||
|
// skip
|
||||||
|
case dash:
|
||||||
|
buf = append(buf, '-')
|
||||||
|
dash = false
|
||||||
|
latin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i := len(buf) - 1; i >= 0 && buf[i] == '-' {
|
||||||
|
buf = buf[:i]
|
||||||
|
}
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSlugAscii returns true only if SlugAscii(s) == s.
|
||||||
|
func IsSlugAscii(s string) bool {
|
||||||
|
dash := true
|
||||||
|
for _, r := range s {
|
||||||
|
switch {
|
||||||
|
case r == '-':
|
||||||
|
if dash {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
dash = true
|
||||||
|
case 'a' <= r && r <= 'z', '0' <= r && r <= '9':
|
||||||
|
dash = false
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !dash
|
||||||
|
}
|
6
Godeps/_workspace/src/github.com/flosch/go-humanize/.gitignore
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/flosch/go-humanize/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#*
|
||||||
|
*.[568]
|
||||||
|
*.a
|
||||||
|
*~
|
||||||
|
[568].out
|
||||||
|
_*
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue