Merge lp:~prudhvikrishna/goamz/sns into lp:~niemeyer/goamz/aws
- sns
- Merge into aws
Proposed by
Prudhvi Surapaneni
Status: | Needs review |
---|---|
Proposed branch: | lp:~prudhvikrishna/goamz/sns |
Merge into: | lp:~niemeyer/goamz/aws |
Diff against target: |
1084 lines (+1047/-0) (has conflicts) 7 files modified
Makefile (+21/-0) README (+1/-0) responses_test.go (+164/-0) sign.go (+64/-0) sns.go (+428/-0) sns_test.go (+230/-0) suite_test.go (+139/-0) Conflict adding file Makefile. Moved existing file to Makefile.moved. Conflict adding file suite_test.go. Moved existing file to suite_test.go.moved. |
To merge this branch: | bzr merge lp:~prudhvikrishna/goamz/sns |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Needs Fixing | ||
Review via email: mp+76465@code.launchpad.net |
Commit message
Description of the change
New SNS Related Code.
To post a comment you must log in.
Unmerged revisions
- 24. By Prudhvi Surapaneni
-
Refactor to be consistent
- 23. By Prudhvi Surapaneni
-
Add Comments
- 22. By Prudhvi Surapaneni
-
Add Functionality for ListSubscriptio
nsByTopic - 21. By Prudhvi Surapaneni
-
Add functionality for RemovePermission
- 20. By Prudhvi Surapaneni
-
Add functionality for AddPermission
- 19. By Prudhvi Surapaneni
-
Add functionality for ConfirmSubscription
- 18. By Prudhvi Surapaneni
-
Add functionality for Unsubscribe
- 17. By Prudhvi Surapaneni
-
gofmt changes
- 16. By Prudhvi Surapaneni
-
Add functionality for Subscribe
- 15. By Prudhvi Surapaneni
-
Add functionality for SetTopicAttributes
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'Makefile' |
2 | --- Makefile 1970-01-01 00:00:00 +0000 |
3 | +++ Makefile 2011-09-21 19:16:26 +0000 |
4 | @@ -0,0 +1,21 @@ |
5 | +include $(GOROOT)/src/Make.inc |
6 | + |
7 | +TARG=launchpad.net/goamz/sns |
8 | + |
9 | +GOFILES=\ |
10 | + sns.go\ |
11 | + sign.go\ |
12 | + |
13 | +include $(GOROOT)/src/Make.pkg |
14 | + |
15 | +GOFMT=gofmt |
16 | +BADFMT=$(shell $(GOFMT) -l $(GOFILES) 2> /dev/null) |
17 | + |
18 | +gofmt: $(BADFMT) |
19 | + @for F in $(BADFMT); do $(GOFMT) -w $$F && echo $$F; done |
20 | + |
21 | +ifneq ($(BADFMT),) |
22 | +ifneq ($(MAKECMDGOALS), gofmt) |
23 | +#$(warning WARNING: make gofmt: $(BADFMT)) |
24 | +endif |
25 | +endif |
26 | |
27 | === renamed file 'Makefile' => 'Makefile.moved' |
28 | === added file 'README' |
29 | --- README 1970-01-01 00:00:00 +0000 |
30 | +++ README 2011-09-21 19:16:26 +0000 |
31 | @@ -0,0 +1,1 @@ |
32 | +Amazon Simple Notification Service API for Golang. |
33 | |
34 | === added file 'responses_test.go' |
35 | --- responses_test.go 1970-01-01 00:00:00 +0000 |
36 | +++ responses_test.go 2011-09-21 19:16:26 +0000 |
37 | @@ -0,0 +1,164 @@ |
38 | +package sns_test |
39 | + |
40 | +var TestListTopicsXmlOK = ` |
41 | +<?xml version="1.0"?> |
42 | +<ListTopicsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
43 | + <ListTopicsResult> |
44 | + <Topics> |
45 | + <member> |
46 | + <TopicArn>arn:aws:sns:us-west-1:331995417492:Transcoding</TopicArn> |
47 | + </member> |
48 | + </Topics> |
49 | + </ListTopicsResult> |
50 | + <ResponseMetadata> |
51 | + <RequestId>bd10b26c-e30e-11e0-ba29-93c3aca2f103</RequestId> |
52 | + </ResponseMetadata> |
53 | +</ListTopicsResponse> |
54 | +` |
55 | + |
56 | +var TestCreateTopicXmlOK = ` |
57 | +<?xml version="1.0"?> |
58 | +<CreateTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
59 | + <CreateTopicResult> |
60 | + <TopicArn>arn:aws:sns:us-east-1:123456789012:My-Topic</TopicArn> |
61 | + </CreateTopicResult> |
62 | + <ResponseMetadata> |
63 | + <RequestId>a8dec8b3-33a4-11df-8963-01868b7c937a</RequestId> |
64 | + </ResponseMetadata> |
65 | +</CreateTopicResponse> |
66 | +` |
67 | + |
68 | +var TestDeleteTopicXmlOK = ` |
69 | +<DeleteTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
70 | + <ResponseMetadata> |
71 | + <RequestId>f3aa9ac9-3c3d-11df-8235-9dab105e9c32</RequestId> |
72 | + </ResponseMetadata> |
73 | +</DeleteTopicResponse> |
74 | +` |
75 | + |
76 | +var TestListSubscriptionsXmlOK = ` |
77 | +<ListSubscriptionsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
78 | + <ListSubscriptionsResult> |
79 | + <Subscriptions> |
80 | + <member> |
81 | + <TopicArn>arn:aws:sns:us-east-1:698519295917:My-Topic</TopicArn> |
82 | + <Protocol>email</Protocol> |
83 | + <SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn> |
84 | + <Owner>123456789012</Owner> |
85 | + <Endpoint>example@amazon.com</Endpoint> |
86 | + </member> |
87 | + </Subscriptions> |
88 | + </ListSubscriptionsResult> |
89 | + <ResponseMetadata> |
90 | + <RequestId>384ac68d-3775-11df-8963-01868b7c937a</RequestId> |
91 | + </ResponseMetadata> |
92 | +</ListSubscriptionsResponse> |
93 | +` |
94 | + |
95 | +var TestGetTopicAttributesXmlOK = ` |
96 | +<GetTopicAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
97 | + <GetTopicAttributesResult> |
98 | + <Attributes> |
99 | + <entry> |
100 | + <key>Owner</key> |
101 | + <value>123456789012</value> |
102 | + </entry> |
103 | + <entry> |
104 | + <key>Policy</key> |
105 | + <value>{"Version":"2008-10-17","Id":"us-east-1/698519295917/test__default_policy_ID","Statement" : [{"Effect":"Allow","Sid":"us-east-1/698519295917/test__default_statement_ID","Principal" : {"AWS": "*"},"Action":["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic","SNS:Subscribe","SNS:ListSubscriptionsByTopic","SNS:Publish","SNS:Receive"],"Resource":"arn:aws:sns:us-east-1:698519295917:test","Condition" : {"StringLike" : {"AWS:SourceArn": "arn:aws:*:*:698519295917:*"}}}]}</value> |
106 | + </entry> |
107 | + <entry> |
108 | + <key>TopicArn</key> |
109 | + <value>arn:aws:sns:us-east-1:123456789012:My-Topic</value> |
110 | + </entry> |
111 | + </Attributes> |
112 | + </GetTopicAttributesResult> |
113 | + <ResponseMetadata> |
114 | + <RequestId>057f074c-33a7-11df-9540-99d0768312d3</RequestId> |
115 | + </ResponseMetadata> |
116 | +</GetTopicAttributesResponse> |
117 | +` |
118 | + |
119 | +var TestPublishXmlOK = ` |
120 | +<PublishResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
121 | + <PublishResult> |
122 | + <MessageId>94f20ce6-13c5-43a0-9a9e-ca52d816e90b</MessageId> |
123 | + </PublishResult> |
124 | + <ResponseMetadata> |
125 | + <RequestId>f187a3c1-376f-11df-8963-01868b7c937a</RequestId> |
126 | + </ResponseMetadata> |
127 | +</PublishResponse> |
128 | +` |
129 | + |
130 | +var TestSetTopicAttributesXmlOK = ` |
131 | +<SetTopicAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
132 | + <ResponseMetadata> |
133 | + <RequestId>a8763b99-33a7-11df-a9b7-05d48da6f042</RequestId> |
134 | + </ResponseMetadata> |
135 | +</SetTopicAttributesResponse> |
136 | +` |
137 | + |
138 | +var TestSubscribeXmlOK = ` |
139 | +<SubscribeResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
140 | + <SubscribeResult> |
141 | + <SubscriptionArn>pending confirmation</SubscriptionArn> |
142 | + </SubscribeResult> |
143 | + <ResponseMetadata> |
144 | + <RequestId>a169c740-3766-11df-8963-01868b7c937a</RequestId> |
145 | + </ResponseMetadata> |
146 | +</SubscribeResponse> |
147 | +` |
148 | + |
149 | +var TestUnsubscribeXmlOK = ` |
150 | +<UnsubscribeResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
151 | + <ResponseMetadata> |
152 | + <RequestId>18e0ac39-3776-11df-84c0-b93cc1666b84</RequestId> |
153 | + </ResponseMetadata> |
154 | +</UnsubscribeResponse> |
155 | +` |
156 | + |
157 | +var TestConfirmSubscriptionXmlOK = ` |
158 | +<ConfirmSubscriptionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
159 | + <ConfirmSubscriptionResult> |
160 | + <SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn> |
161 | + </ConfirmSubscriptionResult> |
162 | + <ResponseMetadata> |
163 | + <RequestId>7a50221f-3774-11df-a9b7-05d48da6f042</RequestId> |
164 | + </ResponseMetadata> |
165 | +</ConfirmSubscriptionResponse> |
166 | +` |
167 | + |
168 | +var TestAddPermissionXmlOK = ` |
169 | +<AddPermissionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
170 | + <ResponseMetadata> |
171 | + <RequestId>6a213e4e-33a8-11df-9540-99d0768312d3</RequestId> |
172 | + </ResponseMetadata> |
173 | +</AddPermissionResponse> |
174 | +` |
175 | + |
176 | +var TestRemovePermissionXmlOK = ` |
177 | +<RemovePermissionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
178 | + <ResponseMetadata> |
179 | + <RequestId>d170b150-33a8-11df-995a-2d6fbe836cc1</RequestId> |
180 | + </ResponseMetadata> |
181 | +</RemovePermissionResponse> |
182 | +` |
183 | + |
184 | +var TestListSubscriptionsByTopicXmlOK = ` |
185 | +<ListSubscriptionsByTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> |
186 | + <ListSubscriptionsByTopicResult> |
187 | + <Subscriptions> |
188 | + <member> |
189 | + <TopicArn>arn:aws:sns:us-east-1:123456789012:My-Topic</TopicArn> |
190 | + <Protocol>email</Protocol> |
191 | + <SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn> |
192 | + <Owner>123456789012</Owner> |
193 | + <Endpoint>example@amazon.com</Endpoint> |
194 | + </member> |
195 | + </Subscriptions> |
196 | + </ListSubscriptionsByTopicResult> |
197 | + <ResponseMetadata> |
198 | + <RequestId>b9275252-3774-11df-9540-99d0768312d3</RequestId> |
199 | + </ResponseMetadata> |
200 | +</ListSubscriptionsByTopicResponse> |
201 | +` |
202 | |
203 | === added file 'sign.go' |
204 | --- sign.go 1970-01-01 00:00:00 +0000 |
205 | +++ sign.go 2011-09-21 19:16:26 +0000 |
206 | @@ -0,0 +1,64 @@ |
207 | +package sns |
208 | + |
209 | +import ( |
210 | + "crypto/hmac" |
211 | + "encoding/base64" |
212 | + "launchpad.net/goamz/aws" |
213 | + "sort" |
214 | + "strings" |
215 | +) |
216 | + |
217 | +var b64 = base64.StdEncoding |
218 | +/* |
219 | +func sign(auth aws.Auth, method, path string, params url.Values, headers http.Header) { |
220 | + var host string |
221 | + for k, v := range headers { |
222 | + k = strings.ToLower(k) |
223 | + switch k { |
224 | + case "host": |
225 | + host = v[0] |
226 | + } |
227 | + } |
228 | + |
229 | + params["AWSAccessKeyId"] = []string{auth.AccessKey} |
230 | + params["SignatureVersion"] = []string{"2"} |
231 | + params["SignatureMethod"] = []string{"HmacSHA256"} |
232 | + |
233 | + var sarry []string |
234 | + for k, v := range params { |
235 | + sarry = append(sarry, aws.Encode(k) + "=" + aws.Encode(v[0])) |
236 | + } |
237 | + |
238 | + sort.StringSlice(sarry).Sort() |
239 | + joined := strings.Join(sarry, "&") |
240 | + |
241 | + payload := strings.Join([]string{method, host, "/", joined}, "\n") |
242 | + hash := hmac.NewSHA256([]byte(auth.SecretKey)) |
243 | + hash.Write([]byte(payload)) |
244 | + signature := make([]byte, b64.EncodedLen(hash.Size())) |
245 | + b64.Encode(signature, hash.Sum()) |
246 | + |
247 | + params["Signature"] = []string{"AWS " + string(signature)} |
248 | + println("Payload:", payload) |
249 | + println("Signature:", strings.Join(params["Signature"], "|")) |
250 | +}*/ |
251 | + |
252 | +func sign(auth aws.Auth, method, path string, params map[string]string, host string) { |
253 | + params["AWSAccessKeyId"] = auth.AccessKey |
254 | + params["SignatureVersion"] = "2" |
255 | + params["SignatureMethod"] = "HmacSHA256" |
256 | + |
257 | + var sarray []string |
258 | + for k, v := range params { |
259 | + sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(v)) |
260 | + } |
261 | + sort.StringSlice(sarray).Sort() |
262 | + joined := strings.Join(sarray, "&") |
263 | + payload := method + "\n" + host + "\n" + path + "\n" + joined |
264 | + hash := hmac.NewSHA256([]byte(auth.SecretKey)) |
265 | + hash.Write([]byte(payload)) |
266 | + signature := make([]byte, b64.EncodedLen(hash.Size())) |
267 | + b64.Encode(signature, hash.Sum()) |
268 | + |
269 | + params["Signature"] = string(signature) |
270 | +} |
271 | |
272 | === added file 'sns.go' |
273 | --- sns.go 1970-01-01 00:00:00 +0000 |
274 | +++ sns.go 2011-09-21 19:16:26 +0000 |
275 | @@ -0,0 +1,428 @@ |
276 | +// |
277 | +// goamz - Go packages to interact with the Amazon Web Services. |
278 | +// |
279 | +// https://wiki.ubuntu.com/goamz |
280 | +// |
281 | +// Copyright (c) 2011 Memeo Inc. |
282 | +// |
283 | +// Written by Prudhvi Krishna Surapaneni <me@prudhvi.net> |
284 | +// |
285 | +package sns |
286 | + |
287 | +import ( |
288 | + "launchpad.net/goamz/aws" |
289 | + "os" |
290 | + "http" |
291 | + "time" |
292 | + "url" |
293 | + "xml" |
294 | + "strconv" |
295 | +) |
296 | + |
297 | +// The SNS type encapsulates operation with an SNS region. |
298 | +type SNS struct { |
299 | + aws.Auth |
300 | + aws.Region |
301 | + private byte // Reserve the right of using private data. |
302 | +} |
303 | + |
304 | +type Topic struct { |
305 | + *SNS |
306 | + TopicArn string |
307 | +} |
308 | + |
309 | +func New(auth aws.Auth, region aws.Region) *SNS { |
310 | + return &SNS{auth, region, 0} |
311 | +} |
312 | + |
313 | +type Message struct { |
314 | + *SNS |
315 | + *Topic |
316 | + Message [8192]byte |
317 | + Subject string |
318 | +} |
319 | + |
320 | +type Subscription struct { |
321 | + Endpoint string |
322 | + Owner string |
323 | + Protocol string |
324 | + SubscriptionArn string |
325 | + TopicArn string |
326 | +} |
327 | + |
328 | +func (topic *Topic) Message(message [8192]byte, subject string) *Message { |
329 | + return &Message{topic.SNS, topic, message, subject} |
330 | +} |
331 | + |
332 | +type ResponseMetadata struct { |
333 | + RequestId string |
334 | + BoxUsage float64 |
335 | +} |
336 | + |
337 | +type ListTopicsResponse struct { |
338 | + Topics []Topic `xml:"ListTopicsResult>Topics>member"` |
339 | + NextToken string |
340 | + ResponseMetadata |
341 | +} |
342 | + |
343 | +type CreateTopicResponse struct { |
344 | + Topic Topic `xml:"CreateTopicResult>"` |
345 | + ResponseMetadata |
346 | +} |
347 | + |
348 | +type DeleteTopicResponse struct { |
349 | + ResponseMetadata |
350 | +} |
351 | + |
352 | +type ListSubscriptionsResponse struct { |
353 | + Subscriptions []Subscription `xml:"ListSubscriptionsResult>Subscriptions>member"` |
354 | + NextToken string |
355 | + ResponseMetadata |
356 | +} |
357 | + |
358 | +type AttributeEntry struct { |
359 | + Key, Value string |
360 | +} |
361 | + |
362 | +type GetTopicAttributesResponse struct { |
363 | + Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"` |
364 | + ResponseMetadata |
365 | +} |
366 | + |
367 | +func makeParams(action string) map[string]string { |
368 | + params := make(map[string]string) |
369 | + params["Action"] = action |
370 | + return params |
371 | +} |
372 | + |
373 | +// ListTopics |
374 | +// |
375 | +// See http://goo.gl/lfrMK for more details. |
376 | +func (sns *SNS) ListTopics(NextToken *string) (resp *ListTopicsResponse, err os.Error) { |
377 | + resp = &ListTopicsResponse{} |
378 | + params := makeParams("ListTopics") |
379 | + if NextToken != nil { |
380 | + params["NextToken"] = *NextToken |
381 | + } |
382 | + err = sns.query(nil, nil, params, resp) |
383 | + return |
384 | +} |
385 | + |
386 | +// CreateTopic |
387 | +// |
388 | +// See http://goo.gl/m9aAt for more details. |
389 | +func (sns *SNS) CreateTopic(Name string) (resp *CreateTopicResponse, err os.Error) { |
390 | + resp = &CreateTopicResponse{} |
391 | + params := makeParams("CreateTopic") |
392 | + params["Name"] = Name |
393 | + err = sns.query(nil, nil, params, resp) |
394 | + return |
395 | +} |
396 | + |
397 | +// DeleteTopic |
398 | +// |
399 | +// See http://goo.gl/OXNcY for more details. |
400 | +func (sns *SNS) DeleteTopic(topic Topic) (resp *DeleteTopicResponse, err os.Error) { |
401 | + resp = &DeleteTopicResponse{} |
402 | + params := makeParams("DeleteTopic") |
403 | + params["TopicArn"] = topic.TopicArn |
404 | + err = sns.query(nil, nil, params, resp) |
405 | + return |
406 | +} |
407 | + |
408 | +// Delete |
409 | +// |
410 | +// Helper function for deleting a topic |
411 | +func (topic *Topic) Delete() (resp *DeleteTopicResponse, err os.Error) { |
412 | + return topic.SNS.DeleteTopic(*topic) |
413 | +} |
414 | + |
415 | +// ListSubscriptions |
416 | +// |
417 | +// See http://goo.gl/k3aGn for more details. |
418 | +func (sns *SNS) ListSubscriptions(NextToken *string) (resp *ListSubscriptionsResponse, err os.Error) { |
419 | + resp = &ListSubscriptionsResponse{} |
420 | + params := makeParams("ListSubscriptions") |
421 | + if NextToken != nil { |
422 | + params["NextToken"] = *NextToken |
423 | + } |
424 | + err = sns.query(nil, nil, params, resp) |
425 | + return |
426 | +} |
427 | + |
428 | +// GetTopicAttributes |
429 | +// |
430 | +// See http://goo.gl/WXRoX for more details. |
431 | +func (sns *SNS) GetTopicAttributes(TopicArn string) (resp *GetTopicAttributesResponse, err os.Error) { |
432 | + resp = &GetTopicAttributesResponse{} |
433 | + params := makeParams("GetTopicAttributes") |
434 | + params["TopicArn"] = TopicArn |
435 | + err = sns.query(nil, nil, params, resp) |
436 | + return |
437 | +} |
438 | + |
439 | +type PublishOpt struct { |
440 | + Message string |
441 | + MessageStructure string |
442 | + Subject string |
443 | + TopicArn string |
444 | +} |
445 | + |
446 | +type PublishResponse struct { |
447 | + MessageId string `xml:"PublishResult>MessageId"` |
448 | + ResponseMetadata |
449 | +} |
450 | + |
451 | +// Publish |
452 | +// |
453 | +// See http://goo.gl/AY2D8 for more details. |
454 | +func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResponse, err os.Error) { |
455 | + resp = &PublishResponse{} |
456 | + params := makeParams("Publish") |
457 | + |
458 | + if options.Subject != "" { |
459 | + params["Subject"] = options.Subject |
460 | + } |
461 | + |
462 | + if options.MessageStructure != "" { |
463 | + params["MessageStructure"] = options.MessageStructure |
464 | + } |
465 | + |
466 | + if options.Message != "" { |
467 | + params["Message"] = options.Message |
468 | + } |
469 | + |
470 | + if options.TopicArn != "" { |
471 | + params["TopicArn"] = options.TopicArn |
472 | + } |
473 | + |
474 | + err = sns.query(nil, nil, params, resp) |
475 | + return |
476 | +} |
477 | + |
478 | +type SetTopicAttributesResponse struct { |
479 | + ResponseMetadata |
480 | +} |
481 | + |
482 | +// SetTopicAttributes |
483 | +// |
484 | +// See http://goo.gl/oVYW7 for more details. |
485 | +func (sns *SNS) SetTopicAttributes(AttributeName, AttributeValue, TopicArn string) (resp *SetTopicAttributesResponse, err os.Error) { |
486 | + resp = &SetTopicAttributesResponse{} |
487 | + params := makeParams("SetTopicAttributes") |
488 | + |
489 | + if AttributeName == "" || TopicArn == "" { |
490 | + return nil, os.NewError("Invalid Attribute Name or TopicArn") |
491 | + } |
492 | + |
493 | + params["AttributeName"] = AttributeName |
494 | + params["AttributeValue"] = AttributeValue |
495 | + params["TopicArn"] = TopicArn |
496 | + |
497 | + err = sns.query(nil, nil, params, resp) |
498 | + return |
499 | +} |
500 | + |
501 | +type SubscribeResponse struct { |
502 | + SubscriptionArn string `xml:"SubscribeResult>SubscriptionArn"` |
503 | + ResponseMetadata |
504 | +} |
505 | + |
506 | +// Subscribe |
507 | +// |
508 | +// See http://goo.gl/c3iGS for more details. |
509 | +func (sns *SNS) Subscribe(Endpoint, Protocol, TopicArn string) (resp *SubscribeResponse, err os.Error) { |
510 | + resp = &SubscribeResponse{} |
511 | + params := makeParams("Subscribe") |
512 | + |
513 | + params["Endpoint"] = Endpoint |
514 | + params["Protocol"] = Protocol |
515 | + params["TopicArn"] = TopicArn |
516 | + |
517 | + err = sns.query(nil, nil, params, resp) |
518 | + return |
519 | +} |
520 | + |
521 | +type UnsubscribeResponse struct { |
522 | + ResponseMetadata |
523 | +} |
524 | + |
525 | +// Unsubscribe |
526 | +// |
527 | +// See http://goo.gl/4l5Ge for more details. |
528 | +func (sns *SNS) Unsubscribe(SubscriptionArn string) (resp *UnsubscribeResponse, err os.Error) { |
529 | + resp = &UnsubscribeResponse{} |
530 | + params := makeParams("Unsubscribe") |
531 | + |
532 | + params["SubscriptionArn"] = SubscriptionArn |
533 | + |
534 | + err = sns.query(nil, nil, params, resp) |
535 | + return |
536 | +} |
537 | + |
538 | +type ConfirmSubscriptionResponse struct { |
539 | + SubscriptionArn string `xml:"ConfirmSubscriptionResult>SubscriptionArn"` |
540 | + ResponseMetadata |
541 | +} |
542 | + |
543 | +type ConfirmSubscriptionOpt struct { |
544 | + AuthenticateOnUnsubscribe string |
545 | + Token string |
546 | + TopicArn string |
547 | +} |
548 | + |
549 | +// ConfirmSubscription |
550 | +// |
551 | +// See http://goo.gl/3hXzH for more details. |
552 | +func (sns *SNS) ConfirmSubscription(options *ConfirmSubscriptionOpt) (resp *ConfirmSubscriptionResponse, err os.Error) { |
553 | + resp = &ConfirmSubscriptionResponse{} |
554 | + params := makeParams("ConfirmSubscription") |
555 | + |
556 | + if options.AuthenticateOnUnsubscribe != "" { |
557 | + params["AuthenticateOnUnsubscribe"] = options.AuthenticateOnUnsubscribe |
558 | + } |
559 | + |
560 | + params["Token"] = options.Token |
561 | + params["TopicArn"] = options.TopicArn |
562 | + |
563 | + err = sns.query(nil, nil, params, resp) |
564 | + return |
565 | +} |
566 | + |
567 | +type Permission struct { |
568 | + ActionName string |
569 | + AccountId string |
570 | +} |
571 | + |
572 | +type AddPermissionResponse struct { |
573 | + ResponseMetadata |
574 | +} |
575 | + |
576 | +// AddPermission |
577 | +// |
578 | +// See http://goo.gl/mbY4a for more details. |
579 | +func (sns *SNS) AddPermission(permissions []Permission, Label, TopicArn string) (resp *AddPermissionResponse, err os.Error) { |
580 | + resp = &AddPermissionResponse{} |
581 | + params := makeParams("AddPermission") |
582 | + |
583 | + for i, p := range permissions { |
584 | + params["AWSAccountId.member."+strconv.Itoa(i+1)] = p.AccountId |
585 | + params["ActionName.member."+strconv.Itoa(i+1)] = p.ActionName |
586 | + } |
587 | + |
588 | + params["Label"] = Label |
589 | + params["TopicArn"] = TopicArn |
590 | + |
591 | + err = sns.query(nil, nil, params, resp) |
592 | + return |
593 | +} |
594 | + |
595 | +type RemovePermissionResponse struct { |
596 | + ResponseMetadata |
597 | +} |
598 | + |
599 | +// RemovePermission |
600 | +// |
601 | +// See http://goo.gl/wGl5j for more details. |
602 | +func (sns *SNS) RemovePermission(Label, TopicArn string) (resp *RemovePermissionResponse, err os.Error) { |
603 | + resp = &RemovePermissionResponse{} |
604 | + params := makeParams("RemovePermission") |
605 | + |
606 | + params["Label"] = Label |
607 | + params["TopicArn"] = TopicArn |
608 | + |
609 | + err = sns.query(nil, nil, params, resp) |
610 | + return |
611 | +} |
612 | + |
613 | +type ListSubscriptionByTopicResponse struct { |
614 | + Subscriptions []Subscription `xml:"ListSubscriptionsByTopicResult>Subscriptions>member"` |
615 | + ResponseMetadata |
616 | +} |
617 | + |
618 | +type ListSubscriptionByTopicOpt struct { |
619 | + NextToken string |
620 | + TopicArn string |
621 | +} |
622 | + |
623 | +// ListSubscriptionByTopic |
624 | +// |
625 | +// See http://goo.gl/LaVcC for more details. |
626 | +func (sns *SNS) ListSubscriptionByTopic(options *ListSubscriptionByTopicOpt) (resp *ListSubscriptionByTopicResponse, err os.Error) { |
627 | + resp = &ListSubscriptionByTopicResponse{} |
628 | + params := makeParams("ListSbubscriptionByTopic") |
629 | + |
630 | + if options.NextToken != "" { |
631 | + params["NextToken"] = options.NextToken |
632 | + } |
633 | + |
634 | + params["TopicArn"] = options.TopicArn |
635 | + |
636 | + err = sns.query(nil, nil, params, resp) |
637 | + return |
638 | +} |
639 | + |
640 | +type Error struct { |
641 | + StatusCode int |
642 | + Code string |
643 | + Message string |
644 | + RequestId string |
645 | +} |
646 | + |
647 | +func (err *Error) String() string { |
648 | + return err.Message |
649 | +} |
650 | + |
651 | +type xmlErrors struct { |
652 | + RequestId string |
653 | + Errors []Error `xml:"Errors>Error"` |
654 | +} |
655 | + |
656 | +func (sns *SNS) query(topic *Topic, message *Message, params map[string]string, resp interface{}) os.Error { |
657 | + params["Timestamp"] = time.UTC().Format(time.RFC3339) |
658 | + url_, err := url.Parse(sns.Region.SNSEndpoint) |
659 | + if err != nil { |
660 | + return err |
661 | + } |
662 | + |
663 | + sign(sns.Auth, "GET", "/", params, url_.Host) |
664 | + url_.RawQuery = multimap(params).Encode() |
665 | + r, err := http.Get(url_.String()) |
666 | + if err != nil { |
667 | + return err |
668 | + } |
669 | + defer r.Body.Close() |
670 | + |
671 | + //dump, _ := http.DumpResponse(r, true) |
672 | + //println("DUMP:\n", string(dump)) |
673 | + //return nil |
674 | + |
675 | + if r.StatusCode != 200 { |
676 | + return buildError(r) |
677 | + } |
678 | + err = xml.Unmarshal(r.Body, resp) |
679 | + return err |
680 | +} |
681 | + |
682 | +func buildError(r *http.Response) os.Error { |
683 | + errors := xmlErrors{} |
684 | + xml.Unmarshal(r.Body, &errors) |
685 | + var err Error |
686 | + if len(errors.Errors) > 0 { |
687 | + err = errors.Errors[0] |
688 | + } |
689 | + err.RequestId = errors.RequestId |
690 | + err.StatusCode = r.StatusCode |
691 | + if err.Message == "" { |
692 | + err.Message = r.Status |
693 | + } |
694 | + return &err |
695 | +} |
696 | + |
697 | +func multimap(p map[string]string) url.Values { |
698 | + q := make(url.Values, len(p)) |
699 | + for k, v := range p { |
700 | + q[k] = []string{v} |
701 | + } |
702 | + return q |
703 | +} |
704 | |
705 | === added file 'sns_test.go' |
706 | --- sns_test.go 1970-01-01 00:00:00 +0000 |
707 | +++ sns_test.go 2011-09-21 19:16:26 +0000 |
708 | @@ -0,0 +1,230 @@ |
709 | +package sns_test |
710 | + |
711 | +import ( |
712 | + "launchpad.net/gocheck" |
713 | + "launchpad.net/goamz/aws" |
714 | + "launchpad.net/goamz/sns" |
715 | +) |
716 | + |
717 | +var _ = gocheck.Suite(&S{}) |
718 | + |
719 | +type S struct { |
720 | + HTTPSuite |
721 | + sns *sns.SNS |
722 | +} |
723 | + |
724 | +func (s *S) SetUpSuite(c *gocheck.C) { |
725 | + s.HTTPSuite.SetUpSuite(c) |
726 | + auth := aws.Auth{"abc", "123"} |
727 | + s.sns = sns.New(auth, aws.Region{SNSEndpoint: testServer.URL}) |
728 | +} |
729 | + |
730 | +func (s *S) TestListTopicsOK(c *gocheck.C) { |
731 | + testServer.PrepareResponse(200, nil, TestListTopicsXmlOK) |
732 | + |
733 | + resp, err := s.sns.ListTopics(nil) |
734 | + req := testServer.WaitRequest() |
735 | + |
736 | + c.Assert(req.Method, gocheck.Equals, "GET") |
737 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
738 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
739 | + |
740 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "bd10b26c-e30e-11e0-ba29-93c3aca2f103") |
741 | + c.Assert(err, gocheck.IsNil) |
742 | +} |
743 | + |
744 | +func (s *S) TestCreateTopic(c *gocheck.C) { |
745 | + testServer.PrepareResponse(200, nil, TestCreateTopicXmlOK) |
746 | + |
747 | + resp, err := s.sns.CreateTopic("My-Topic") |
748 | + req := testServer.WaitRequest() |
749 | + |
750 | + c.Assert(req.Method, gocheck.Equals, "GET") |
751 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
752 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
753 | + |
754 | + c.Assert(resp.Topic.TopicArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic") |
755 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "a8dec8b3-33a4-11df-8963-01868b7c937a") |
756 | + c.Assert(err, gocheck.IsNil) |
757 | +} |
758 | + |
759 | +func (s *S) TestDeleteTopic(c *gocheck.C) { |
760 | + testServer.PrepareResponse(200, nil, TestDeleteTopicXmlOK) |
761 | + |
762 | + t := sns.Topic{nil, "arn:aws:sns:us-east-1:123456789012:My-Topic"} |
763 | + resp, err := s.sns.DeleteTopic(t) |
764 | + req := testServer.WaitRequest() |
765 | + |
766 | + c.Assert(req.Method, gocheck.Equals, "GET") |
767 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
768 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
769 | + |
770 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "f3aa9ac9-3c3d-11df-8235-9dab105e9c32") |
771 | + c.Assert(err, gocheck.IsNil) |
772 | +} |
773 | + |
774 | +func (s *S) TestListSubscriptions(c *gocheck.C) { |
775 | + testServer.PrepareResponse(200, nil, TestListSubscriptionsXmlOK) |
776 | + |
777 | + resp, err := s.sns.ListSubscriptions(nil) |
778 | + req := testServer.WaitRequest() |
779 | + |
780 | + c.Assert(req.Method, gocheck.Equals, "GET") |
781 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
782 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
783 | + |
784 | + c.Assert(len(resp.Subscriptions), gocheck.Not(gocheck.Equals), 0) |
785 | + c.Assert(resp.Subscriptions[0].Protocol, gocheck.Equals, "email") |
786 | + c.Assert(resp.Subscriptions[0].Endpoint, gocheck.Equals, "example@amazon.com") |
787 | + c.Assert(resp.Subscriptions[0].SubscriptionArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca") |
788 | + c.Assert(resp.Subscriptions[0].TopicArn, gocheck.Equals, "arn:aws:sns:us-east-1:698519295917:My-Topic") |
789 | + c.Assert(resp.Subscriptions[0].Owner, gocheck.Equals, "123456789012") |
790 | + c.Assert(err, gocheck.IsNil) |
791 | +} |
792 | + |
793 | +func (s *S) TestGetTopicAttributes(c *gocheck.C) { |
794 | + testServer.PrepareResponse(200, nil, TestGetTopicAttributesXmlOK) |
795 | + |
796 | + resp, err := s.sns.GetTopicAttributes("arn:aws:sns:us-east-1:123456789012:My-Topic") |
797 | + req := testServer.WaitRequest() |
798 | + |
799 | + c.Assert(req.Method, gocheck.Equals, "GET") |
800 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
801 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
802 | + |
803 | + c.Assert(len(resp.Attributes), gocheck.Not(gocheck.Equals), 0) |
804 | + c.Assert(resp.Attributes[0].Key, gocheck.Equals, "Owner") |
805 | + c.Assert(resp.Attributes[0].Value, gocheck.Equals, "123456789012") |
806 | + c.Assert(resp.Attributes[1].Key, gocheck.Equals, "Policy") |
807 | + c.Assert(resp.Attributes[1].Value, gocheck.Equals, `{"Version":"2008-10-17","Id":"us-east-1/698519295917/test__default_policy_ID","Statement" : [{"Effect":"Allow","Sid":"us-east-1/698519295917/test__default_statement_ID","Principal" : {"AWS": "*"},"Action":["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic","SNS:Subscribe","SNS:ListSubscriptionsByTopic","SNS:Publish","SNS:Receive"],"Resource":"arn:aws:sns:us-east-1:698519295917:test","Condition" : {"StringLike" : {"AWS:SourceArn": "arn:aws:*:*:698519295917:*"}}}]}`) |
808 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "057f074c-33a7-11df-9540-99d0768312d3") |
809 | + c.Assert(err, gocheck.IsNil) |
810 | +} |
811 | + |
812 | +func (s *S) TestPublish(c *gocheck.C) { |
813 | + testServer.PrepareResponse(200, nil, TestPublishXmlOK) |
814 | + |
815 | + pubOpt := &sns.PublishOpt{"foobar","","subject","arn:aws:sns:us-east-1:123456789012:My-Topic"} |
816 | + resp, err := s.sns.Publish(pubOpt) |
817 | + req := testServer.WaitRequest() |
818 | + |
819 | + c.Assert(req.Method, gocheck.Equals, "GET") |
820 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
821 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
822 | + |
823 | + c.Assert(resp.MessageId, gocheck.Equals, "94f20ce6-13c5-43a0-9a9e-ca52d816e90b") |
824 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "f187a3c1-376f-11df-8963-01868b7c937a") |
825 | + c.Assert(err, gocheck.IsNil) |
826 | +} |
827 | + |
828 | +func (s *S) TestSetTopicAttributes(c *gocheck.C) { |
829 | + testServer.PrepareResponse(200, nil, TestSetTopicAttributesXmlOK) |
830 | + |
831 | + resp, err := s.sns.SetTopicAttributes("DisplayName", "MyTopicName", "arn:aws:sns:us-east-1:123456789012:My-Topic") |
832 | + req := testServer.WaitRequest() |
833 | + |
834 | + c.Assert(req.Method, gocheck.Equals, "GET") |
835 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
836 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
837 | + |
838 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "a8763b99-33a7-11df-a9b7-05d48da6f042") |
839 | + c.Assert(err, gocheck.IsNil) |
840 | +} |
841 | + |
842 | +func (s *S) TestSubscribe(c *gocheck.C) { |
843 | + testServer.PrepareResponse(200, nil, TestSubscribeXmlOK) |
844 | + |
845 | + resp, err := s.sns.Subscribe("example@amazon.com", "email", "arn:aws:sns:us-east-1:123456789012:My-Topic") |
846 | + req := testServer.WaitRequest() |
847 | + |
848 | + c.Assert(req.Method, gocheck.Equals, "GET") |
849 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
850 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
851 | + |
852 | + c.Assert(resp.SubscriptionArn, gocheck.Equals, "pending confirmation") |
853 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "a169c740-3766-11df-8963-01868b7c937a") |
854 | + c.Assert(err, gocheck.IsNil) |
855 | +} |
856 | + |
857 | +func (s *S) TestUnsubscribe(c *gocheck.C) { |
858 | + testServer.PrepareResponse(200, nil, TestUnsubscribeXmlOK) |
859 | + |
860 | + resp, err := s.sns.Unsubscribe("arn:aws:sns:us-east-1:123456789012:My-Topic:a169c740-3766-11df-8963-01868b7c937a") |
861 | + req := testServer.WaitRequest() |
862 | + |
863 | + c.Assert(req.Method, gocheck.Equals, "GET") |
864 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
865 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
866 | + |
867 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "18e0ac39-3776-11df-84c0-b93cc1666b84") |
868 | + c.Assert(err, gocheck.IsNil) |
869 | +} |
870 | + |
871 | +func (s *S) TestConfirmSubscription(c *gocheck.C) { |
872 | + testServer.PrepareResponse(200, nil, TestConfirmSubscriptionXmlOK) |
873 | + |
874 | + opt := &sns.ConfirmSubscriptionOpt{"", "51b2ff3edb475b7d91550e0ab6edf0c1de2a34e6ebaf6", "arn:aws:sns:us-east-1:123456789012:My-Topic"} |
875 | + resp, err := s.sns.ConfirmSubscription(opt) |
876 | + req := testServer.WaitRequest() |
877 | + |
878 | + c.Assert(req.Method, gocheck.Equals, "GET") |
879 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
880 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
881 | + |
882 | + c.Assert(resp.SubscriptionArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca") |
883 | + c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "7a50221f-3774-11df-a9b7-05d48da6f042") |
884 | + c.Assert(err, gocheck.IsNil) |
885 | +} |
886 | + |
887 | +func (s *S) TestAddPermission(c *gocheck.C) { |
888 | + testServer.PrepareResponse(200, nil, TestAddPermissionXmlOK) |
889 | + perm := make([]sns.Permission,2) |
890 | + perm[0].ActionName = "Publish" |
891 | + perm[1].ActionName = "GetTopicAttributes" |
892 | + perm[0].AccountId = "987654321000" |
893 | + perm[1].AccountId = "876543210000" |
894 | + |
895 | + resp, err := s.sns.AddPermission(perm, "NewPermission", "arn:aws:sns:us-east-1:123456789012:My-Topic") |
896 | + req := testServer.WaitRequest() |
897 | + |
898 | + c.Assert(req.Method, gocheck.Equals, "GET") |
899 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
900 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
901 | + |
902 | + c.Assert(resp.RequestId, gocheck.Equals, "6a213e4e-33a8-11df-9540-99d0768312d3") |
903 | + c.Assert(err, gocheck.IsNil) |
904 | +} |
905 | + |
906 | +func (s *S) TestRemovePermission(c *gocheck.C) { |
907 | + testServer.PrepareResponse(200, nil, TestRemovePermissionXmlOK) |
908 | + |
909 | + resp, err := s.sns.RemovePermission("NewPermission", "arn:aws:sns:us-east-1:123456789012:My-Topic") |
910 | + req := testServer.WaitRequest() |
911 | + |
912 | + c.Assert(req.Method, gocheck.Equals, "GET") |
913 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
914 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
915 | + |
916 | + c.Assert(resp.RequestId, gocheck.Equals, "d170b150-33a8-11df-995a-2d6fbe836cc1") |
917 | + c.Assert(err, gocheck.IsNil) |
918 | +} |
919 | + |
920 | +func (s *S) TestListSubscriptionByTopic(c *gocheck.C) { |
921 | + testServer.PrepareResponse(200, nil, TestListSubscriptionsByTopicXmlOK) |
922 | + |
923 | + opt := &sns.ListSubscriptionByTopicOpt{"", "arn:aws:sns:us-east-1:123456789012:My-Topic"} |
924 | + resp, err := s.sns.ListSubscriptionByTopic(opt) |
925 | + req := testServer.WaitRequest() |
926 | + |
927 | + c.Assert(req.Method, gocheck.Equals, "GET") |
928 | + c.Assert(req.URL.Path, gocheck.Equals, "/") |
929 | + c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "") |
930 | + |
931 | + c.Assert(len(resp.Subscriptions), gocheck.Not(gocheck.Equals), 0) |
932 | + c.Assert(resp.Subscriptions[0].TopicArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic") |
933 | + c.Assert(resp.Subscriptions[0].SubscriptionArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca") |
934 | + c.Assert(resp.Subscriptions[0].Owner, gocheck.Equals, "123456789012") |
935 | + c.Assert(resp.Subscriptions[0].Endpoint, gocheck.Equals, "example@amazon.com") |
936 | + c.Assert(resp.Subscriptions[0].Protocol, gocheck.Equals, "email") |
937 | + c.Assert(err, gocheck.IsNil) |
938 | +} |
939 | |
940 | === added file 'suite_test.go' |
941 | --- suite_test.go 1970-01-01 00:00:00 +0000 |
942 | +++ suite_test.go 2011-09-21 19:16:26 +0000 |
943 | @@ -0,0 +1,139 @@ |
944 | +package sns_test |
945 | + |
946 | +import ( |
947 | + "flag" |
948 | + "fmt" |
949 | + . "launchpad.net/gocheck" |
950 | + "http" |
951 | + "launchpad.net/goamz/aws" |
952 | + "os" |
953 | + "testing" |
954 | + "time" |
955 | + "url" |
956 | +) |
957 | + |
958 | +func Test(t *testing.T) { |
959 | + TestingT(t) |
960 | +} |
961 | + |
962 | +var integration = flag.Bool("i", false, "Enable integration tests") |
963 | + |
964 | +type SuiteI struct { |
965 | + auth aws.Auth |
966 | +} |
967 | + |
968 | +func (s *SuiteI) SetUpSuite(c *C) { |
969 | + if !*integration { |
970 | + c.Skip("Integration tests not enabled (-i flag)") |
971 | + } |
972 | + auth, err := aws.EnvAuth() |
973 | + if err != nil { |
974 | + c.Fatal(err.String()) |
975 | + } |
976 | + s.auth = auth |
977 | +} |
978 | + |
979 | +type HTTPSuite struct{} |
980 | + |
981 | +var testServer = NewTestHTTPServer("http://localhost:4444", 5e9) |
982 | + |
983 | +func (s *HTTPSuite) SetUpSuite(c *C) { |
984 | + testServer.Start() |
985 | +} |
986 | + |
987 | +func (s *HTTPSuite) TearDownTest(c *C) { |
988 | + testServer.FlushRequests() |
989 | +} |
990 | + |
991 | +type TestHTTPServer struct { |
992 | + URL string |
993 | + Timeout int64 |
994 | + started bool |
995 | + request chan *http.Request |
996 | + response chan *testResponse |
997 | + pending chan bool |
998 | +} |
999 | + |
1000 | +type testResponse struct { |
1001 | + Status int |
1002 | + Headers map[string]string |
1003 | + Body string |
1004 | +} |
1005 | + |
1006 | +func NewTestHTTPServer(url string, timeout int64) *TestHTTPServer { |
1007 | + return &TestHTTPServer{URL: url, Timeout: timeout} |
1008 | +} |
1009 | + |
1010 | +func (s *TestHTTPServer) Start() { |
1011 | + if s.started { |
1012 | + return |
1013 | + } |
1014 | + s.started = true |
1015 | + |
1016 | + s.request = make(chan *http.Request, 64) |
1017 | + s.response = make(chan *testResponse, 64) |
1018 | + s.pending = make(chan bool, 64) |
1019 | + |
1020 | + url, _ := url.Parse(s.URL) |
1021 | + go http.ListenAndServe(url.Host, s) |
1022 | + |
1023 | + s.PrepareResponse(202, nil, "Nothing.") |
1024 | + for { |
1025 | + // Wait for it to be up. |
1026 | + resp, err := http.Get(s.URL) |
1027 | + if err == nil && resp.StatusCode == 202 { |
1028 | + break |
1029 | + } |
1030 | + fmt.Fprintf(os.Stderr, "\nWaiting for fake server to be up... ") |
1031 | + time.Sleep(1e8) |
1032 | + } |
1033 | + fmt.Fprintf(os.Stderr, "done\n\n") |
1034 | + s.WaitRequest() // Consume dummy request. |
1035 | +} |
1036 | + |
1037 | +// FlushRequests discards requests which were not yet consumed by WaitRequest. |
1038 | +func (s *TestHTTPServer) FlushRequests() { |
1039 | + for { |
1040 | + select { |
1041 | + case <-s.request: |
1042 | + default: |
1043 | + return |
1044 | + } |
1045 | + } |
1046 | +} |
1047 | + |
1048 | +func (s *TestHTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
1049 | + s.request <- req |
1050 | + var resp *testResponse |
1051 | + select { |
1052 | + case resp = <-s.response: |
1053 | + case <-time.After(s.Timeout): |
1054 | + fmt.Fprintf(os.Stderr, "ERROR: Timeout waiting for test to provide response\n") |
1055 | + resp = &testResponse{500, nil, ""} |
1056 | + } |
1057 | + if resp.Headers != nil { |
1058 | + h := w.Header() |
1059 | + for k, v := range resp.Headers { |
1060 | + h.Set(k, v) |
1061 | + } |
1062 | + } |
1063 | + if resp.Status != 0 { |
1064 | + w.WriteHeader(resp.Status) |
1065 | + } |
1066 | + w.Write([]byte(resp.Body)) |
1067 | +} |
1068 | + |
1069 | +func (s *TestHTTPServer) WaitRequest() *http.Request { |
1070 | + select { |
1071 | + case req := <-s.request: |
1072 | + req.ParseForm() |
1073 | + return req |
1074 | + case <-time.After(s.Timeout): |
1075 | + panic("Timeout waiting for goamz request") |
1076 | + } |
1077 | + panic("unreached") |
1078 | +} |
1079 | + |
1080 | +func (s *TestHTTPServer) PrepareResponse(status int, headers map[string]string, body string) { |
1081 | + s.response <- &testResponse{status, headers, body} |
1082 | +} |
1083 | |
1084 | === renamed file 'suite_test.go' => 'suite_test.go.moved' |
This looks fantastic overall.
I have several points that are mainly related to conventions and idiomatic
coding for Go, but nothing really major or hard.
Please push it again and ping me once you have these ready, and let me
know if you have any questions on these.
[1]
> type Message struct {
(...)
> Message [8192]byte
(...)
> func (topic *Topic) Message(message [8192]byte, subject string) *Message {
> return &Message{topic.SNS, topic, message, subject}
> }
Both of these should use []byte rather than [8192]byte, and the field should
be named something like Payload instead of Message (Message.Message isn't
nice).
That said, why do we need the Message struct at all? It doesn't look
like it's used anywhere, so we can just drop it.
[2]
> type PublishOpt struct {
> (...)
> func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResponse, err os.Error) {
This is one of the most interesting methods in the interface, and the function signature
feels a bit unfortunate to be using frequently. Let's organize it like this:
func (sns *SNS) Publish(message []byte, topic string) message []byte, topic string, subject string) e(message []byte, topic string, subject string, structure string)
func (sns *SNS) PublishSubject(
func (sns *SNS) PublishStructur
The first two are actually backed by the last one.
[3]
> type Topic struct {
> *SNS
> TopicArn string
> }
s/TopicArn/ARN/
I've made the mistake of duplicating the type name in other places, but I want
to get it fixed at some point. The structure name is already Topic, so there's
no need to put Topic in the field name as well.
Also, let's please do s/Arn/ARN/ in the rest of the code in the Go side. This
is an acronym, so should be capitalized (like SNS itself).
[4]
> type Subscription struct {
> Endpoint string
> Owner string
> Protocol string
> SubscriptionArn string
> TopicArn string
> }
Similarly, s/SubscriptionA rn/ARN/ , and s/TopicArn/ TopicARN/ .
[5]
> type AttributeEntry struct { tesResponse struct { ttributesResult >Attributes> entry"`
> Key, Value string
> }
>
> type GetTopicAttribu
> Attributes []AttributeEntry `xml:"GetTopicA
> ResponseMetadata
> }
Let's please try to avoid such long names:
s/Attributes/Attrs/
s/Response/Resp/
across the whole code, besides the necessary strings to make
Amazon happy, and ResponseMetadata which feels like a convenient
way to parse that recurring blob of data.
[6]
> type GetTopicAttribu tesResponse struct { ttributesResult >Attributes> entry"`
> Attributes []AttributeEntry `xml:"GetTopicA
Then, the Attrs field in that type is actually a map, and users will be
much happier to handle it as such in the Go side. Define an internal struct
so that you can parse the XML out, and then internally translate it into
the following:
type GetTopicAttrsResp struct { adata
Attrs map[string]string
ResponseMet
}
[7]
> type SetTopicAttribu tesResponse struct { nResponse struct {
> ResponseMetadata
> }
>
> type DeleteTopicResponse struct {
> ResponseMetadata
> }
> type RemovePermissio
> ResponseMetadata
> }
(...)
There are ...