Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

much simpler serialization

  • Loading branch information...
commit 22e87afc49b367e9111a898224828d096c045c57 1 parent d338e72
@fabiokung authored
Showing with 182 additions and 125 deletions.
  1. +47 −29 dynamodb.go
  2. +135 −96 json.go
View
76 dynamodb.go
@@ -4,13 +4,13 @@ import (
"bytes"
"encoding/json"
"github.com/bmizerany/aws4"
+ "log"
"io/ioutil"
"net/http"
+ "net/http/httputil"
"time"
)
-const iSO8601BasicFormat = "20060102T150405Z"
-
type Region struct {
name string
endpoint string
@@ -41,13 +41,23 @@ func NewTable(name string, region *Region, awsAccessKeyId string, awsSecretAcces
return &Table{name, region, k, s}
}
-func (t *Table) PutItem(item interface{}) error {
- body, err := t.putItemRequestBody(item)
+func (t *Table) UpdateItem(key interface{}, item map[string]interface{}) error {
+ k, err := NewField(key)
if err != nil {
return err
}
+ attrs, err := valuesToAttributeMap(item)
+ if err != nil {
+ return err
+ }
+
+ r := new(UpdateItemRequest)
+ r.TableName = t.name
+ r.Key = Key{HashKeyElement: k}
+ r.AttributeUpdates = attrs
+ r.ReturnValues = "UPDATED_OLD"
- _, err = t.doDynamoRequest("PutItem", body)
+ _, err = t.doDynamoRequest("PutItem", r)
if err != nil {
return err
}
@@ -55,46 +65,48 @@ func (t *Table) PutItem(item interface{}) error {
return nil
}
-func (t *Table) Query(key interface{}, limit int, consistent bool) ([]map[string]interface{}, error) {
- body, err := t.queryRequestBody(key, limit, consistent)
+func (t *Table) Query(key interface{}, consistent bool) ([]map[string]interface{}, error) {
+ k, err := NewField(key)
if err != nil {
return nil, err
}
- resp, err := t.doDynamoRequest("Query", body)
+ r := new(QueryRequest)
+ r.TableName = t.name
+ r.HashKeyValue = k
+ r.ConsistentRead = consistent
+
+
+ rawResp, err := t.doDynamoRequest("Query", r)
if err != nil {
return nil, err
}
-
- data := make(map[string]interface{})
- err = json.Unmarshal(resp, &data)
+ resp := new(QueryResponse)
+ err = json.Unmarshal(rawResp, &resp)
if err != nil {
return nil, err
}
- items := data["Items"].([]interface{})
- parsed := make([]map[string]interface{}, len(items))
- for i, raw := range items {
- item := raw.(map[string]interface{})
- parsed[i], err = dynamoItemToMap(item)
- if err != nil {
- return parsed, err
- }
+ items := make([]map[string]interface{}, len(resp.Items))
+ for i, item := range resp.Items {
+ items[i] = item.Map()
}
-
- return parsed, nil
+ return items, nil
}
-func (t *Table) doDynamoRequest(operation string, body []byte) ([]byte, error) {
- req, err := http.NewRequest("POST", t.region.url(), ioutil.NopCloser(bytes.NewReader(body)))
+func (t *Table) doDynamoRequest(operation string, body interface{}) ([]byte, error) {
+ var b bytes.Buffer
+ if err := json.NewEncoder(&b).Encode(body); err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", t.region.url(), &b)
if err != nil {
return nil, err
}
- req.ContentLength = int64(len(body))
- req.Header.Set("Host", t.region.endpoint)
- req.Header.Set("X-Amz-Target", "DynamoDB_20111205."+operation)
req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
+ req.Header.Set("X-Amz-Target", "DynamoDB_20111205."+operation)
req.Header.Set("Content-Type", "application/x-amz-json-1.0")
req.Header.Set("Connection", "Keep-Alive")
@@ -103,18 +115,24 @@ func (t *Table) doDynamoRequest(operation string, body []byte) ([]byte, error) {
return nil, err
}
+ out, err := httputil.DumpRequestOut(req, true)
+ if err != nil {
+ return nil, err
+ }
+ log.Println(string(out))
+
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
- body, err = ioutil.ReadAll(resp.Body)
+ respBody, err := ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 {
- return body, RequestError{Status: resp.Status, Message: string(body)}
+ return respBody, RequestError{Status: resp.Status, Message: string(respBody)}
}
- return body, err
+ return respBody, err
}
type RequestError struct {
View
231 json.go
@@ -1,7 +1,6 @@
package dynamodb
import (
- "bytes"
"encoding/json"
"fmt"
"reflect"
@@ -9,93 +8,175 @@ import (
"strings"
)
-type queryRequest struct {
- TableName string
- Limit int `json:",omitempty"`
- ConsistentRead bool `json:",omitempty"`
- HashKeyValue map[string]string
+type Key struct {
+ HashKeyElement Field
+ RangeKeyElement Field `json:",omitempty"`
}
-func (t *Table) queryRequestBody(key interface{}, limit int, consistent bool) ([]byte, error) {
- v := reflect.ValueOf(key)
- typeId, value, err := fieldToDynamoString(v)
- if err != nil {
- return []byte(""), err
- }
+type Field interface {
+ Type() string
+ Value() interface{}
+}
+
+type Number struct {
+ N interface{} `json:",string"`
+}
+
+func (n *Number) Type() string {
+ return "N"
+}
- request := &queryRequest{
- TableName: t.name,
- Limit: limit,
- ConsistentRead: consistent,
- HashKeyValue: make(map[string]string)}
- request.HashKeyValue[typeId] = value
- return json.Marshal(request)
+func (n *Number) Value() interface{} {
+ return n.N
}
-type putItemRequest struct {
- TableName string
- Item putRequestItem
+type String struct {
+ S string
}
-func (t *Table) putItemRequestBody(item interface{}) ([]byte, error) {
- data := putItemRequest{TableName: t.name, Item: putRequestItem{&item}}
- return json.Marshal(data)
+func (s *String) Type() string {
+ return "S"
}
-type putRequestItem struct {
- Value interface{}
+func (s *String) Value() interface{} {
+ return s.S
}
-func (i putRequestItem) MarshalJSON() ([]byte, error) {
- var out bytes.Buffer
+type Byte struct {
+ B []byte `json:",string"`
+}
+
+func (b *Byte) Type() string {
+ return "B"
+}
- v := reflect.ValueOf(i.Value)
- for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
+func (b *Byte) Value() interface{} {
+ return b.B
+}
+
+func NewField(value interface{}) (Field, error) {
+ v := reflect.ValueOf(value)
+ if v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
v = v.Elem()
}
- t := v.Type()
+ switch v.Kind() {
+ case reflect.String:
+ return &String{S: value.(string)}, nil
+
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
+ reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
+ reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
+ return &Number{N: value}, nil
+ }
+
+ // TODO: []byte
+
+ return nil, &json.MarshalerError{Type: v.Type()}
+}
+
+// UpdateItem
- out.WriteString("{")
- for i := 0; i < v.NumField(); i++ {
- f := v.Field(i)
- out.WriteString("\"" + t.Field(i).Name + "\":")
+type UpdateItemRequest struct {
+ TableName string
+ Key Key
+ AttributeUpdates map[string]Attribute
+ Expected map[string]Attribute `json:",omitempty"`
+ ReturnValues string
+}
+
+type Attribute struct {
+ Value Field
+}
- typeId, fieldVal, err := fieldToDynamoString(f)
+func valuesToAttributeMap(item map[string]interface{}) (map[string]Attribute, error) {
+ attrs := make(map[string]Attribute, len(item))
+ for n, v := range item {
+ f, err := NewField(v)
if err != nil {
- return []byte(""), err
+ return nil, err
}
- out.WriteString("{")
- out.WriteString("\"" + typeId + "\":")
- out.WriteString("\"" + fieldVal + "\"")
- out.WriteString("}")
+ attrs[n] = Attribute{Value: f}
+ }
+ return attrs, nil
+}
- if i < v.NumField()-1 {
- out.WriteString(",")
- }
+func attributeMapToValues(attrs map[string]Attribute) map[string]interface{} {
+ item := make(map[string]interface{}, len(attrs))
+ for n, a := range attrs {
+ item[n] = a.Value.Value()
}
- out.WriteString("}")
+ return item
+}
- return out.Bytes(), nil
+// Query
+
+type QueryRequest struct {
+ TableName string
+ HashKeyValue Field
+ ConsistentRead bool `json:",omitempty"`
+ ScanIndexForward bool `json:",omitempty"`
+ RangeKeyCondition QueryAttributes `json:",omitempty"`
+ Limit int `json:",omitempty"`
+ ExclusiveStartKey Key `json:",omitempty"`
+ AttributesToGet []string `json:",omitempty"`
}
-func dynamoItemToMap(item map[string]interface{}) (map[string]interface{}, error) {
- result := make(map[string]interface{}, len(item))
+type QueryAttributes struct {
+ AttributeValueList []Field
+ ComparisonOperator string
+}
+
+type QueryResponse struct {
+ Count int
+ Items []QueryItem
+ LastEvaluatedKey Key
+ ConsumedCapacityUnits int
+}
+
+type QueryItem struct {
+ Item map[string]Field
+}
+
+func (qi *QueryItem) Map() map[string]interface{} {
+ r := make(map[string]interface{}, len(qi.Item))
+ for n, f := range qi.Item {
+ r[n] = f.Value()
+ }
+ return r
+}
+
+func (q *QueryItem) UnmarshalJSON(data []byte) error {
+ var items map[string]interface{}
+ if err := json.Unmarshal(data, &items); err != nil {
+ return err
+ }
+
+ fields, err := itemsToFields(items)
+ if err != nil {
+ return err
+ }
+ q.Item = fields
+ return nil
+}
+
+func itemsToFields(item map[string]interface{}) (map[string]Field, error) {
+ result := make(map[string]Field, len(item))
for name, raw := range item {
attr := raw.(map[string]interface{})
- var value interface{}
+ var value Field
if v, ok := attr["S"]; ok {
- value = v.(string)
+ value = &String{S: v.(string)}
} else if v, ok := attr["N"]; ok {
- var err error
- value, err = parseNumber(v.(string))
+ n, err := parseNumber(v.(string))
if err != nil {
return result, err
}
+ value = &Number{N: n}
} else if v, ok := attr["B"]; ok {
- value = []byte(v.(string))
+ value = &Byte{B: []byte(v.(string))}
} else {
var first string
for k, _ := range attr {
@@ -120,48 +201,6 @@ func parseNumber(value string) (number interface{}, err error) {
return
}
-func fieldToDynamoString(v reflect.Value) (typeId string, value string, err error) {
- if v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
- v = v.Elem()
- }
-
- switch v.Kind() {
-
- case reflect.String:
- return "S", v.Interface().(string), nil
-
- case reflect.Int:
- return "N", strconv.FormatInt(int64(v.Interface().(int)), 10), nil
- case reflect.Int8:
- return "N", strconv.FormatInt(int64(v.Interface().(int8)), 10), nil
- case reflect.Int16:
- return "N", strconv.FormatInt(int64(v.Interface().(int16)), 10), nil
- case reflect.Int32:
- return "N", strconv.FormatInt(int64(v.Interface().(int32)), 10), nil
- case reflect.Int64:
- return "N", strconv.FormatInt(v.Interface().(int64), 10), nil
-
- case reflect.Uint:
- return "N", strconv.FormatUint(uint64(v.Interface().(uint)), 10), nil
- case reflect.Uint8:
- return "N", strconv.FormatUint(uint64(v.Interface().(uint8)), 10), nil
- case reflect.Uint16:
- return "N", strconv.FormatUint(uint64(v.Interface().(uint16)), 10), nil
- case reflect.Uint32:
- return "N", strconv.FormatUint(uint64(v.Interface().(uint32)), 10), nil
- case reflect.Uint64:
- return "N", strconv.FormatUint(v.Interface().(uint64), 10), nil
-
- case reflect.Float32:
- return "N", strconv.FormatFloat(float64(v.Interface().(float32)), 'f', -1, 32), nil
- case reflect.Float64:
- return "N", strconv.FormatFloat(v.Interface().(float64), 'f', -1, 64), nil
-
- }
-
- return "", "", &json.MarshalerError{Type: v.Type()}
-}
-
type UnsupportedTypeError struct {
TypeId string
}
Please sign in to comment.
Something went wrong with that request. Please try again.