@@ -5,42 +5,66 @@ package fargo
55import (
66 "encoding/json"
77 "encoding/xml"
8+ "fmt"
89 "io"
910 "strconv"
1011)
1112
12- // Temporary structs used for GetAppsResponse unmarshalling
13- type getAppsResponseArray GetAppsResponse
14- type getAppsResponseSingle struct {
15- Application * Application `json:"application"`
16- AppsHashcode string `json:"apps__hashcode"`
17- VersionsDelta int `json:"versions__delta"`
13+ func intFromJSONNumberOrString (jv interface {}, description string ) (int , error ) {
14+ switch v := jv .(type ) {
15+ case float64 :
16+ return int (v ), nil
17+ case string :
18+ n , err := strconv .Atoi (v )
19+ if err != nil {
20+ return 0 , err
21+ }
22+ return n , nil
23+ default :
24+ return 0 , fmt .Errorf ("unexpected %s: %[2]v (type %[2]T)" , description , jv )
25+ }
1826}
1927
2028// UnmarshalJSON is a custom JSON unmarshaler for GetAppsResponse to deal with
2129// sometimes non-wrapped Application arrays when there is only a single Application item.
2230func (r * GetAppsResponse ) UnmarshalJSON (b []byte ) error {
2331 marshalLog .Debugf ("GetAppsResponse.UnmarshalJSON b:%s\n " , string (b ))
24- var err error
32+ resolveDelta := func (d interface {}) (int , error ) {
33+ return intFromJSONNumberOrString (d , "versions delta" )
34+ }
2535
2636 // Normal array case
27- var ra getAppsResponseArray
28- if err = json .Unmarshal (b , & ra ); err == nil {
29- marshalLog .Debug ("GetAppsResponse.UnmarshalJSON ra:%+v\n " , ra )
30- * r = GetAppsResponse (ra )
31- return nil
37+ type getAppsResponse GetAppsResponse
38+ auxArray := struct {
39+ * getAppsResponse
40+ VersionsDelta interface {} `json:"versions__delta"`
41+ }{
42+ getAppsResponse : (* getAppsResponse )(r ),
3243 }
44+ var err error
45+ if err = json .Unmarshal (b , & auxArray ); err == nil {
46+ marshalLog .Debugf ("GetAppsResponse.UnmarshalJSON array:%+v\n " , auxArray )
47+ r .VersionsDelta , err = resolveDelta (auxArray .VersionsDelta )
48+ return err
49+ }
50+
3351 // Bogus non-wrapped case
34- var rs getAppsResponseSingle
35- if err = json .Unmarshal (b , & rs ); err == nil {
36- marshalLog .Debug ("GetAppsResponse.UnmarshalJSON rs:%+v\n " , rs )
37- r .Applications = make ([]* Application , 1 , 1 )
38- r .Applications [0 ] = rs .Application
39- r .AppsHashcode = rs .AppsHashcode
40- r .VersionsDelta = rs .VersionsDelta
41- return nil
52+ auxSingle := struct {
53+ Application * Application `json:"application"`
54+ AppsHashcode string `json:"apps__hashcode"`
55+ VersionsDelta interface {} `json:"versions__delta"`
56+ }{}
57+ if err := json .Unmarshal (b , & auxSingle ); err != nil {
58+ return err
4259 }
43- return err
60+ marshalLog .Debugf ("GetAppsResponse.UnmarshalJSON single:%+v\n " , auxSingle )
61+ if r .VersionsDelta , err = resolveDelta (auxSingle .VersionsDelta ); err != nil {
62+ return err
63+ }
64+ r .Applications = make ([]* Application , 1 , 1 )
65+ r .Applications [0 ] = auxSingle .Application
66+ r .AppsHashcode = auxSingle .AppsHashcode
67+ return nil
4468}
4569
4670// Temporary structs used for Application unmarshalling
@@ -59,15 +83,15 @@ func (a *Application) UnmarshalJSON(b []byte) error {
5983 // Normal array case
6084 var aa applicationArray
6185 if err = json .Unmarshal (b , & aa ); err == nil {
62- marshalLog .Debug ("Application.UnmarshalJSON aa:%+v\n " , aa )
86+ marshalLog .Debugf ("Application.UnmarshalJSON aa:%+v\n " , aa )
6387 * a = Application (aa )
6488 return nil
6589 }
6690
6791 // Bogus non-wrapped case
6892 var as applicationSingle
6993 if err = json .Unmarshal (b , & as ); err == nil {
70- marshalLog .Debug ("Application.UnmarshalJSON as:%+v\n " , as )
94+ marshalLog .Debugf ("Application.UnmarshalJSON as:%+v\n " , as )
7195 a .Name = as .Name
7296 a .Instances = make ([]* Instance , 1 , 1 )
7397 a .Instances [0 ] = as .Instance
@@ -76,30 +100,140 @@ func (a *Application) UnmarshalJSON(b []byte) error {
76100 return err
77101}
78102
79- type instance Instance
103+ func stringAsBool (s string ) bool {
104+ return s == "true"
105+ }
80106
81- // UnmarshalJSON is a custom JSON unmarshaler for Instance to deal with the
82- // different Port encodings between XML and JSON. Here we copy the values from the JSON
83- // Port struct into the simple XML int field.
107+ // UnmarshalJSON is a custom JSON unmarshaler for Instance, transcribing the two composite port
108+ // specifications up to top-level fields.
84109func (i * Instance ) UnmarshalJSON (b []byte ) error {
110+ // Preclude recursive calls to MarshalJSON.
111+ type instance Instance
112+ // inboundJSONFormatPort describes an instance's network port, including whether its registrant
113+ // considers the port to be enabled or disabled.
114+ //
115+ // Example JSON encoding:
116+ //
117+ // Eureka versions 1.2.1 and prior:
118+ // "port":{"@enabled":"true", "$":"7101"}
119+ //
120+ // Eureka version 1.2.2 and later:
121+ // "port":{"@enabled":"true", "$":7101}
122+ //
123+ // Note that later versions of Eureka write the port number as a JSON number rather than as a
124+ // decimal-formatted string. We accept it as either an integer or a string. Strangely, the
125+ // "@enabled" field remains a string.
126+ type inboundJSONFormatPort struct {
127+ Number interface {} `json:"$"`
128+ Enabled bool `json:"@enabled,string"`
129+ }
130+ aux := struct {
131+ * instance
132+ Port inboundJSONFormatPort `json:"port"`
133+ SecurePort inboundJSONFormatPort `json:"securePort"`
134+ }{
135+ instance : (* instance )(i ),
136+ }
137+ if err := json .Unmarshal (b , & aux ); err != nil {
138+ return err
139+ }
140+ resolvePort := func (port interface {}) (int , error ) {
141+ return intFromJSONNumberOrString (port , "port number" )
142+ }
85143 var err error
86- var ii instance
87- if err = json .Unmarshal (b , & ii ); err == nil {
88- marshalLog .Debug ("Instance.UnmarshalJSON ii:%+v\n " , ii )
89- * i = Instance (ii )
90- i .Port = parsePort (ii .PortJ .Number )
91- i .SecurePort = parsePort (ii .SecurePortJ .Number )
92- return nil
144+ if i .Port , err = resolvePort (aux .Port .Number ); err != nil {
145+ return err
93146 }
94- return err
147+ i .PortEnabled = aux .Port .Enabled
148+ if i .SecurePort , err = resolvePort (aux .SecurePort .Number ); err != nil {
149+ return err
150+ }
151+ i .SecurePortEnabled = aux .SecurePort .Enabled
152+ return nil
95153}
96154
97- func parsePort (s string ) int {
98- n , err := strconv .Atoi (s )
99- if err != nil {
100- log .Warningf ("Invalid port error: %s" , err .Error ())
155+ // MarshalJSON is a custom JSON marshaler for Instance, adapting the top-level raw port values to
156+ // the composite port specifications.
157+ func (i * Instance ) MarshalJSON () ([]byte , error ) {
158+ // Preclude recursive calls to MarshalJSON.
159+ type instance Instance
160+ // outboundJSONFormatPort describes an instance's network port, including whether its registrant
161+ // considers the port to be enabled or disabled.
162+ //
163+ // Example JSON encoding:
164+ //
165+ // "port":{"@enabled":"true", "$":"7101"}
166+ //
167+ // Note that later versions of Eureka write the port number as a JSON number rather than as a
168+ // decimal-formatted string. We emit the port number as a string, not knowing the Eureka
169+ // server's version. Strangely, the "@enabled" field remains a string.
170+ type outboundJSONFormatPort struct {
171+ Number int `json:"$,string"`
172+ Enabled bool `json:"@enabled,string"`
173+ }
174+ aux := struct {
175+ * instance
176+ Port outboundJSONFormatPort `json:"port"`
177+ SecurePort outboundJSONFormatPort `json:"securePort"`
178+ }{
179+ (* instance )(i ),
180+ outboundJSONFormatPort {i .Port , i .PortEnabled },
181+ outboundJSONFormatPort {i .SecurePort , i .SecurePortEnabled },
101182 }
102- return n
183+ return json .Marshal (& aux )
184+ }
185+
186+ // xmlFormatPort describes an instance's network port, including whether its registrant considers
187+ // the port to be enabled or disabled.
188+ //
189+ // Example XML encoding:
190+ //
191+ // <port enabled="true">7101</port>
192+ type xmlFormatPort struct {
193+ Number int `xml:",chardata"`
194+ Enabled bool `xml:"enabled,attr"`
195+ }
196+
197+ // UnmarshalXML is a custom XML unmarshaler for Instance, transcribing the two composite port
198+ // specifications up to top-level fields.
199+ func (i * Instance ) UnmarshalXML (d * xml.Decoder , start xml.StartElement ) error {
200+ type instance Instance
201+ aux := struct {
202+ * instance
203+ Port xmlFormatPort `xml:"port"`
204+ SecurePort xmlFormatPort `xml:"securePort"`
205+ }{
206+ instance : (* instance )(i ),
207+ }
208+ if err := d .DecodeElement (& aux , & start ); err != nil {
209+ return err
210+ }
211+ i .Port = aux .Port .Number
212+ i .PortEnabled = aux .Port .Enabled
213+ i .SecurePort = aux .SecurePort .Number
214+ i .SecurePortEnabled = aux .SecurePort .Enabled
215+ return nil
216+ }
217+
218+ // startLocalName creates a start-tag of an XML element with the given local name and no namespace name.
219+ func startLocalName (local string ) xml.StartElement {
220+ return xml.StartElement {Name : xml.Name {Space : "" , Local : local }}
221+ }
222+
223+ // MarshalXML is a custom XML marshaler for Instance, adapting the top-level raw port values to
224+ // the composite port specifications.
225+ func (i * Instance ) MarshalXML (e * xml.Encoder , start xml.StartElement ) error {
226+ type instance Instance
227+ aux := struct {
228+ * instance
229+ Port xmlFormatPort `xml:"port"`
230+ SecurePort xmlFormatPort `xml:"securePort"`
231+ }{
232+ instance : (* instance )(i ),
233+ Port : xmlFormatPort {i .Port , i .PortEnabled },
234+ SecurePort : xmlFormatPort {i .SecurePort , i .SecurePortEnabled },
235+ }
236+ return e .EncodeElement (& aux , startLocalName ("instance" ))
103237}
104238
105239// UnmarshalJSON is a custom JSON unmarshaler for InstanceMetadata to handle squirreling away
@@ -123,11 +257,6 @@ func (i *InstanceMetadata) MarshalJSON() ([]byte, error) {
123257 return i .Raw , nil
124258}
125259
126- // startLocalName creates a start-tag of an XML element with the given local name and no namespace name.
127- func startLocalName (local string ) xml.StartElement {
128- return xml.StartElement {Name : xml.Name {Space : "" , Local : local }}
129- }
130-
131260// MarshalXML is a custom XML marshaler for InstanceMetadata.
132261func (i InstanceMetadata ) MarshalXML (e * xml.Encoder , start xml.StartElement ) error {
133262 tokens := []xml.Token {start }
@@ -191,7 +320,7 @@ func (m metadataMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
191320 return nil
192321}
193322
194- func metadataValue (i DataCenterInfo ) interface {} {
323+ func metadataValue (i * DataCenterInfo ) interface {} {
195324 if i .Name == Amazon {
196325 return i .Metadata
197326 }
@@ -205,7 +334,7 @@ var (
205334
206335// MarshalXML is a custom XML marshaler for DataCenterInfo, writing either Metadata or AlternateMetadata
207336// depending on the type of data center indicated by the Name.
208- func (i DataCenterInfo ) MarshalXML (e * xml.Encoder , start xml.StartElement ) error {
337+ func (i * DataCenterInfo ) MarshalXML (e * xml.Encoder , start xml.StartElement ) error {
209338 if err := e .EncodeToken (start ); err != nil {
210339 return err
211340 }
@@ -222,6 +351,7 @@ func (i DataCenterInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) error
222351
223352type preliminaryDataCenterInfo struct {
224353 Name string `xml:"name" json:"name"`
354+ Class string `xml:"-" json:"@class"`
225355 Metadata metadataMap `xml:"metadata" json:"metadata"`
226356}
227357
@@ -247,8 +377,9 @@ func populateAmazonMetadata(dst *AmazonMetadataType, src map[string]string) {
247377 bindValue (& dst .InstanceType , src , "instance-type" )
248378}
249379
250- func adaptDataCenterInfo (dst * DataCenterInfo , src preliminaryDataCenterInfo ) {
380+ func adaptDataCenterInfo (dst * DataCenterInfo , src * preliminaryDataCenterInfo ) {
251381 dst .Name = src .Name
382+ dst .Class = src .Class
252383 if src .Name == Amazon {
253384 populateAmazonMetadata (& dst .Metadata , src .Metadata )
254385 } else {
@@ -265,37 +396,76 @@ func (i *DataCenterInfo) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er
265396 if err := d .DecodeElement (& p , & start ); err != nil {
266397 return err
267398 }
268- adaptDataCenterInfo (i , p )
399+ adaptDataCenterInfo (i , & p )
269400 return nil
270401}
271402
272403// MarshalJSON is a custom JSON marshaler for DataCenterInfo, writing either Metadata or AlternateMetadata
273404// depending on the type of data center indicated by the Name.
274- func (i DataCenterInfo ) MarshalJSON () ([]byte , error ) {
405+ func (i * DataCenterInfo ) MarshalJSON () ([]byte , error ) {
275406 type named struct {
276- Name string `json:"name"`
407+ Name string `json:"name"`
408+ Class string `json:"@class"`
277409 }
278410 if i .Name == Amazon {
279411 return json .Marshal (struct {
280412 named
281413 Metadata AmazonMetadataType `json:"metadata"`
282- }{named {i .Name }, i .Metadata })
414+ }{
415+ named {i .Name , "com.netflix.appinfo.AmazonInfo" },
416+ i .Metadata ,
417+ })
418+ }
419+ class := "com.netflix.appinfo.MyDataCenterInfo"
420+ if i .Name != MyOwn {
421+ class = i .Class
283422 }
284423 return json .Marshal (struct {
285424 named
286- Metadata map [string ]string `json:"metadata"`
287- }{named {i .Name }, i .AlternateMetadata })
425+ Metadata map [string ]string `json:"metadata,omitempty"`
426+ }{
427+ named {i .Name , class },
428+ i .AlternateMetadata ,
429+ })
430+ }
431+
432+ func jsonValueAsString (i interface {}) string {
433+ switch v := i .(type ) {
434+ case string :
435+ return v
436+ case float64 :
437+ return fmt .Sprintf ("%.f" , v )
438+ case bool :
439+ return strconv .FormatBool (v )
440+ case []interface {}, map [string ]interface {}:
441+ // Don't bother trying to decode these.
442+ return ""
443+ case nil :
444+ return ""
445+ default :
446+ panic ("type of unexpected value" )
447+ }
288448}
289449
290450// UnmarshalJSON is a custom JSON unmarshaler for DataCenterInfo, populating either Metadata or AlternateMetadata
291451// depending on the type of data center indicated by the Name.
292452func (i * DataCenterInfo ) UnmarshalJSON (b []byte ) error {
293- p := preliminaryDataCenterInfo {
294- Metadata : make (map [string ]string , 11 ),
453+ // The Eureka server will mistakenly convert metadata values that look like numbers to JSON numbers.
454+ // Convert them back to strings.
455+ aux := struct {
456+ * preliminaryDataCenterInfo
457+ PreliminaryMetadata map [string ]interface {} `json:"metadata"`
458+ }{
459+ PreliminaryMetadata : make (map [string ]interface {}, 11 ),
295460 }
296- if err := json .Unmarshal (b , & p ); err != nil {
461+ if err := json .Unmarshal (b , & aux ); err != nil {
297462 return err
298463 }
299- adaptDataCenterInfo (i , p )
464+ metadata := make (map [string ]string , len (aux .PreliminaryMetadata ))
465+ for k , v := range aux .PreliminaryMetadata {
466+ metadata [k ] = jsonValueAsString (v )
467+ }
468+ aux .Metadata = metadata
469+ adaptDataCenterInfo (i , aux .preliminaryDataCenterInfo )
300470 return nil
301471}
0 commit comments