Merge lp:~jtv/gomaasapi/single-wrapper-class into lp:gomaasapi

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: 23
Merged at revision: 19
Proposed branch: lp:~jtv/gomaasapi/single-wrapper-class
Merge into: lp:gomaasapi
Diff against target: 1106 lines (+312/-326)
9 files modified
client.go (+16/-2)
example/live_example.go (+1/-3)
jsonobject.go (+58/-129)
jsonobject_test.go (+70/-38)
maas.go (+2/-2)
maasobject.go (+93/-87)
maasobject_test.go (+29/-31)
testservice.go (+34/-28)
testservice_test.go (+9/-6)
To merge this branch: bzr merge lp:~jtv/gomaasapi/single-wrapper-class
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+147643@code.launchpad.net

Commit message

Rearrange JSONObject so its conversions are all in a single class. Prepares for supporting non-JSON data. Also, document more functions.

Description of the change

After this, the JSONObject you get back from a result will also store its original raw HTTP response as []byte. You can then query that root JSONObject, in addition to the types you already get (string, number, array, object, MAAS object, bool), as raw data.

The interfaces have become classes. I also added some documentation along the way, in some cases because things weren't clear to myself and so I thought the next reader would appreciate a note.

A large portion of the diff (which is ginormous, sorry) is accounted for by removing casts between Go types and the jsonX types that formerly wrapped them. We can't have wrapper types that are direct aliases of the contained types any more, since we'll need to store some extra data in JSONObject.

Because there is now just a single wrapper type, it's no longer just MAASObject that knows its Client. Every JSONObject now does. A MAASObject is only constructed on the fly, when you ask for one. Call GetMAASObject() twice on the same JSONObject and you'll get two separate instances (with identical contents of course). Which is probably safer, although if you were modifying MAASObjects in place you may have been asking for trouble anyway.

There is one unfortunate API change: a JSONObject itself can no longer be nil; it can just wrap a nil value. So before using a JSONObject, call IsNil() on it now, instead of comparing it to nil. I hope it's early enough in this API's lifetime that such changes are still feasible.

Jeroen

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

Thanks a lot for fixing this (and adding a handful of comment on the existing code). Looks good to me but you need to fix live_example.go (see [5]).

[0]

> I hope it's early enough in this API's lifetime that such changes are still feasible.

I really think it's fine, nothing has been released yet.

[1]

(+303/-323) 8 files modified

A huge diff indeed but this kind of stats looks good to me.

[2]

186 - msg := fmt.Sprintf("Requested %v, got %v.", wantedType, obj.Type())
187 + msg := fmt.Sprintf("Requested %v, got %T.", wantedType, obj.value)

Don't you think that the message here will be a bit confusing? I'd change that to
msg := fmt.Sprintf("Requested type %v, got object %T.", wantedType, obj.value)

[3]

273 +func (obj JSONObject) GetString() (value string, err error) {
[...]
281 +func (obj JSONObject) GetFloat64() (value float64, err error) {
etc.

I think it's more idiomatic to use pointers as method receivers:
func (obj *JSONObject) GetString() (value string, err error) {
func (obj *JSONObject) GetFloat64() (value float64, err error) {
etc.

[3]

359 + _ = JSONObject(arr[0])
and
382 + _ = JSONObject(mp["key"])

I'm a bit confused here, what is this for exactly and why are you simply ignoring the return value?

[4]

+// a JSONObject, so if an API call returns a list of objects "l," you first

I think you want to write 'a list of objects "l", you first' here.

Same kind of typo here:
755 +// in "params." It returns the object's new value as received from the API.
and here:
768 +// "params." It returns the object's new value as received from the API.

[5]

There is a tiny problem in example/live_example.go now, with this fix, it works again: http://paste.ubuntu.com/1636327/

[6]

1021 +func (obj JSONObject) MarshalJSON() ([]byte, error) {
and
1028 +func (obj MAASObject) MarshalJSON() ([]byte, error) {

I think it's worth adding a little comment here to explain that this is to implement the Marshaler interface from the std lib.

review: Approve
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (4.3 KiB)

Relieved to see that you got through the review and that it's not too bad! My apologies again for producing such a big branch.

> 186 - msg := fmt.Sprintf("Requested %v, got %v.", wantedType,
> obj.Type())
> 187 + msg := fmt.Sprintf("Requested %v, got %T.", wantedType,
> obj.value)
>
> Don't you think that the message here will be a bit confusing? I'd change
> that to
> msg := fmt.Sprintf("Requested type %v, got object %T.", wantedType, obj.value)

The %T prints the value's type, not its contents. The code is asymmetric, in that I'm printing a value (%v) for the wanted type but a type (%T) for the actual type; but that's because the parameters are respectively a string containing the requested type, and an example of the actual type. So the message that comes out will be symmetric: "Requested bool, got string."

> 273 +func (obj JSONObject) GetString() (value string, err error) {
> [...]
> 281 +func (obj JSONObject) GetFloat64() (value float64, err error) {
> etc.
>
> I think it's more idiomatic to use pointers as method receivers:
> func (obj *JSONObject) GetString() (value string, err error) {
> func (obj *JSONObject) GetFloat64() (value float64, err error) {
> etc.

I don't think it's particularly idiomatic, or the language wouldn't give us this unusual choice in the first place. It's just one of those make-work decisions the language forces on teams, and which seemed like good ideas in the days when code was usually “owned” by one programmer. It can lead to endless discussion, and I wonder if that is what the Go team have in mind when they say they wanted to make programming fun again... The gift that keeps on giving!

JSONObject is small and meant to be used in an immutable fashion, in which case I think not having the pointer is actually clearer. It rules out most of the worries about which change might affect what instances.

If JSONObject were an interface and the objects had, or might become, mutable operations then it would be different: in that case I think only pointers make sense as receiver types.

And there you have it: three different criteria driving the same decision — mutability, copying cost, and interfaces. The situation pretty much rules out full agreement in everyday situations. :(

> 359 + _ = JSONObject(arr[0])
> and
> 382 + _ = JSONObject(mp["key"])
>
> I'm a bit confused here, what is this for exactly and why are you simply
> ignoring the return value?

Well spotted. That was meant to be a type assertion. It has _some_ power to do that in its current form, but it's not very effective. I changed it to:

    var _ JSONObject = arr[0]

(mutatis mutandi of course).

> +// a JSONObject, so if an API call returns a list of objects "l," you first
>
> I think you want to write 'a list of objects "l", you first' here.
>
> Same kind of typo here:
> 755 +// in "params." It returns the object's new value as received from
> the API.
> and here:
> 768 +// "params." It returns the object's new value as received from the
> API.

Not a typo. Typography. LaTeX, which is a great school for these things, taught me to move a full stop or comma insi...

Read more...

Revision history for this message
MAAS Lander (maas-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Revision history for this message
Raphaël Badin (rvb) wrote :

> The %T prints the value's type, not its contents. The code is asymmetric, in that I'm printing a value (%v) for the
> wanted type but a type (%T) for the actual type; but that's because the parameters are respectively a string
> containing the requested type, and an example of the actual type. So the message that comes out will be symmetric:
> "Requested bool, got string."

Ah right, I missed that, thanks for the clarification.

> Not a typo. Typography. LaTeX, which is a great school for these things, taught me to move a full stop or comma
> inside the quotes. I do sometimes break that rule when talking about code or other literal strings where it may
> lead to confusion.

> I realize this may be controversial. It's been a while since I last read 1984, but as I recall Winston Smith, after
> the régime breaks him, gets put on a committee to argue this very point. That book was written about 65 years ago,
> so I'm fairly confident without looking that there has been no sudden consensus since that overrules the LaTeX
> rule. :-)

Hum, I did not know about that… and last time I used LaTeX is was less that 3 years ago… I must say this really looks ugly, I'll need to verify this…

> Speaking of controversial commas: I first read this as “There is a tiny problem in example/live_example.go now,
> with this fix. It works again”! That's the problem with using commas to separate sentences: it leaves room for
> ambiguity.

Yeah, sorry for the vague sentence.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'client.go'
2--- client.go 2013-02-05 14:27:56 +0000
3+++ client.go 2013-02-12 05:49:22 +0000
4@@ -19,6 +19,7 @@
5 Signer OAuthSigner
6 }
7
8+// dispatchRequest sends a request to the server, and interprets the response.
9 func (client Client) dispatchRequest(request *http.Request) ([]byte, error) {
10 client.Signer.OAuthSign(request)
11 httpClient := http.Client{}
12@@ -36,10 +37,16 @@
13 return body, nil
14 }
15
16+// GetURL returns the URL to a given resource on the API, based on its URI.
17+// The resource URI may be absolute or relative; either way the result is a
18+// full absolute URL including the network part.
19 func (client Client) GetURL(uri *url.URL) *url.URL {
20 return client.BaseURL.ResolveReference(uri)
21 }
22
23+// Get performs an HTTP "GET" to the API. This may be either an API method
24+// invocation (if you pass its name in "operation") or plain resource
25+// retrieval (if you leave "operation" blank).
26 func (client Client) Get(uri *url.URL, operation string, parameters url.Values) ([]byte, error) {
27 opParameter := parameters.Get("op")
28 if opParameter != "" {
29@@ -58,7 +65,8 @@
30 return client.dispatchRequest(request)
31 }
32
33-// nonIdempotentRequest is a utility method to issue a PUT or a POST request.
34+// nonIdempotentRequest implements the common functionality of PUT and POST
35+// requests (but not GET or DELETE requests).
36 func (client Client) nonIdempotentRequest(method string, uri *url.URL, parameters url.Values) ([]byte, error) {
37 url := client.GetURL(uri)
38 request, err := http.NewRequest(method, url.String(), strings.NewReader(string(parameters.Encode())))
39@@ -69,16 +77,21 @@
40 return client.dispatchRequest(request)
41 }
42
43+// Post performs an HTTP "POST" to the API. This may be either an API method
44+// invocation (if you pass its name in "operation") or plain resource
45+// retrieval (if you leave "operation" blank).
46 func (client Client) Post(uri *url.URL, operation string, parameters url.Values) ([]byte, error) {
47 queryParams := url.Values{"op": {operation}}
48 uri.RawQuery = queryParams.Encode()
49 return client.nonIdempotentRequest("POST", uri, parameters)
50 }
51
52+// Put updates an object on the API, using an HTTP "PUT" request.
53 func (client Client) Put(uri *url.URL, parameters url.Values) ([]byte, error) {
54 return client.nonIdempotentRequest("PUT", uri, parameters)
55 }
56
57+// Delete deletes an object on the API, using an HTTP "DELETE" request.
58 func (client Client) Delete(uri *url.URL) error {
59 url := client.GetURL(uri)
60 request, err := http.NewRequest("DELETE", url.String(), strings.NewReader(""))
61@@ -92,13 +105,14 @@
62 return nil
63 }
64
65+// Anonymous "signature method" implementation.
66 type anonSigner struct{}
67
68 func (signer anonSigner) OAuthSign(request *http.Request) error {
69 return nil
70 }
71
72-// Trick to ensure *anonSigner implements the OAuthSigner interface.
73+// *anonSigner implements the OAuthSigner interface.
74 var _ OAuthSigner = anonSigner{}
75
76 // NewAnonymousClient creates a client that issues anonymous requests.
77
78=== modified file 'example/live_example.go'
79--- example/live_example.go 2013-02-05 14:27:56 +0000
80+++ example/live_example.go 2013-02-12 05:49:22 +0000
81@@ -77,9 +77,7 @@
82 updateParams := url.Values{"hostname": {"mynewname"}}
83 newNodeObj2, err := newNode.Update(updateParams)
84 checkError(err)
85- newNode2, err := newNodeObj2.GetMAASObject()
86- checkError(err)
87- newNodeName2, err := newNode2.GetField("hostname")
88+ newNodeName2, err := newNodeObj2.GetField("hostname")
89 checkError(err)
90 fmt.Printf("New node updated, now named: %s\n", newNodeName2)
91
92
93=== modified file 'jsonobject.go'
94--- jsonobject.go 2013-02-06 03:54:20 +0000
95+++ jsonobject.go 2013-02-12 05:49:22 +0000
96@@ -23,79 +23,43 @@
97 // There is one exception: a MAASObject is really a special kind of map,
98 // so you can read it as either.
99 // Reading a null item is also an error. So before you try obj.Get*(),
100-// first check that obj != nil.
101-type JSONObject interface {
102- // Type of this value:
103- // "string", "float64", "map", "maasobject", "array", or "bool".
104- Type() string
105- // Read as string.
106- GetString() (string, error)
107- // Read number as float64.
108- GetFloat64() (float64, error)
109- // Read object as map.
110- GetMap() (map[string]JSONObject, error)
111- // Read object as MAAS object.
112- GetMAASObject() (MAASObject, error)
113- // Read list as array.
114- GetArray() ([]JSONObject, error)
115- // Read as bool.
116- GetBool() (bool, error)
117+// first check obj.IsNil().
118+type JSONObject struct {
119+ value interface{}
120+ client Client
121 }
122
123-// Internally, each JSONObject already knows what type it is. It just
124-// can't tell the caller yet because the caller may not have the right
125-// hard-coded variable type.
126-// So for each JSON type, there is a separate implementation of JSONObject
127-// that converts only to that type. Any other conversion is an error.
128-// One type is special: jsonMAASObject is an object in the MAAS sense. It
129-// behaves just like a jsonMap if you want it to, but it also implements
130-// MAASObject.
131-type jsonString string
132-type jsonFloat64 float64
133-type jsonMap map[string]JSONObject
134-type jsonArray []JSONObject
135-type jsonBool bool
136-
137 // Our JSON processor distinguishes a MAASObject from a jsonMap by the fact
138 // that it contains a key "resource_uri". (A regular map might contain the
139 // same key through sheer coincide, but never mind: you can still treat it
140 // as a jsonMap and never notice the difference.)
141 const resourceURI = "resource_uri"
142
143-// maasify is internal. It turns a completely untyped json.Unmarshal result
144-// into a JSONObject (with the appropriate implementation of course).
145-// This function is recursive. Maps and arrays are deep-copied, with each
146-// individual value being converted to a JSONObject type.
147+// maasify turns a completely untyped json.Unmarshal result into a JSONObject
148+// (with the appropriate implementation of course). This function is
149+// recursive. Maps and arrays are deep-copied, with each individual value
150+// being converted to a JSONObject type.
151 func maasify(client Client, value interface{}) JSONObject {
152 if value == nil {
153- return nil
154+ return JSONObject{}
155 }
156 switch value.(type) {
157- case string:
158- return jsonString(value.(string))
159- case float64:
160- return jsonFloat64(value.(float64))
161+ case string, float64, bool:
162+ return JSONObject{value: value}
163 case map[string]interface{}:
164 original := value.(map[string]interface{})
165 result := make(map[string]JSONObject, len(original))
166 for key, value := range original {
167 result[key] = maasify(client, value)
168 }
169- if _, ok := result[resourceURI]; ok {
170- // If the map contains "resource-uri", we can treat
171- // it as a MAAS object.
172- return newJSONMAASObject(result, client)
173- }
174- return jsonMap(result)
175+ return JSONObject{value: result, client: client}
176 case []interface{}:
177 original := value.([]interface{})
178 result := make([]JSONObject, len(original))
179 for index, value := range original {
180 result[index] = maasify(client, value)
181 }
182- return jsonArray(result)
183- case bool:
184- return jsonBool(value.(bool))
185+ return JSONObject{value: result}
186 }
187 msg := fmt.Sprintf("Unknown JSON type, can't be converted to JSONObject: %v", value)
188 panic(msg)
189@@ -106,92 +70,57 @@
190 var obj interface{}
191 err := json.Unmarshal(input, &obj)
192 if err != nil {
193- return nil, err
194+ return JSONObject{}, err
195 }
196 return maasify(client, obj), nil
197 }
198
199 // Return error value for failed type conversion.
200 func failConversion(wantedType string, obj JSONObject) error {
201- msg := fmt.Sprintf("Requested %v, got %v.", wantedType, obj.Type())
202+ msg := fmt.Sprintf("Requested %v, got %T.", wantedType, obj.value)
203 return errors.New(msg)
204 }
205
206-// Error return values for failure to convert to string.
207-func failString(obj JSONObject) (string, error) {
208- return "", failConversion("string", obj)
209-}
210-
211-// Error return values for failure to convert to float64.
212-func failFloat64(obj JSONObject) (float64, error) {
213- return 0.0, failConversion("float64", obj)
214-}
215-
216-// Error return values for failure to convert to map.
217-func failMap(obj JSONObject) (map[string]JSONObject, error) {
218- return make(map[string]JSONObject, 0), failConversion("map", obj)
219-}
220-
221-// Error return values for failure to convert to MAAS object.
222-func failMAASObject(obj JSONObject) (MAASObject, error) {
223- return jsonMAASObject{}, failConversion("maasobject", obj)
224-}
225-
226-// Error return values for failure to convert to array.
227-func failArray(obj JSONObject) ([]JSONObject, error) {
228- return make([]JSONObject, 0), failConversion("array", obj)
229-}
230-
231-// Error return values for failure to convert to bool.
232-func failBool(obj JSONObject) (bool, error) {
233- return false, failConversion("bool", obj)
234-}
235-
236-// JSONObject implementation for jsonString.
237-func (jsonString) Type() string { return "string" }
238-func (obj jsonString) GetString() (string, error) { return string(obj), nil }
239-func (obj jsonString) GetFloat64() (float64, error) { return failFloat64(obj) }
240-func (obj jsonString) GetMap() (map[string]JSONObject, error) { return failMap(obj) }
241-func (obj jsonString) GetMAASObject() (MAASObject, error) { return failMAASObject(obj) }
242-func (obj jsonString) GetArray() ([]JSONObject, error) { return failArray(obj) }
243-func (obj jsonString) GetBool() (bool, error) { return failBool(obj) }
244-
245-// JSONObject implementation for jsonFloat64.
246-func (jsonFloat64) Type() string { return "float64" }
247-func (obj jsonFloat64) GetString() (string, error) { return failString(obj) }
248-func (obj jsonFloat64) GetFloat64() (float64, error) { return float64(obj), nil }
249-func (obj jsonFloat64) GetMap() (map[string]JSONObject, error) { return failMap(obj) }
250-func (obj jsonFloat64) GetMAASObject() (MAASObject, error) { return failMAASObject(obj) }
251-func (obj jsonFloat64) GetArray() ([]JSONObject, error) { return failArray(obj) }
252-func (obj jsonFloat64) GetBool() (bool, error) { return failBool(obj) }
253-
254-// JSONObject implementation for jsonMap.
255-func (jsonMap) Type() string { return "map" }
256-func (obj jsonMap) GetString() (string, error) { return failString(obj) }
257-func (obj jsonMap) GetFloat64() (float64, error) { return failFloat64(obj) }
258-func (obj jsonMap) GetMap() (map[string]JSONObject, error) {
259- return (map[string]JSONObject)(obj), nil
260-}
261-func (obj jsonMap) GetMAASObject() (MAASObject, error) { return failMAASObject(obj) }
262-func (obj jsonMap) GetArray() ([]JSONObject, error) { return failArray(obj) }
263-func (obj jsonMap) GetBool() (bool, error) { return failBool(obj) }
264-
265-// JSONObject implementation for jsonArray.
266-func (jsonArray) Type() string { return "array" }
267-func (obj jsonArray) GetString() (string, error) { return failString(obj) }
268-func (obj jsonArray) GetFloat64() (float64, error) { return failFloat64(obj) }
269-func (obj jsonArray) GetMap() (map[string]JSONObject, error) { return failMap(obj) }
270-func (obj jsonArray) GetMAASObject() (MAASObject, error) { return failMAASObject(obj) }
271-func (obj jsonArray) GetArray() ([]JSONObject, error) {
272- return ([]JSONObject)(obj), nil
273-}
274-func (obj jsonArray) GetBool() (bool, error) { return failBool(obj) }
275-
276-// JSONObject implementation for jsonBool.
277-func (jsonBool) Type() string { return "bool" }
278-func (obj jsonBool) GetString() (string, error) { return failString(obj) }
279-func (obj jsonBool) GetFloat64() (float64, error) { return failFloat64(obj) }
280-func (obj jsonBool) GetMap() (map[string]JSONObject, error) { return failMap(obj) }
281-func (obj jsonBool) GetMAASObject() (MAASObject, error) { return failMAASObject(obj) }
282-func (obj jsonBool) GetArray() ([]JSONObject, error) { return failArray(obj) }
283-func (obj jsonBool) GetBool() (bool, error) { return bool(obj), nil }
284+func (obj JSONObject) IsNil() bool {
285+ return obj.value == nil
286+}
287+
288+func (obj JSONObject) GetString() (value string, err error) {
289+ value, ok := obj.value.(string)
290+ if !ok {
291+ err = failConversion("string", obj)
292+ }
293+ return
294+}
295+
296+func (obj JSONObject) GetFloat64() (value float64, err error) {
297+ value, ok := obj.value.(float64)
298+ if !ok {
299+ err = failConversion("float64", obj)
300+ }
301+ return
302+}
303+
304+func (obj JSONObject) GetMap() (value map[string]JSONObject, err error) {
305+ value, ok := obj.value.(map[string]JSONObject)
306+ if !ok {
307+ err = failConversion("map", obj)
308+ }
309+ return
310+}
311+
312+func (obj JSONObject) GetArray() (value []JSONObject, err error) {
313+ value, ok := obj.value.([]JSONObject)
314+ if !ok {
315+ err = failConversion("array", obj)
316+ }
317+ return
318+}
319+
320+func (obj JSONObject) GetBool() (value bool, err error) {
321+ value, ok := obj.value.(bool)
322+ if !ok {
323+ err = failConversion("bool", obj)
324+ }
325+ return
326+}
327
328=== modified file 'jsonobject_test.go'
329--- jsonobject_test.go 2013-02-05 14:35:15 +0000
330+++ jsonobject_test.go 2013-02-12 05:49:22 +0000
331@@ -14,49 +14,59 @@
332
333 // maasify() converts nil.
334 func (suite *JSONObjectSuite) TestMaasifyConvertsNil(c *C) {
335- c.Check(maasify(Client{}, nil), Equals, nil)
336+ c.Check(maasify(Client{}, nil).IsNil(), Equals, true)
337 }
338
339 // maasify() converts strings.
340 func (suite *JSONObjectSuite) TestMaasifyConvertsString(c *C) {
341 const text = "Hello"
342- c.Check(string(maasify(Client{}, text).(jsonString)), Equals, text)
343+ out, err := maasify(Client{}, text).GetString()
344+ c.Assert(err, IsNil)
345+ c.Check(out, Equals, text)
346 }
347
348 // maasify() converts float64 numbers.
349 func (suite *JSONObjectSuite) TestMaasifyConvertsNumber(c *C) {
350 const number = 3.1415926535
351- c.Check(float64(maasify(Client{}, number).(jsonFloat64)), Equals, number)
352+ num, err := maasify(Client{}, number).GetFloat64()
353+ c.Assert(err, IsNil)
354+ c.Check(num, Equals, number)
355 }
356
357 // maasify() converts array slices.
358 func (suite *JSONObjectSuite) TestMaasifyConvertsArray(c *C) {
359 original := []interface{}{3.0, 2.0, 1.0}
360- output := maasify(Client{}, original).(jsonArray)
361+ output, err := maasify(Client{}, original).GetArray()
362+ c.Assert(err, IsNil)
363 c.Check(len(output), Equals, len(original))
364 }
365
366 // When maasify() converts an array slice, the result contains JSONObjects.
367 func (suite *JSONObjectSuite) TestMaasifyArrayContainsJSONObjects(c *C) {
368- arr := maasify(Client{}, []interface{}{9.9}).(jsonArray)
369- var entry JSONObject
370- entry = arr[0]
371- c.Check((float64)(entry.(jsonFloat64)), Equals, 9.9)
372+ arr, err := maasify(Client{}, []interface{}{9.9}).GetArray()
373+ c.Assert(err, IsNil)
374+ var _ JSONObject = arr[0]
375+ entry, err := arr[0].GetFloat64()
376+ c.Assert(err, IsNil)
377+ c.Check(entry, Equals, 9.9)
378 }
379
380 // maasify() converts maps.
381 func (suite *JSONObjectSuite) TestMaasifyConvertsMap(c *C) {
382 original := map[string]interface{}{"1": "one", "2": "two", "3": "three"}
383- output := maasify(Client{}, original).(jsonMap)
384+ output, err := maasify(Client{}, original).GetMap()
385+ c.Assert(err, IsNil)
386 c.Check(len(output), Equals, len(original))
387 }
388
389 // When maasify() converts a map, the result contains JSONObjects.
390 func (suite *JSONObjectSuite) TestMaasifyMapContainsJSONObjects(c *C) {
391- mp := maasify(Client{}, map[string]interface{}{"key": "value"}).(jsonMap)
392- var entry JSONObject
393- entry = mp["key"]
394- c.Check((string)(entry.(jsonString)), Equals, "value")
395+ jsonobj := maasify(Client{}, map[string]interface{}{"key": "value"})
396+ mp, err := jsonobj.GetMap()
397+ var _ JSONObject = mp["key"]
398+ c.Assert(err, IsNil)
399+ entry, err := mp["key"].GetString()
400+ c.Check(entry, Equals, "value")
401 }
402
403 // maasify() converts MAAS objects.
404@@ -65,66 +75,89 @@
405 "resource_uri": "http://example.com/foo",
406 "size": "3",
407 }
408- output := maasify(Client{}, original).(jsonMAASObject)
409- c.Check(len(output.jsonMap), Equals, len(original))
410- c.Check((string)(output.jsonMap["size"].(jsonString)), Equals, "3")
411+ obj, err := maasify(Client{}, original).GetMAASObject()
412+ c.Assert(err, IsNil)
413+ c.Check(len(obj.GetMap()), Equals, len(original))
414+ size, err := obj.GetMap()["size"].GetString()
415+ c.Assert(err, IsNil)
416+ c.Check(size, Equals, "3")
417 }
418
419 // maasify() passes its client to a MAASObject it creates.
420-func (suite *JSONObjectSuite) TestMaasifyPassesInfoToMAASObject(c *C) {
421+func (suite *JSONObjectSuite) TestMaasifyPassesClientToMAASObject(c *C) {
422 client := Client{}
423 original := map[string]interface{}{"resource_uri": "/foo"}
424- output := maasify(client, original).(jsonMAASObject)
425+ output, err := maasify(client, original).GetMAASObject()
426+ c.Assert(err, IsNil)
427 c.Check(output.client, Equals, client)
428 }
429
430 // maasify() passes its client into an array of MAASObjects it creates.
431-func (suite *JSONObjectSuite) TestMaasifyPassesInfoIntoArray(c *C) {
432+func (suite *JSONObjectSuite) TestMaasifyPassesClientIntoArray(c *C) {
433 client := Client{}
434 obj := map[string]interface{}{"resource_uri": "/foo"}
435 list := []interface{}{obj}
436- output := maasify(client, list).(jsonArray)
437- c.Check(output[0].(jsonMAASObject).client, Equals, client)
438+ jsonobj, err := maasify(client, list).GetArray()
439+ c.Assert(err, IsNil)
440+ out, err := jsonobj[0].GetMAASObject()
441+ c.Assert(err, IsNil)
442+ c.Check(out.client, Equals, client)
443 }
444
445 // maasify() passes its client into a map of MAASObjects it creates.
446-func (suite *JSONObjectSuite) TestMaasifyPassesInfoIntoMap(c *C) {
447+func (suite *JSONObjectSuite) TestMaasifyPassesClientIntoMap(c *C) {
448 client := Client{}
449 obj := map[string]interface{}{"resource_uri": "/foo"}
450 mp := map[string]interface{}{"key": obj}
451- output := maasify(client, mp).(jsonMap)
452- c.Check(output["key"].(jsonMAASObject).client, Equals, client)
453+ jsonobj, err := maasify(client, mp).GetMap()
454+ c.Assert(err, IsNil)
455+ out, err := jsonobj["key"].GetMAASObject()
456+ c.Assert(err, IsNil)
457+ c.Check(out.client, Equals, client)
458 }
459
460 // maasify() passes its client all the way down into any MAASObjects in the
461 // object structure it creates.
462-func (suite *JSONObjectSuite) TestMaasifyPassesInfoAllTheWay(c *C) {
463+func (suite *JSONObjectSuite) TestMaasifyPassesClientAllTheWay(c *C) {
464 client := Client{}
465 obj := map[string]interface{}{"resource_uri": "/foo"}
466 mp := map[string]interface{}{"key": obj}
467 list := []interface{}{mp}
468- output := maasify(client, list).(jsonArray)
469- maasobj := output[0].(jsonMap)["key"]
470- c.Check(maasobj.(jsonMAASObject).client, Equals, client)
471+ jsonobj, err := maasify(client, list).GetArray()
472+ c.Assert(err, IsNil)
473+ outerMap, err := jsonobj[0].GetMap()
474+ c.Assert(err, IsNil)
475+ out, err := outerMap["key"].GetMAASObject()
476+ c.Assert(err, IsNil)
477+ c.Check(out.client, Equals, client)
478 }
479
480 // maasify() converts Booleans.
481 func (suite *JSONObjectSuite) TestMaasifyConvertsBool(c *C) {
482- c.Check(bool(maasify(Client{}, true).(jsonBool)), Equals, true)
483- c.Check(bool(maasify(Client{}, false).(jsonBool)), Equals, false)
484+ t, err := maasify(Client{}, true).GetBool()
485+ c.Assert(err, IsNil)
486+ f, err := maasify(Client{}, false).GetBool()
487+ c.Assert(err, IsNil)
488+ c.Check(t, Equals, true)
489+ c.Check(f, Equals, false)
490 }
491
492 // Parse takes you from a JSON blob to a JSONObject.
493 func (suite *JSONObjectSuite) TestParseMaasifiesJSONBlob(c *C) {
494 blob := []byte("[12]")
495 obj, err := Parse(Client{}, blob)
496- c.Check(err, IsNil)
497- c.Check(float64(obj.(jsonArray)[0].(jsonFloat64)), Equals, 12.0)
498+ c.Assert(err, IsNil)
499+
500+ arr, err := obj.GetArray()
501+ c.Assert(err, IsNil)
502+ out, err := arr[0].GetFloat64()
503+ c.Assert(err, IsNil)
504+ c.Check(out, Equals, 12.0)
505 }
506
507 // String-type JSONObjects convert only to string.
508 func (suite *JSONObjectSuite) TestConversionsString(c *C) {
509- obj := jsonString("Test string")
510+ obj := maasify(Client{}, "Test string")
511
512 value, err := obj.GetString()
513 c.Check(err, IsNil)
514@@ -144,7 +177,7 @@
515
516 // Number-type JSONObjects convert only to float64.
517 func (suite *JSONObjectSuite) TestConversionsFloat64(c *C) {
518- obj := jsonFloat64(1.1)
519+ obj := maasify(Client{}, 1.1)
520
521 value, err := obj.GetFloat64()
522 c.Check(err, IsNil)
523@@ -164,8 +197,7 @@
524
525 // Map-type JSONObjects convert only to map.
526 func (suite *JSONObjectSuite) TestConversionsMap(c *C) {
527- input := map[string]JSONObject{"x": jsonString("y")}
528- obj := jsonMap(input)
529+ obj := maasify(Client{}, map[string]interface{}{"x": "y"})
530
531 value, err := obj.GetMap()
532 c.Check(err, IsNil)
533@@ -187,7 +219,7 @@
534
535 // Array-type JSONObjects convert only to array.
536 func (suite *JSONObjectSuite) TestConversionsArray(c *C) {
537- obj := jsonArray([]JSONObject{jsonString("item")})
538+ obj := maasify(Client{}, []interface{}{"item"})
539
540 value, err := obj.GetArray()
541 c.Check(err, IsNil)
542@@ -209,7 +241,7 @@
543
544 // Boolean-type JSONObjects convert only to bool.
545 func (suite *JSONObjectSuite) TestConversionsBool(c *C) {
546- obj := jsonBool(false)
547+ obj := maasify(Client{}, false)
548
549 value, err := obj.GetBool()
550 c.Check(err, IsNil)
551
552=== modified file 'maas.go'
553--- maas.go 2013-02-05 11:35:09 +0000
554+++ maas.go 2013-02-12 05:49:22 +0000
555@@ -5,6 +5,6 @@
556
557 // NewMAAS returns an interface to the MAAS API as a MAASObject.
558 func NewMAAS(client Client) MAASObject {
559- input := map[string]JSONObject{resourceURI: jsonString(client.BaseURL.String())}
560- return newJSONMAASObject(jsonMap(input), client)
561+ attrs := map[string]interface{}{resourceURI: client.BaseURL.String()}
562+ return newJSONMAASObject(attrs, client)
563 }
564
565=== modified file 'maasobject.go'
566--- maasobject.go 2013-02-07 16:01:48 +0000
567+++ maasobject.go 2013-02-12 05:49:22 +0000
568@@ -5,88 +5,77 @@
569
570 import (
571 "errors"
572+ "fmt"
573 "net/url"
574 )
575
576 // MAASObject represents a MAAS object as returned by the MAAS API, such as a
577 // Node or a Tag.
578-// This is a special kind of JSONObject. A MAAS API call will usually return
579-// either a MAASObject or a list of MAASObjects. (The list itself will be
580-// wrapped in a JSONObject).
581-type MAASObject interface {
582- JSONObject
583-
584- // Utility method to extract a string field from this MAAS object.
585- GetField(name string) (string, error)
586- // URL for this MAAS object.
587- URL() *url.URL
588- // Resource URI for this MAAS object.
589- URI() *url.URL
590- // Retrieve the MAAS object located at thisObject.URI()+name.
591- GetSubObject(name string) MAASObject
592- // Retrieve this MAAS object.
593- Get() (MAASObject, error)
594- // Write this MAAS object.
595- Post(params url.Values) (JSONObject, error)
596- // Update this MAAS object with the given values.
597- Update(params url.Values) (MAASObject, error)
598- // Delete this MAAS object.
599- Delete() error
600- // Invoke a GET-based method on this MAAS object.
601- CallGet(operation string, params url.Values) (JSONObject, error)
602- // Invoke a POST-based method on this MAAS object.
603- CallPost(operation string, params url.Values) (JSONObject, error)
604-}
605-
606-// JSONObject implementation for a MAAS object. From a decoding perspective,
607-// a jsonMAASObject is just like a jsonMap except it contains a key
608-// "resource_uri", and it keeps track of the Client you got it from so that
609-// you can invoke API methods directly on their MAAS objects.
610-// jsonMAASObject implements both JSONObject and MAASObject.
611-type jsonMAASObject struct {
612- jsonMap
613+// You can extract a MAASObject out of a JSONObject using
614+// JSONObject.GetMAASObject. A MAAS API call will usually return either a
615+// MAASObject or a list of MAASObjects. The list itself would be wrapped in
616+// a JSONObject, so if an API call returns a list of objects "l," you first
617+// obtain the array using l.GetArray(). Then, for each item "i" in the array,
618+// obtain the matching MAASObject using i.GetMAASObject().
619+type MAASObject struct {
620+ values map[string]JSONObject
621 client Client
622 uri *url.URL
623 }
624
625 // newJSONMAASObject creates a new MAAS object. It will panic if the given map
626 // does not contain a valid URL for the 'resource_uri' key.
627-func newJSONMAASObject(jmap jsonMap, client Client) jsonMAASObject {
628- const panicPrefix = "Error processing MAAS object: "
629- uriObj, ok := jmap[resourceURI]
630+func newJSONMAASObject(jmap map[string]interface{}, client Client) MAASObject {
631+ obj, err := maasify(client, jmap).GetMAASObject()
632+ if err != nil {
633+ panic(err)
634+ }
635+ return obj
636+}
637+
638+var noResourceURI = errors.New("not a MAAS object: no 'resource_uri' key")
639+
640+// extractURI obtains the "resource_uri" string from a JSONObject map.
641+func extractURI(attrs map[string]JSONObject) (*url.URL, error) {
642+ uriEntry, ok := attrs[resourceURI]
643 if !ok {
644- panic(errors.New(panicPrefix + "no 'resource_uri' key present in the given jsonMap."))
645- }
646- uriString, err := uriObj.GetString()
647- if err != nil {
648- panic(errors.New(panicPrefix + "the value of 'resource_uri' is not a string."))
649- }
650- uri, err := url.Parse(uriString)
651- if err != nil {
652- panic(errors.New(panicPrefix + "the value of 'resource_uri' is not a valid URL."))
653- }
654- return jsonMAASObject{jmap, client, uri}
655-}
656-
657-var _ JSONObject = (*jsonMAASObject)(nil)
658-var _ MAASObject = (*jsonMAASObject)(nil)
659-
660-// JSONObject implementation for jsonMAASObject.
661-func (jsonMAASObject) Type() string { return "maasobject" }
662-func (obj jsonMAASObject) GetString() (string, error) { return failString(obj) }
663-func (obj jsonMAASObject) GetFloat64() (float64, error) { return failFloat64(obj) }
664-func (obj jsonMAASObject) GetMap() (map[string]JSONObject, error) { return obj.jsonMap.GetMap() }
665-func (obj jsonMAASObject) GetMAASObject() (MAASObject, error) { return obj, nil }
666-func (obj jsonMAASObject) GetArray() ([]JSONObject, error) { return failArray(obj) }
667-func (obj jsonMAASObject) GetBool() (bool, error) { return failBool(obj) }
668-
669-// MAASObject implementation for jsonMAASObject.
670-
671-func (obj jsonMAASObject) GetField(name string) (string, error) {
672- return obj.jsonMap[name].GetString()
673-}
674-
675-func (obj jsonMAASObject) URI() *url.URL {
676+ return nil, noResourceURI
677+ }
678+ uri, err := uriEntry.GetString()
679+ if err != nil {
680+ return nil, fmt.Errorf("invalid resource_uri: %v", uri)
681+ }
682+ resourceURL, err := url.Parse(uri)
683+ if err != nil {
684+ return nil, fmt.Errorf("resource_uri does not contain a valid URL: %v", uri)
685+ }
686+ return resourceURL, nil
687+}
688+
689+// JSONObject getter for a MAAS object. From a decoding perspective, a
690+// MAASObject is just like a map except it contains a key "resource_uri", and
691+// it keeps track of the Client you got it from so that you can invoke API
692+// methods directly on their MAAS objects.
693+func (obj JSONObject) GetMAASObject() (MAASObject, error) {
694+ attrs, err := obj.GetMap()
695+ if err != nil {
696+ return MAASObject{}, err
697+ }
698+ uri, err := extractURI(attrs)
699+ if err != nil {
700+ return MAASObject{}, err
701+ }
702+ return MAASObject{values: attrs, client: obj.client, uri: uri}, nil
703+}
704+
705+// GetField extracts a string field from this MAAS object.
706+func (obj MAASObject) GetField(name string) (string, error) {
707+ return obj.values[name].GetString()
708+}
709+
710+// URI is the resource URI for this MAAS object. It is an absolute path, but
711+// without a network part.
712+func (obj MAASObject) URI() *url.URL {
713 // Duplicate the URL.
714 uri, err := url.Parse(obj.uri.String())
715 if err != nil {
716@@ -95,11 +84,20 @@
717 return uri
718 }
719
720-func (obj jsonMAASObject) URL() *url.URL {
721+// URL returns a full absolute URL (including network part) for this MAAS
722+// object on the API.
723+func (obj MAASObject) URL() *url.URL {
724 return obj.client.GetURL(obj.URI())
725 }
726
727-func (obj jsonMAASObject) GetSubObject(name string) MAASObject {
728+// GetMap returns all of the object's attributes in the form of a map.
729+func (obj MAASObject) GetMap() map[string]JSONObject {
730+ return obj.values
731+}
732+
733+// GetSubObject returns a new MAASObject representing the API resource found
734+// at a given sub-path of the current object's resource URI.
735+func (obj MAASObject) GetSubObject(name string) MAASObject {
736 uri := obj.URI()
737 newUrl, err := url.Parse(name)
738 if err != nil {
739@@ -107,66 +105,74 @@
740 }
741 resUrl := uri.ResolveReference(newUrl)
742 resUrl.Path = EnsureTrailingSlash(resUrl.Path)
743- input := map[string]JSONObject{resourceURI: jsonString(resUrl.String())}
744- return newJSONMAASObject(jsonMap(input), obj.client)
745+ input := map[string]interface{}{resourceURI: resUrl.String()}
746+ return newJSONMAASObject(input, obj.client)
747 }
748
749 var NotImplemented = errors.New("Not implemented")
750
751-func (obj jsonMAASObject) Get() (MAASObject, error) {
752+// Get retrieves a fresh copy of this MAAS object from the API.
753+func (obj MAASObject) Get() (MAASObject, error) {
754 uri := obj.URI()
755 result, err := obj.client.Get(uri, "", url.Values{})
756 if err != nil {
757- return nil, err
758+ return MAASObject{}, err
759 }
760 jsonObj, err := Parse(obj.client, result)
761 if err != nil {
762- return nil, err
763+ return MAASObject{}, err
764 }
765 return jsonObj.GetMAASObject()
766 }
767
768-func (obj jsonMAASObject) Post(params url.Values) (JSONObject, error) {
769+// Post overwrites this object's existing value on the API with those given
770+// in "params." It returns the object's new value as received from the API.
771+func (obj MAASObject) Post(params url.Values) (JSONObject, error) {
772 uri := obj.URI()
773 result, err := obj.client.Post(uri, "", params)
774 if err != nil {
775- return nil, err
776+ return JSONObject{}, err
777 }
778 return Parse(obj.client, result)
779 }
780
781-func (obj jsonMAASObject) Update(params url.Values) (MAASObject, error) {
782+// Update modifies this object on the API, based on the values given in
783+// "params." It returns the object's new value as received from the API.
784+func (obj MAASObject) Update(params url.Values) (MAASObject, error) {
785 uri := obj.URI()
786 result, err := obj.client.Put(uri, params)
787 if err != nil {
788- return nil, err
789+ return MAASObject{}, err
790 }
791 jsonObj, err := Parse(obj.client, result)
792 if err != nil {
793- return nil, err
794+ return MAASObject{}, err
795 }
796 return jsonObj.GetMAASObject()
797 }
798
799-func (obj jsonMAASObject) Delete() error {
800+// Delete removes this object on the API.
801+func (obj MAASObject) Delete() error {
802 uri := obj.URI()
803 return obj.client.Delete(uri)
804 }
805
806-func (obj jsonMAASObject) CallGet(operation string, params url.Values) (JSONObject, error) {
807+// CallGet invokes an idempotent API method on this object.
808+func (obj MAASObject) CallGet(operation string, params url.Values) (JSONObject, error) {
809 uri := obj.URI()
810 result, err := obj.client.Get(uri, operation, params)
811 if err != nil {
812- return nil, err
813+ return JSONObject{}, err
814 }
815 return Parse(obj.client, result)
816 }
817
818-func (obj jsonMAASObject) CallPost(operation string, params url.Values) (JSONObject, error) {
819+// CallPost invokes a non-idempotent API method on this object.
820+func (obj MAASObject) CallPost(operation string, params url.Values) (JSONObject, error) {
821 uri := obj.URI()
822 result, err := obj.client.Post(uri, operation, params)
823 if err != nil {
824- return nil, err
825+ return JSONObject{}, err
826 }
827 return Parse(obj.client, result)
828 }
829
830=== modified file 'maasobject_test.go'
831--- maasobject_test.go 2013-02-07 16:01:48 +0000
832+++ maasobject_test.go 2013-02-12 05:49:22 +0000
833@@ -18,16 +18,10 @@
834 return "http://example.com/" + fmt.Sprint(rand.Int31())
835 }
836
837-func makeFakeMAASObject() jsonMAASObject {
838- attrs := make(map[string]JSONObject)
839- attrs[resourceURI] = jsonString(makeFakeResourceURI())
840- return jsonMAASObject{jsonMap: jsonMap(attrs)}
841-}
842-
843-// jsonMAASObjects convert only to map or to MAASObject.
844+// JSONObjects containing MAAS objects convert only to map or to MAASObject.
845 func (suite *MAASObjectSuite) TestConversionsMAASObject(c *C) {
846- input := map[string]JSONObject{resourceURI: jsonString("someplace")}
847- obj := jsonMAASObject{jsonMap: jsonMap(input)}
848+ input := map[string]interface{}{resourceURI: "someplace"}
849+ obj := maasify(Client{}, input)
850
851 mp, err := obj.GetMap()
852 c.Check(err, IsNil)
853@@ -35,9 +29,10 @@
854 c.Check(err, IsNil)
855 c.Check(text, Equals, "someplace")
856
857- maasobj, err := obj.GetMAASObject()
858- c.Check(err, IsNil)
859- _ = maasobj.(jsonMAASObject)
860+ var maasobj MAASObject
861+ maasobj, err = obj.GetMAASObject()
862+ c.Assert(err, IsNil)
863+ c.Check(maasobj, NotNil)
864
865 _, err = obj.GetString()
866 c.Check(err, NotNil)
867@@ -56,8 +51,9 @@
868 msg := recoveredError.(error).Error()
869 c.Check(msg, Matches, ".*no 'resource_uri' key.*")
870 }()
871- input := map[string]JSONObject{"test": jsonString("test")}
872- newJSONMAASObject(jsonMap(input), Client{})
873+
874+ input := map[string]interface{}{"test": "test"}
875+ newJSONMAASObject(input, Client{})
876 }
877
878 func (suite *MAASObjectSuite) TestNewJSONMAASObjectPanicsIfResourceURINotString(c *C) {
879@@ -65,10 +61,11 @@
880 recoveredError := recover()
881 c.Check(recoveredError, NotNil)
882 msg := recoveredError.(error).Error()
883- c.Check(msg, Matches, ".*the value of 'resource_uri' is not a string.*")
884+ c.Check(msg, Matches, ".*invalid resource_uri.*")
885 }()
886- input := map[string]JSONObject{resourceURI: jsonFloat64(77.7)}
887- newJSONMAASObject(jsonMap(input), Client{})
888+
889+ input := map[string]interface{}{resourceURI: 77.77}
890+ newJSONMAASObject(input, Client{})
891 }
892
893 func (suite *MAASObjectSuite) TestNewJSONMAASObjectPanicsIfResourceURINotURL(c *C) {
894@@ -76,16 +73,17 @@
895 recoveredError := recover()
896 c.Check(recoveredError, NotNil)
897 msg := recoveredError.(error).Error()
898- c.Check(msg, Matches, ".*the value of 'resource_uri' is not a valid URL.*")
899+ c.Check(msg, Matches, ".*resource_uri.*valid URL.*")
900 }()
901- input := map[string]JSONObject{resourceURI: jsonString("")}
902- newJSONMAASObject(jsonMap(input), Client{})
903+
904+ input := map[string]interface{}{resourceURI: ""}
905+ newJSONMAASObject(input, Client{})
906 }
907
908 func (suite *MAASObjectSuite) TestNewJSONMAASObjectSetsUpURI(c *C) {
909 URI, _ := url.Parse("http://example.com/a/resource")
910- input := map[string]JSONObject{resourceURI: jsonString(URI.String())}
911- obj := newJSONMAASObject(jsonMap(input), Client{})
912+ attrs := map[string]interface{}{resourceURI: URI.String()}
913+ obj := newJSONMAASObject(attrs, Client{})
914 c.Check(obj.uri, DeepEquals, URI)
915 }
916
917@@ -93,9 +91,9 @@
918 baseURL, _ := url.Parse("http://example.com/")
919 uri := "http://example.com/a/resource"
920 resourceURL, _ := url.Parse(uri)
921- input := map[string]JSONObject{resourceURI: jsonString(uri)}
922+ input := map[string]interface{}{resourceURI: uri}
923 client := Client{BaseURL: baseURL}
924- obj := newJSONMAASObject(jsonMap(input), client)
925+ obj := newJSONMAASObject(input, client)
926
927 URL := obj.URL()
928
929@@ -105,9 +103,9 @@
930 func (suite *MAASObjectSuite) TestGetSubObjectRelative(c *C) {
931 baseURL, _ := url.Parse("http://example.com/")
932 uri := "http://example.com/a/resource/"
933- input := map[string]JSONObject{resourceURI: jsonString(uri)}
934+ input := map[string]interface{}{resourceURI: uri}
935 client := Client{BaseURL: baseURL}
936- obj := newJSONMAASObject(jsonMap(input), client)
937+ obj := newJSONMAASObject(input, client)
938 subName := "test"
939
940 subObj := obj.GetSubObject(subName)
941@@ -122,9 +120,9 @@
942 func (suite *MAASObjectSuite) TestGetSubObjectAbsolute(c *C) {
943 baseURL, _ := url.Parse("http://example.com/")
944 uri := "http://example.com/a/resource/"
945- input := map[string]JSONObject{resourceURI: jsonString(uri)}
946+ input := map[string]interface{}{resourceURI: uri}
947 client := Client{BaseURL: baseURL}
948- obj := newJSONMAASObject(jsonMap(input), client)
949+ obj := newJSONMAASObject(input, client)
950 subName := "/b/test"
951
952 subObj := obj.GetSubObject(subName)
953@@ -138,10 +136,10 @@
954 uri := "http://example.com/a/resource"
955 fieldName := "field name"
956 fieldValue := "a value"
957- input := map[string]JSONObject{
958- resourceURI: jsonString(uri), fieldName: jsonString(fieldValue),
959+ input := map[string]interface{}{
960+ resourceURI: uri, fieldName: fieldValue,
961 }
962- obj := jsonMAASObject{jsonMap: jsonMap(input)}
963+ obj := newJSONMAASObject(input, Client{})
964 value, err := obj.GetField(fieldName)
965 c.Check(err, IsNil)
966 c.Check(value, Equals, fieldValue)
967
968=== modified file 'testservice.go'
969--- testservice.go 2013-02-07 10:01:10 +0000
970+++ testservice.go 2013-02-12 05:49:22 +0000
971@@ -21,9 +21,6 @@
972 TestServer *TestServer
973 }
974
975-// TestMAASObject implements the MAASObject interface.
976-var _ MAASObject = (*TestMAASObject)(nil)
977-
978 // NewTestMAAS returns a TestMAASObject that implements the MAASObject
979 // interface and thus can be used as a test object instead of the one returned
980 // by gomaasapi.NewMAAS().
981@@ -84,28 +81,21 @@
982 // string representing a map and contain a string value for the key
983 // 'system_id'. e.g. `{"system_id": "mysystemid"}`.
984 // If one of these conditions is not met, NewNode panics.
985-func (server *TestServer) NewNode(json string) MAASObject {
986- obj, err := Parse(server.client, []byte(json))
987- if err != nil {
988- panic(err)
989- }
990- mapobj, err := obj.GetMap()
991- if err != nil {
992- panic(err)
993- }
994- systemId, hasSystemId := mapobj["system_id"]
995+func (server *TestServer) NewNode(jsonText string) MAASObject {
996+ var attrs map[string]interface{}
997+ err := json.Unmarshal([]byte(jsonText), &attrs)
998+ if err != nil {
999+ panic(err)
1000+ }
1001+ systemIdEntry, hasSystemId := attrs["system_id"]
1002 if !hasSystemId {
1003 panic("The given map json string does not contain a 'system_id' value.")
1004 }
1005- stringSystemId, err := systemId.GetString()
1006- if err != nil {
1007- panic(err)
1008- }
1009- resourceUri := getNodeURI(server.version, stringSystemId)
1010- mapobj[resourceURI] = jsonString(resourceUri)
1011- maasobj := newJSONMAASObject(mapobj, server.client)
1012- server.nodes[stringSystemId] = maasobj
1013- return maasobj
1014+ systemId := systemIdEntry.(string)
1015+ attrs[resourceURI] = getNodeURI(server.version, systemId)
1016+ obj := newJSONMAASObject(attrs, server.client)
1017+ server.nodes[systemId] = obj
1018+ return obj
1019 }
1020
1021 // Returns a map associating all the nodes' system ids with the nodes'
1022@@ -129,8 +119,7 @@
1023 if !found {
1024 panic("No node with such 'system_id'.")
1025 }
1026- mapObj, _ := node.GetMap()
1027- mapObj[key] = jsonString(value)
1028+ node.GetMap()[key] = maasify(server.client, value)
1029 }
1030
1031 func getNodeListingURL(version string) string {
1032@@ -191,9 +180,27 @@
1033 }
1034 }
1035
1036+// MarshalJSON tells the standard json package how to serialize a JSONObject.
1037+func (obj JSONObject) MarshalJSON() ([]byte, error) {
1038+ if obj.IsNil() {
1039+ return json.Marshal(nil)
1040+ }
1041+ return json.Marshal(obj.value)
1042+}
1043+
1044+// With MarshalJSON, JSONObject implements json.Marshaler.
1045+var _ json.Marshaler = (*JSONObject)(nil)
1046+
1047+// MarshalJSON tells the standard json package how to serialize a MAASObject.
1048+func (obj MAASObject) MarshalJSON() ([]byte, error) {
1049+ return json.Marshal(obj.GetMap())
1050+}
1051+
1052+// With MarshalJSON, MAASObject implements json.Marshaler.
1053+var _ json.Marshaler = (*MAASObject)(nil)
1054+
1055 func marshalNode(node MAASObject) string {
1056- mapObj, _ := node.GetMap()
1057- res, _ := json.Marshal(mapObj)
1058+ res, _ := json.Marshal(node)
1059 return string(res)
1060
1061 }
1062@@ -253,8 +260,7 @@
1063 var convertedNodes = []map[string]JSONObject{}
1064 for systemId, node := range server.nodes {
1065 if !hasId || contains(ids, systemId) {
1066- mapp, _ := node.GetMap()
1067- convertedNodes = append(convertedNodes, mapp)
1068+ convertedNodes = append(convertedNodes, node.GetMap())
1069 }
1070 }
1071 res, _ := json.Marshal(convertedNodes)
1072
1073=== modified file 'testservice_test.go'
1074--- testservice_test.go 2013-02-07 10:01:10 +0000
1075+++ testservice_test.go 2013-02-12 05:49:22 +0000
1076@@ -78,8 +78,8 @@
1077 suite.server.ChangeNode("mysystemid", "newfield", "newvalue")
1078
1079 node, _ := suite.server.nodes["mysystemid"]
1080- mapObj, _ := node.GetMap()
1081- field, _ := mapObj["newfield"].GetString()
1082+ field, err := node.GetField("newfield")
1083+ c.Assert(err, IsNil)
1084 c.Check(field, Equals, "newvalue")
1085 }
1086
1087@@ -234,13 +234,16 @@
1088
1089 c.Check(err, IsNil)
1090 listNodes, err := listNodeObjects.GetArray()
1091- c.Check(err, IsNil)
1092+ c.Assert(err, IsNil)
1093 c.Check(len(listNodes), Equals, 1)
1094- node, _ := listNodes[0].GetMAASObject()
1095- systemId, _ := node.GetField("system_id")
1096+ node, err := listNodes[0].GetMAASObject()
1097+ c.Assert(err, IsNil)
1098+ systemId, err := node.GetField("system_id")
1099+ c.Assert(err, IsNil)
1100 c.Check(systemId, Equals, "mysystemid")
1101 resourceURI, _ := node.GetField(resourceURI)
1102- expectedResourceURI := fmt.Sprintf("/api/%s/nodes/mysystemid/", suite.TestMAASObject.TestServer.version)
1103+ apiVersion := suite.TestMAASObject.TestServer.version
1104+ expectedResourceURI := fmt.Sprintf("/api/%s/nodes/mysystemid/", apiVersion)
1105 c.Check(resourceURI, Equals, expectedResourceURI)
1106 }
1107

Subscribers

People subscribed via source and target branches

to all changes: