Browse Source

use uritemplates

Use uritemplates instead of cleanPathString to correctly encode the
URL path.

We follow the Google Go API here. See
https://code.google.com/p/google-api-go-client/ and especially
the subdirectory
https://code.google.com/p/google-api-go-client/source/browse/#hg%2Fgoogleapi%2Finternal%2Furitemplates
tags/v1
Oliver Eilhard 5 years ago
parent
commit
ceb70f82e9
22 changed files with 713 additions and 65 deletions
  1. 11
    1
      aliases.go
  2. 16
    2
      bulk.go
  3. 18
    2
      count.go
  4. 33
    3
      create_index.go
  5. 10
    5
      delete.go
  6. 8
    3
      delete_index.go
  7. 10
    5
      exists.go
  8. 9
    1
      flush.go
  9. 10
    4
      get.go
  10. 10
    6
      index.go
  11. 8
    3
      index_exists.go
  12. 9
    1
      optimize.go
  13. 9
    1
      refresh.go
  14. 16
    2
      scan.go
  15. 16
    2
      scroll.go
  16. 16
    2
      search.go
  17. 9
    1
      suggest.go
  18. 18
    0
      uritemplates/LICENSE
  19. 359
    0
      uritemplates/uritemplates.go
  20. 13
    0
      uritemplates/utils.go
  21. 105
    0
      uritemplates/utils_test.go
  22. 0
    21
      utils.go

+ 11
- 1
aliases.go View File

@@ -12,6 +12,8 @@ import (
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type AliasesService struct {
@@ -50,13 +52,21 @@ func (s *AliasesService) Indices(indexNames ...string) *AliasesService {
}

func (s *AliasesService) Do() (*AliasesResult, error) {
var err error

// Build url
urls := "/"

// Indices part
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err = uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return nil, err
}
indexPart = append(indexPart, index)
}
urls += strings.Join(indexPart, ",")


+ 16
- 2
bulk.go View File

@@ -12,6 +12,8 @@ import (
"net/http"
"net/http/httputil"
"net/url"

"github.com/olivere/elastic/uritemplates"
)

type BulkService struct {
@@ -122,10 +124,22 @@ func (s *BulkService) Do() (*BulkResponse, error) {
// Build url
urls := "/"
if s.index != "" {
urls += cleanPathString(s.index) + "/"
index, err := uritemplates.Expand("{index}", map[string]string{
"index": s.index,
})
if err != nil {
return nil, err
}
urls += index + "/"
}
if s._type != "" {
urls += cleanPathString(s._type) + "/"
typ, err := uritemplates.Expand("{type}", map[string]string{
"type": s._type,
})
if err != nil {
return nil, err
}
urls += typ + "/"
}
urls += "_bulk"


+ 18
- 2
count.go View File

@@ -11,6 +11,8 @@ import (
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

// CountService is a convenient service for determining the
@@ -89,13 +91,21 @@ func (s *CountService) Debug(debug bool) *CountService {
}

func (s *CountService) Do() (int64, error) {
var err error

// Build url
urls := "/"

// Indices part
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err = uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return 0, err
}
indexPart = append(indexPart, index)
}
if len(indexPart) > 0 {
urls += strings.Join(indexPart, ",")
@@ -104,7 +114,13 @@ func (s *CountService) Do() (int64, error) {
// Types part
typesPart := make([]string, 0)
for _, typ := range s.types {
typesPart = append(typesPart, cleanPathString(typ))
typ, err = uritemplates.Expand("{type}", map[string]string{
"type": typ,
})
if err != nil {
return 0, err
}
typesPart = append(typesPart, typ)
}
if len(typesPart) > 0 {
urls += "/" + strings.Join(typesPart, ",")

+ 33
- 3
create_index.go View File

@@ -6,14 +6,19 @@ package elastic

import (
"encoding/json"
"log"
"net/http"
"strings"
"net/http/httputil"

"github.com/olivere/elastic/uritemplates"
)

type CreateIndexService struct {
client *Client
index string
body string
pretty bool
debug bool
}

func NewCreateIndexService(client *Client) *CreateIndexService {
@@ -33,10 +38,24 @@ func (b *CreateIndexService) Body(body string) *CreateIndexService {
return b
}

func (b *CreateIndexService) Pretty(pretty bool) *CreateIndexService {
b.pretty = pretty
return b
}

func (b *CreateIndexService) Debug(debug bool) *CreateIndexService {
b.debug = debug
return b
}

func (b *CreateIndexService) Do() (*CreateIndexResult, error) {
// Build url
urls := "/{index}/"
urls = strings.Replace(urls, "{index}", cleanPathString(b.index), 1)
urls, err := uritemplates.Expand("/{index}/", map[string]string{
"index": b.index,
})
if err != nil {
return nil, err
}

// Set up a new request
req, err := b.client.NewRequest("PUT", urls)
@@ -47,6 +66,11 @@ func (b *CreateIndexService) Do() (*CreateIndexResult, error) {
// Set body
req.SetBodyString(b.body)

if b.debug {
out, _ := httputil.DumpRequestOut((*http.Request)(req), true)
log.Printf("%s\n", string(out))
}

// Get response
res, err := b.client.c.Do((*http.Request)(req))
if err != nil {
@@ -56,6 +80,12 @@ func (b *CreateIndexService) Do() (*CreateIndexResult, error) {
return nil, err
}
defer res.Body.Close()

if b.debug {
out, _ := httputil.DumpResponse(res, true)
log.Printf("%s\n", string(out))
}

ret := new(CreateIndexResult)
if err := json.NewDecoder(res.Body).Decode(ret); err != nil {
return nil, err

+ 10
- 5
delete.go View File

@@ -10,7 +10,8 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type DeleteService struct {
@@ -76,10 +77,14 @@ func (s *DeleteService) Debug(debug bool) *DeleteService {

func (s *DeleteService) Do() (*DeleteResult, error) {
// Build url
urls := "/{index}/{type}/{id}"
urls = strings.Replace(urls, "{index}", cleanPathString(s.index), 1)
urls = strings.Replace(urls, "{type}", cleanPathString(s._type), 1)
urls = strings.Replace(urls, "{id}", cleanPathString(s.id), 1)
urls, err := uritemplates.Expand("/{index}/{type}/{id}", map[string]string{
"index": s.index,
"type": s._type,
"id": s.id,
})
if err != nil {
return nil, err
}

// Parameters
params := make(url.Values)

+ 8
- 3
delete_index.go View File

@@ -7,7 +7,8 @@ package elastic
import (
"encoding/json"
"net/http"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type DeleteIndexService struct {
@@ -29,8 +30,12 @@ func (b *DeleteIndexService) Index(index string) *DeleteIndexService {

func (b *DeleteIndexService) Do() (*DeleteIndexResult, error) {
// Build url
urls := "/{index}/"
urls = strings.Replace(urls, "{index}", cleanPathString(b.index), 1)
urls, err := uritemplates.Expand("/{index}/", map[string]string{
"index": b.index,
})
if err != nil {
return nil, err
}

// Set up a new request
req, err := b.client.NewRequest("DELETE", urls)

+ 10
- 5
exists.go View File

@@ -7,7 +7,8 @@ package elastic
import (
"fmt"
"net/http"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type ExistsService struct {
@@ -48,10 +49,14 @@ func (s *ExistsService) Id(id string) *ExistsService {

func (s *ExistsService) Do() (bool, error) {
// Build url
urls := "/{index}/{type}/{id}"
urls = strings.Replace(urls, "{index}", cleanPathString(s.index), 1)
urls = strings.Replace(urls, "{type}", cleanPathString(s._type), 1)
urls = strings.Replace(urls, "{id}", cleanPathString(s.id), 1)
urls, err := uritemplates.Expand("/{index}/{type}/{id}", map[string]string{
"index": s.index,
"type": s._type,
"id": s.id,
})
if err != nil {
return false, err
}

// Set up a new request
req, err := s.client.NewRequest("HEAD", urls)

+ 9
- 1
flush.go View File

@@ -10,6 +10,8 @@ import (
"net/http"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type FlushService struct {
@@ -61,7 +63,13 @@ func (s *FlushService) Do() (*FlushResult, error) {
if len(s.indices) > 0 {
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err := uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return nil, err
}
indexPart = append(indexPart, index)
}
urls += strings.Join(indexPart, ",") + "/"
}

+ 10
- 4
get.go View File

@@ -10,6 +10,8 @@ import (
"net/http"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type GetService struct {
@@ -92,10 +94,14 @@ func (b *GetService) Realtime(realtime bool) *GetService {

func (b *GetService) Do() (*GetResult, error) {
// Build url
urls := "/{index}/{type}/{id}"
urls = strings.Replace(urls, "{index}", cleanPathString(b.index), 1)
urls = strings.Replace(urls, "{type}", cleanPathString(b._type), 1)
urls = strings.Replace(urls, "{id}", cleanPathString(b.id), 1)
urls, err := uritemplates.Expand("/{index}/{type}/{id}", map[string]string{
"index": b.index,
"type": b._type,
"id": b.id,
})
if err != nil {
return nil, err
}

params := make(url.Values)
if b.realtime != nil {

+ 10
- 6
index.go View File

@@ -11,7 +11,8 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

// IndexResult is the result of indexing a document in Elasticsearch.
@@ -143,16 +144,19 @@ func (b *IndexService) Do() (*IndexResult, error) {
// Create document with manual id
method = "PUT"
urls = "/{index}/{type}/{id}"
urls = strings.Replace(urls, "{index}", cleanPathString(b.index), 1)
urls = strings.Replace(urls, "{type}", cleanPathString(b._type), 1)
urls = strings.Replace(urls, "{id}", cleanPathString(b.id), 1)
} else {
// Automatic ID generation
// See: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-index_.html#index-creation
method = "POST"
urls = "/{index}/{type}/"
urls = strings.Replace(urls, "{index}", cleanPathString(b.index), 1)
urls = strings.Replace(urls, "{type}", cleanPathString(b._type), 1)
}
urls, err := uritemplates.Expand(urls, map[string]string{
"index": b.index,
"type": b._type,
"id": b.id,
})
if err != nil {
return nil, err
}

// Parameters

+ 8
- 3
index_exists.go View File

@@ -7,7 +7,8 @@ package elastic
import (
"fmt"
"net/http"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type IndexExistsService struct {
@@ -29,8 +30,12 @@ func (b *IndexExistsService) Index(index string) *IndexExistsService {

func (b *IndexExistsService) Do() (bool, error) {
// Build url
urls := "/{index}"
urls = strings.Replace(urls, "{index}", cleanPathString(b.index), 1)
urls, err := uritemplates.Expand("/{index}", map[string]string{
"index": b.index,
})
if err != nil {
return false, err
}

// Set up a new request
req, err := b.client.NewRequest("HEAD", urls)

+ 9
- 1
optimize.go View File

@@ -11,6 +11,8 @@ import (
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type OptimizeService struct {
@@ -85,7 +87,13 @@ func (s *OptimizeService) Do() (*OptimizeResult, error) {
// Indices part
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err := uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return nil, err
}
indexPart = append(indexPart, index)
}
if len(indexPart) > 0 {
urls += strings.Join(indexPart, ",")

+ 9
- 1
refresh.go View File

@@ -11,6 +11,8 @@ import (
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

type RefreshService struct {
@@ -61,7 +63,13 @@ func (s *RefreshService) Do() (*RefreshResult, error) {
// Indices part
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err := uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return nil, err
}
indexPart = append(indexPart, index)
}
if len(indexPart) > 0 {
urls += strings.Join(indexPart, ",")

+ 16
- 2
scan.go View File

@@ -13,6 +13,8 @@ import (
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

const (
@@ -122,7 +124,13 @@ func (s *ScanService) Do() (*ScanCursor, error) {
// Indices part
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err := uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return nil, err
}
indexPart = append(indexPart, index)
}
if len(indexPart) > 0 {
urls += strings.Join(indexPart, ",")
@@ -131,7 +139,13 @@ func (s *ScanService) Do() (*ScanCursor, error) {
// Types
typesPart := make([]string, 0)
for _, typ := range s.types {
typesPart = append(typesPart, cleanPathString(typ))
typ, err := uritemplates.Expand("{type}", map[string]string{
"type": typ,
})
if err != nil {
return nil, err
}
typesPart = append(typesPart, typ)
}
if len(typesPart) > 0 {
urls += "/" + strings.Join(typesPart, ",")

+ 16
- 2
scroll.go View File

@@ -12,6 +12,8 @@ import (
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

// ScrollService manages a cursor through documents in Elasticsearch.
@@ -122,7 +124,13 @@ func (s *ScrollService) GetFirstPage() (*SearchResult, error) {
// Indices part
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err := uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return nil, err
}
indexPart = append(indexPart, index)
}
if len(indexPart) > 0 {
urls += strings.Join(indexPart, ",")
@@ -131,7 +139,13 @@ func (s *ScrollService) GetFirstPage() (*SearchResult, error) {
// Types
typesPart := make([]string, 0)
for _, typ := range s.types {
typesPart = append(typesPart, cleanPathString(typ))
typ, err := uritemplates.Expand("{type}", map[string]string{
"type": typ,
})
if err != nil {
return nil, err
}
typesPart = append(typesPart, typ)
}
if len(typesPart) > 0 {
urls += "/" + strings.Join(typesPart, ",")

+ 16
- 2
search.go View File

@@ -11,6 +11,8 @@ import (
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

// Search for documents in Elasticsearch.
@@ -194,7 +196,13 @@ func (s *SearchService) Do() (*SearchResult, error) {
// Indices part
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err := uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return nil, err
}
indexPart = append(indexPart, index)
}
urls += strings.Join(indexPart, ",")

@@ -202,7 +210,13 @@ func (s *SearchService) Do() (*SearchResult, error) {
if len(s.types) > 0 {
typesPart := make([]string, 0)
for _, typ := range s.types {
typesPart = append(typesPart, cleanPathString(typ))
typ, err := uritemplates.Expand("{type}", map[string]string{
"type": typ,
})
if err != nil {
return nil, err
}
typesPart = append(typesPart, typ)
}
urls += "/"
urls += strings.Join(typesPart, ",")

+ 9
- 1
suggest.go View File

@@ -11,6 +11,8 @@ import (
"net/http/httputil"
"net/url"
"strings"

"github.com/olivere/elastic/uritemplates"
)

// SuggestService returns suggestions for text.
@@ -75,7 +77,13 @@ func (s *SuggestService) Do() (SuggestResult, error) {
// Indices part
indexPart := make([]string, 0)
for _, index := range s.indices {
indexPart = append(indexPart, cleanPathString(index))
index, err := uritemplates.Expand("{index}", map[string]string{
"index": index,
})
if err != nil {
return nil, err
}
indexPart = append(indexPart, index)
}
urls += strings.Join(indexPart, ",")


+ 18
- 0
uritemplates/LICENSE View File

@@ -0,0 +1,18 @@
Copyright (c) 2013 Joshua Tacoma

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 359
- 0
uritemplates/uritemplates.go View File

@@ -0,0 +1,359 @@
// Copyright 2013 Joshua Tacoma. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package uritemplates is a level 4 implementation of RFC 6570 (URI
// Template, http://tools.ietf.org/html/rfc6570).
//
// To use uritemplates, parse a template string and expand it with a value
// map:
//
// template, _ := uritemplates.Parse("https://api.github.com/repos{/user,repo}")
// values := make(map[string]interface{})
// values["user"] = "jtacoma"
// values["repo"] = "uritemplates"
// expanded, _ := template.ExpandString(values)
// fmt.Printf(expanded)
//
package uritemplates

import (
"bytes"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)

var (
unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
hex = []byte("0123456789ABCDEF")
)

func pctEncode(src []byte) []byte {
dst := make([]byte, len(src)*3)
for i, b := range src {
buf := dst[i*3 : i*3+3]
buf[0] = 0x25
buf[1] = hex[b/16]
buf[2] = hex[b%16]
}
return dst
}

func escape(s string, allowReserved bool) (escaped string) {
if allowReserved {
escaped = string(reserved.ReplaceAllFunc([]byte(s), pctEncode))
} else {
escaped = string(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
}
return escaped
}

// A UriTemplate is a parsed representation of a URI template.
type UriTemplate struct {
raw string
parts []templatePart
}

// Parse parses a URI template string into a UriTemplate object.
func Parse(rawtemplate string) (template *UriTemplate, err error) {
template = new(UriTemplate)
template.raw = rawtemplate
split := strings.Split(rawtemplate, "{")
template.parts = make([]templatePart, len(split)*2-1)
for i, s := range split {
if i == 0 {
if strings.Contains(s, "}") {
err = errors.New("unexpected }")
break
}
template.parts[i].raw = s
} else {
subsplit := strings.Split(s, "}")
if len(subsplit) != 2 {
err = errors.New("malformed template")
break
}
expression := subsplit[0]
template.parts[i*2-1], err = parseExpression(expression)
if err != nil {
break
}
template.parts[i*2].raw = subsplit[1]
}
}
if err != nil {
template = nil
}
return template, err
}

type templatePart struct {
raw string
terms []templateTerm
first string
sep string
named bool
ifemp string
allowReserved bool
}

type templateTerm struct {
name string
explode bool
truncate int
}

func parseExpression(expression string) (result templatePart, err error) {
switch expression[0] {
case '+':
result.sep = ","
result.allowReserved = true
expression = expression[1:]
case '.':
result.first = "."
result.sep = "."
expression = expression[1:]
case '/':
result.first = "/"
result.sep = "/"
expression = expression[1:]
case ';':
result.first = ";"
result.sep = ";"
result.named = true
expression = expression[1:]
case '?':
result.first = "?"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '&':
result.first = "&"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '#':
result.first = "#"
result.sep = ","
result.allowReserved = true
expression = expression[1:]
default:
result.sep = ","
}
rawterms := strings.Split(expression, ",")
result.terms = make([]templateTerm, len(rawterms))
for i, raw := range rawterms {
result.terms[i], err = parseTerm(raw)
if err != nil {
break
}
}
return result, err
}

func parseTerm(term string) (result templateTerm, err error) {
if strings.HasSuffix(term, "*") {
result.explode = true
term = term[:len(term)-1]
}
split := strings.Split(term, ":")
if len(split) == 1 {
result.name = term
} else if len(split) == 2 {
result.name = split[0]
var parsed int64
parsed, err = strconv.ParseInt(split[1], 10, 0)
result.truncate = int(parsed)
} else {
err = errors.New("multiple colons in same term")
}
if !validname.MatchString(result.name) {
err = errors.New("not a valid name: " + result.name)
}
if result.explode && result.truncate > 0 {
err = errors.New("both explode and prefix modifers on same term")
}
return result, err
}

// Expand expands a URI template with a set of values to produce a string.
func (self *UriTemplate) Expand(value interface{}) (string, error) {
values, ismap := value.(map[string]interface{})
if !ismap {
if m, ismap := struct2map(value); !ismap {
return "", errors.New("expected map[string]interface{}, struct, or pointer to struct.")
} else {
return self.Expand(m)
}
}
var buf bytes.Buffer
for _, p := range self.parts {
err := p.expand(&buf, values)
if err != nil {
return "", err
}
}
return buf.String(), nil
}

func (self *templatePart) expand(buf *bytes.Buffer, values map[string]interface{}) error {
if len(self.raw) > 0 {
buf.WriteString(self.raw)
return nil
}
var zeroLen = buf.Len()
buf.WriteString(self.first)
var firstLen = buf.Len()
for _, term := range self.terms {
value, exists := values[term.name]
if !exists {
continue
}
if buf.Len() != firstLen {
buf.WriteString(self.sep)
}
switch v := value.(type) {
case string:
self.expandString(buf, term, v)
case []interface{}:
self.expandArray(buf, term, v)
case map[string]interface{}:
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, v)
default:
if m, ismap := struct2map(value); ismap {
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, m)
} else {
str := fmt.Sprintf("%v", value)
self.expandString(buf, term, str)
}
}
}
if buf.Len() == firstLen {
original := buf.Bytes()[:zeroLen]
buf.Reset()
buf.Write(original)
}
return nil
}

func (self *templatePart) expandName(buf *bytes.Buffer, name string, empty bool) {
if self.named {
buf.WriteString(name)
if empty {
buf.WriteString(self.ifemp)
} else {
buf.WriteString("=")
}
}
}

func (self *templatePart) expandString(buf *bytes.Buffer, t templateTerm, s string) {
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
self.expandName(buf, t.name, len(s) == 0)
buf.WriteString(escape(s, self.allowReserved))
}

func (self *templatePart) expandArray(buf *bytes.Buffer, t templateTerm, a []interface{}) {
if len(a) == 0 {
return
} else if !t.explode {
self.expandName(buf, t.name, false)
}
for i, value := range a {
if t.explode && i > 0 {
buf.WriteString(self.sep)
} else if i > 0 {
buf.WriteString(",")
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
if self.named && t.explode {
self.expandName(buf, t.name, len(s) == 0)
}
buf.WriteString(escape(s, self.allowReserved))
}
}

func (self *templatePart) expandMap(buf *bytes.Buffer, t templateTerm, m map[string]interface{}) {
if len(m) == 0 {
return
}
if !t.explode {
self.expandName(buf, t.name, len(m) == 0)
}
var firstLen = buf.Len()
for k, value := range m {
if firstLen != buf.Len() {
if t.explode {
buf.WriteString(self.sep)
} else {
buf.WriteString(",")
}
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if t.explode {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune('=')
buf.WriteString(escape(s, self.allowReserved))
} else {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune(',')
buf.WriteString(escape(s, self.allowReserved))
}
}
}

func struct2map(v interface{}) (map[string]interface{}, bool) {
value := reflect.ValueOf(v)
switch value.Type().Kind() {
case reflect.Ptr:
return struct2map(value.Elem().Interface())
case reflect.Struct:
m := make(map[string]interface{})
for i := 0; i < value.NumField(); i++ {
tag := value.Type().Field(i).Tag
var name string
if strings.Contains(string(tag), ":") {
name = tag.Get("uri")
} else {
name = strings.TrimSpace(string(tag))
}
if len(name) == 0 {
name = value.Type().Field(i).Name
}
m[name] = value.Field(i).Interface()
}
return m, true
}
return nil, false
}

+ 13
- 0
uritemplates/utils.go View File

@@ -0,0 +1,13 @@
package uritemplates

func Expand(path string, expansions map[string]string) (string, error) {
template, err := Parse(path)
if err != nil {
return "", err
}
values := make(map[string]interface{})
for k, v := range expansions {
values[k] = v
}
return template.Expand(values)
}

+ 105
- 0
uritemplates/utils_test.go View File

@@ -0,0 +1,105 @@
package uritemplates

import (
"testing"
)

type ExpandTest struct {
in string
expansions map[string]string
want string
}

var expandTests = []ExpandTest{
// #0: no expansions
{
"http://www.golang.org/",
map[string]string{},
"http://www.golang.org/",
},
// #1: one expansion, no escaping
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red",
},
"http://www.golang.org/red/delete",
},
// #2: one expansion, with hex escapes
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red/blue",
},
"http://www.golang.org/red%2Fblue/delete",
},
// #3: one expansion, with space
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red or blue",
},
"http://www.golang.org/red%20or%20blue/delete",
},
// #4: expansion not found
{
"http://www.golang.org/{object}/delete",
map[string]string{
"bucket": "red or blue",
},
"http://www.golang.org//delete",
},
// #5: multiple expansions
{
"http://www.golang.org/{one}/{two}/{three}/get",
map[string]string{
"one": "ONE",
"two": "TWO",
"three": "THREE",
},
"http://www.golang.org/ONE/TWO/THREE/get",
},
// #6: utf-8 characters
{
"http://www.golang.org/{bucket}/get",
map[string]string{
"bucket": "£100",
},
"http://www.golang.org/%C2%A3100/get",
},
// #7: punctuations
{
"http://www.golang.org/{bucket}/get",
map[string]string{
"bucket": `/\@:,.*~`,
},
"http://www.golang.org/%2F%5C%40%3A%2C.%2A~/get",
},
// #8: mis-matched brackets
{
"http://www.golang.org/{bucket/get",
map[string]string{
"bucket": "red",
},
"",
},
// #9: "+" prefix for suppressing escape
// See also: http://tools.ietf.org/html/rfc6570#section-3.2.3
{
"http://www.golang.org/{+topic}",
map[string]string{
"topic": "/topics/myproject/mytopic",
},
// The double slashes here look weird, but it's intentional
"http://www.golang.org//topics/myproject/mytopic",
},
}

func TestExpand(t *testing.T) {
for i, test := range expandTests {
got, _ := Expand(test.in, test.expansions)
if got != test.want {
t.Errorf("got %q expected %q in test %d", got, test.want, i)
}
}
}

+ 0
- 21
utils.go View File

@@ -1,21 +0,0 @@
// Copyright 2012-2014 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package elastic

import (
"strings"
)

// Removes all characters unless they're
// between greater or equal to 0x2d (-) or
// less than or equal to (z) or a tilde (~).
func cleanPathString(s string) string {
return strings.Map(func(r rune) rune {
if r >= 0x2d && r <= 0x7a || r == '~' {
return r
}
return -1
}, s)
}

Loading…
Cancel
Save