Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

much simpler serialization

  • Loading branch information...
commit 22e87afc49b367e9111a898224828d096c045c57 1 parent d338e72
Fabio Kung authored

Showing 2 changed files with 182 additions and 125 deletions. Show diff stats Hide diff stats

  1. +47 29 dynamodb.go
  2. +135 96 json.go
76 dynamodb.go
@@ -4,13 +4,13 @@ import (
4 4 "bytes"
5 5 "encoding/json"
6 6 "github.com/bmizerany/aws4"
  7 + "log"
7 8 "io/ioutil"
8 9 "net/http"
  10 + "net/http/httputil"
9 11 "time"
10 12 )
11 13
12   -const iSO8601BasicFormat = "20060102T150405Z"
13   -
14 14 type Region struct {
15 15 name string
16 16 endpoint string
@@ -41,13 +41,23 @@ func NewTable(name string, region *Region, awsAccessKeyId string, awsSecretAcces
41 41 return &Table{name, region, k, s}
42 42 }
43 43
44   -func (t *Table) PutItem(item interface{}) error {
45   - body, err := t.putItemRequestBody(item)
  44 +func (t *Table) UpdateItem(key interface{}, item map[string]interface{}) error {
  45 + k, err := NewField(key)
46 46 if err != nil {
47 47 return err
48 48 }
  49 + attrs, err := valuesToAttributeMap(item)
  50 + if err != nil {
  51 + return err
  52 + }
  53 +
  54 + r := new(UpdateItemRequest)
  55 + r.TableName = t.name
  56 + r.Key = Key{HashKeyElement: k}
  57 + r.AttributeUpdates = attrs
  58 + r.ReturnValues = "UPDATED_OLD"
49 59
50   - _, err = t.doDynamoRequest("PutItem", body)
  60 + _, err = t.doDynamoRequest("PutItem", r)
51 61 if err != nil {
52 62 return err
53 63 }
@@ -55,46 +65,48 @@ func (t *Table) PutItem(item interface{}) error {
55 65 return nil
56 66 }
57 67
58   -func (t *Table) Query(key interface{}, limit int, consistent bool) ([]map[string]interface{}, error) {
59   - body, err := t.queryRequestBody(key, limit, consistent)
  68 +func (t *Table) Query(key interface{}, consistent bool) ([]map[string]interface{}, error) {
  69 + k, err := NewField(key)
60 70 if err != nil {
61 71 return nil, err
62 72 }
63 73
64   - resp, err := t.doDynamoRequest("Query", body)
  74 + r := new(QueryRequest)
  75 + r.TableName = t.name
  76 + r.HashKeyValue = k
  77 + r.ConsistentRead = consistent
  78 +
  79 +
  80 + rawResp, err := t.doDynamoRequest("Query", r)
65 81 if err != nil {
66 82 return nil, err
67 83 }
68   -
69   - data := make(map[string]interface{})
70   - err = json.Unmarshal(resp, &data)
  84 + resp := new(QueryResponse)
  85 + err = json.Unmarshal(rawResp, &resp)
71 86 if err != nil {
72 87 return nil, err
73 88 }
74 89
75   - items := data["Items"].([]interface{})
76   - parsed := make([]map[string]interface{}, len(items))
77   - for i, raw := range items {
78   - item := raw.(map[string]interface{})
79   - parsed[i], err = dynamoItemToMap(item)
80   - if err != nil {
81   - return parsed, err
82   - }
  90 + items := make([]map[string]interface{}, len(resp.Items))
  91 + for i, item := range resp.Items {
  92 + items[i] = item.Map()
83 93 }
84   -
85   - return parsed, nil
  94 + return items, nil
86 95 }
87 96
88   -func (t *Table) doDynamoRequest(operation string, body []byte) ([]byte, error) {
89   - req, err := http.NewRequest("POST", t.region.url(), ioutil.NopCloser(bytes.NewReader(body)))
  97 +func (t *Table) doDynamoRequest(operation string, body interface{}) ([]byte, error) {
  98 + var b bytes.Buffer
  99 + if err := json.NewEncoder(&b).Encode(body); err != nil {
  100 + return nil, err
  101 + }
  102 +
  103 + req, err := http.NewRequest("POST", t.region.url(), &b)
90 104 if err != nil {
91 105 return nil, err
92 106 }
93 107
94   - req.ContentLength = int64(len(body))
95   - req.Header.Set("Host", t.region.endpoint)
96   - req.Header.Set("X-Amz-Target", "DynamoDB_20111205."+operation)
97 108 req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
  109 + req.Header.Set("X-Amz-Target", "DynamoDB_20111205."+operation)
98 110 req.Header.Set("Content-Type", "application/x-amz-json-1.0")
99 111 req.Header.Set("Connection", "Keep-Alive")
100 112
@@ -103,18 +115,24 @@ func (t *Table) doDynamoRequest(operation string, body []byte) ([]byte, error) {
103 115 return nil, err
104 116 }
105 117
  118 + out, err := httputil.DumpRequestOut(req, true)
  119 + if err != nil {
  120 + return nil, err
  121 + }
  122 + log.Println(string(out))
  123 +
106 124 resp, err := http.DefaultClient.Do(req)
107 125 if err != nil {
108 126 return nil, err
109 127 }
110 128
111 129 defer resp.Body.Close()
112   - body, err = ioutil.ReadAll(resp.Body)
  130 + respBody, err := ioutil.ReadAll(resp.Body)
113 131 if resp.StatusCode != 200 {
114   - return body, RequestError{Status: resp.Status, Message: string(body)}
  132 + return respBody, RequestError{Status: resp.Status, Message: string(respBody)}
115 133 }
116 134
117   - return body, err
  135 + return respBody, err
118 136 }
119 137
120 138 type RequestError struct {
231 json.go
... ... @@ -1,7 +1,6 @@
1 1 package dynamodb
2 2
3 3 import (
4   - "bytes"
5 4 "encoding/json"
6 5 "fmt"
7 6 "reflect"
@@ -9,93 +8,175 @@ import (
9 8 "strings"
10 9 )
11 10
12   -type queryRequest struct {
13   - TableName string
14   - Limit int `json:",omitempty"`
15   - ConsistentRead bool `json:",omitempty"`
16   - HashKeyValue map[string]string
  11 +type Key struct {
  12 + HashKeyElement Field
  13 + RangeKeyElement Field `json:",omitempty"`
17 14 }
18 15
19   -func (t *Table) queryRequestBody(key interface{}, limit int, consistent bool) ([]byte, error) {
20   - v := reflect.ValueOf(key)
21   - typeId, value, err := fieldToDynamoString(v)
22   - if err != nil {
23   - return []byte(""), err
24   - }
  16 +type Field interface {
  17 + Type() string
  18 + Value() interface{}
  19 +}
  20 +
  21 +type Number struct {
  22 + N interface{} `json:",string"`
  23 +}
  24 +
  25 +func (n *Number) Type() string {
  26 + return "N"
  27 +}
25 28
26   - request := &queryRequest{
27   - TableName: t.name,
28   - Limit: limit,
29   - ConsistentRead: consistent,
30   - HashKeyValue: make(map[string]string)}
31   - request.HashKeyValue[typeId] = value
32   - return json.Marshal(request)
  29 +func (n *Number) Value() interface{} {
  30 + return n.N
33 31 }
34 32
35   -type putItemRequest struct {
36   - TableName string
37   - Item putRequestItem
  33 +type String struct {
  34 + S string
38 35 }
39 36
40   -func (t *Table) putItemRequestBody(item interface{}) ([]byte, error) {
41   - data := putItemRequest{TableName: t.name, Item: putRequestItem{&item}}
42   - return json.Marshal(data)
  37 +func (s *String) Type() string {
  38 + return "S"
43 39 }
44 40
45   -type putRequestItem struct {
46   - Value interface{}
  41 +func (s *String) Value() interface{} {
  42 + return s.S
47 43 }
48 44
49   -func (i putRequestItem) MarshalJSON() ([]byte, error) {
50   - var out bytes.Buffer
  45 +type Byte struct {
  46 + B []byte `json:",string"`
  47 +}
  48 +
  49 +func (b *Byte) Type() string {
  50 + return "B"
  51 +}
51 52
52   - v := reflect.ValueOf(i.Value)
53   - for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
  53 +func (b *Byte) Value() interface{} {
  54 + return b.B
  55 +}
  56 +
  57 +func NewField(value interface{}) (Field, error) {
  58 + v := reflect.ValueOf(value)
  59 + if v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
54 60 v = v.Elem()
55 61 }
56 62
57   - t := v.Type()
  63 + switch v.Kind() {
  64 + case reflect.String:
  65 + return &String{S: value.(string)}, nil
  66 +
  67 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
  68 + reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
  69 + reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
  70 + return &Number{N: value}, nil
  71 + }
  72 +
  73 + // TODO: []byte
  74 +
  75 + return nil, &json.MarshalerError{Type: v.Type()}
  76 +}
  77 +
  78 +// UpdateItem
58 79
59   - out.WriteString("{")
60   - for i := 0; i < v.NumField(); i++ {
61   - f := v.Field(i)
62   - out.WriteString("\"" + t.Field(i).Name + "\":")
  80 +type UpdateItemRequest struct {
  81 + TableName string
  82 + Key Key
  83 + AttributeUpdates map[string]Attribute
  84 + Expected map[string]Attribute `json:",omitempty"`
  85 + ReturnValues string
  86 +}
  87 +
  88 +type Attribute struct {
  89 + Value Field
  90 +}
63 91
64   - typeId, fieldVal, err := fieldToDynamoString(f)
  92 +func valuesToAttributeMap(item map[string]interface{}) (map[string]Attribute, error) {
  93 + attrs := make(map[string]Attribute, len(item))
  94 + for n, v := range item {
  95 + f, err := NewField(v)
65 96 if err != nil {
66   - return []byte(""), err
  97 + return nil, err
67 98 }
68 99
69   - out.WriteString("{")
70   - out.WriteString("\"" + typeId + "\":")
71   - out.WriteString("\"" + fieldVal + "\"")
72   - out.WriteString("}")
  100 + attrs[n] = Attribute{Value: f}
  101 + }
  102 + return attrs, nil
  103 +}
73 104
74   - if i < v.NumField()-1 {
75   - out.WriteString(",")
76   - }
  105 +func attributeMapToValues(attrs map[string]Attribute) map[string]interface{} {
  106 + item := make(map[string]interface{}, len(attrs))
  107 + for n, a := range attrs {
  108 + item[n] = a.Value.Value()
77 109 }
78   - out.WriteString("}")
  110 + return item
  111 +}
79 112
80   - return out.Bytes(), nil
  113 +// Query
  114 +
  115 +type QueryRequest struct {
  116 + TableName string
  117 + HashKeyValue Field
  118 + ConsistentRead bool `json:",omitempty"`
  119 + ScanIndexForward bool `json:",omitempty"`
  120 + RangeKeyCondition QueryAttributes `json:",omitempty"`
  121 + Limit int `json:",omitempty"`
  122 + ExclusiveStartKey Key `json:",omitempty"`
  123 + AttributesToGet []string `json:",omitempty"`
81 124 }
82 125
83   -func dynamoItemToMap(item map[string]interface{}) (map[string]interface{}, error) {
84   - result := make(map[string]interface{}, len(item))
  126 +type QueryAttributes struct {
  127 + AttributeValueList []Field
  128 + ComparisonOperator string
  129 +}
  130 +
  131 +type QueryResponse struct {
  132 + Count int
  133 + Items []QueryItem
  134 + LastEvaluatedKey Key
  135 + ConsumedCapacityUnits int
  136 +}
  137 +
  138 +type QueryItem struct {
  139 + Item map[string]Field
  140 +}
  141 +
  142 +func (qi *QueryItem) Map() map[string]interface{} {
  143 + r := make(map[string]interface{}, len(qi.Item))
  144 + for n, f := range qi.Item {
  145 + r[n] = f.Value()
  146 + }
  147 + return r
  148 +}
  149 +
  150 +func (q *QueryItem) UnmarshalJSON(data []byte) error {
  151 + var items map[string]interface{}
  152 + if err := json.Unmarshal(data, &items); err != nil {
  153 + return err
  154 + }
  155 +
  156 + fields, err := itemsToFields(items)
  157 + if err != nil {
  158 + return err
  159 + }
  160 + q.Item = fields
  161 + return nil
  162 +}
  163 +
  164 +func itemsToFields(item map[string]interface{}) (map[string]Field, error) {
  165 + result := make(map[string]Field, len(item))
85 166 for name, raw := range item {
86 167 attr := raw.(map[string]interface{})
87 168
88   - var value interface{}
  169 + var value Field
89 170 if v, ok := attr["S"]; ok {
90   - value = v.(string)
  171 + value = &String{S: v.(string)}
91 172 } else if v, ok := attr["N"]; ok {
92   - var err error
93   - value, err = parseNumber(v.(string))
  173 + n, err := parseNumber(v.(string))
94 174 if err != nil {
95 175 return result, err
96 176 }
  177 + value = &Number{N: n}
97 178 } else if v, ok := attr["B"]; ok {
98   - value = []byte(v.(string))
  179 + value = &Byte{B: []byte(v.(string))}
99 180 } else {
100 181 var first string
101 182 for k, _ := range attr {
@@ -120,48 +201,6 @@ func parseNumber(value string) (number interface{}, err error) {
120 201 return
121 202 }
122 203
123   -func fieldToDynamoString(v reflect.Value) (typeId string, value string, err error) {
124   - if v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
125   - v = v.Elem()
126   - }
127   -
128   - switch v.Kind() {
129   -
130   - case reflect.String:
131   - return "S", v.Interface().(string), nil
132   -
133   - case reflect.Int:
134   - return "N", strconv.FormatInt(int64(v.Interface().(int)), 10), nil
135   - case reflect.Int8:
136   - return "N", strconv.FormatInt(int64(v.Interface().(int8)), 10), nil
137   - case reflect.Int16:
138   - return "N", strconv.FormatInt(int64(v.Interface().(int16)), 10), nil
139   - case reflect.Int32:
140   - return "N", strconv.FormatInt(int64(v.Interface().(int32)), 10), nil
141   - case reflect.Int64:
142   - return "N", strconv.FormatInt(v.Interface().(int64), 10), nil
143   -
144   - case reflect.Uint:
145   - return "N", strconv.FormatUint(uint64(v.Interface().(uint)), 10), nil
146   - case reflect.Uint8:
147   - return "N", strconv.FormatUint(uint64(v.Interface().(uint8)), 10), nil
148   - case reflect.Uint16:
149   - return "N", strconv.FormatUint(uint64(v.Interface().(uint16)), 10), nil
150   - case reflect.Uint32:
151   - return "N", strconv.FormatUint(uint64(v.Interface().(uint32)), 10), nil
152   - case reflect.Uint64:
153   - return "N", strconv.FormatUint(v.Interface().(uint64), 10), nil
154   -
155   - case reflect.Float32:
156   - return "N", strconv.FormatFloat(float64(v.Interface().(float32)), 'f', -1, 32), nil
157   - case reflect.Float64:
158   - return "N", strconv.FormatFloat(v.Interface().(float64), 'f', -1, 64), nil
159   -
160   - }
161   -
162   - return "", "", &json.MarshalerError{Type: v.Type()}
163   -}
164   -
165 204 type UnsupportedTypeError struct {
166 205 TypeId string
167 206 }

0 comments on commit 22e87af

Please sign in to comment.
Something went wrong with that request. Please try again.