Skip to content

Commit

Permalink
Add JSON support:
Browse files Browse the repository at this point in the history
 - rpc extensions to support both XML and JSON requests and responses
 - net support for a Eureka connection using JSON or XML
 - net support for marshalling and unmarshalling of the apps, app and instance structs
 - custom unmarshallers and intermediate wrapper structs to deal with nasty Eureka JSON
  • Loading branch information
cquinn committed Jun 17, 2014
1 parent c4574c4 commit d62037f
Show file tree
Hide file tree
Showing 10 changed files with 768 additions and 242 deletions.
104 changes: 104 additions & 0 deletions marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package fargo

// MIT Licensed (see README.md) - Copyright (c) 2013 Hudl <@Hudl>

import (
"encoding/json"
//"fmt"
"strconv"
)

// Temporary structs used for GetAppsResponse unmarshalling
type getAppsResponseArray GetAppsResponse
type getAppsResponseSingle struct {
Application *Application `json:"application"`
AppsHashcode string `json:"apps__hashcode"`
VersionsDelta int `json:"versions__delta"`
}

// UnmarshalJSON is a custom JSON unmarshaler for GetAppsResponse to deal with
// sometimes non-wrapped Application arrays when there is only a single Application item.
func (r *GetAppsResponse) UnmarshalJSON(b []byte) error {
//fmt.Printf("GetAppsResponse.UnmarshalJSON b:%s\n", string(b))
var err error

// Normal array case
var ra getAppsResponseArray
if err = json.Unmarshal(b, &ra); err == nil {
//fmt.Printf("GetAppsResponse.UnmarshalJSON ra:%+v\n", ra)
*r = GetAppsResponse(ra)
return nil
}
// Bogus non-wrapped case
var rs getAppsResponseSingle
if err = json.Unmarshal(b, &rs); err == nil {
//fmt.Printf("GetAppsResponse.UnmarshalJSON rs:%+v\n", rs)
r.Applications = make([]*Application, 1, 1)
r.Applications[0] = rs.Application
r.AppsHashcode = rs.AppsHashcode
r.VersionsDelta = rs.VersionsDelta
return nil
}
return err
}

// Temporary structs used for Application unmarshalling
type applicationArray Application
type applicationSingle struct {
Name string `json:"name"`
Instance *Instance `json:"instance"`
}

// UnmarshalJSON is a custom JSON unmarshaler for Application to deal with
// sometimes non-wrapped Instance array when there is only a single Instance item.
func (a *Application) UnmarshalJSON(b []byte) error {
//fmt.Printf("Application.UnmarshalJSON b:%s\n", string(b))
var err error

// Normal array case
var aa applicationArray
if err = json.Unmarshal(b, &aa); err == nil {
//fmt.Printf("Application.UnmarshalJSON aa:%+v\n", aa)
*a = Application(aa)
return nil
}

// Bogus non-wrapped case
var as applicationSingle
if err = json.Unmarshal(b, &as); err == nil {
//fmt.Printf("Application.UnmarshalJSON as:%+v\n", as)
a.Name = as.Name
a.Instances = make([]*Instance, 1, 1)
a.Instances[0] = as.Instance
return nil
}
return err
}

type instance Instance

// UnmarshalJSON is a custom JSON unmarshaler for Instance to deal with the
// different Port encodings between XML and JSON. Here we copy the values from the JSON
// Port struct into the simple XML int field.
func (i *Instance) UnmarshalJSON(b []byte) error {
var err error
var ii instance
if err = json.Unmarshal(b, &ii); err == nil {
//fmt.Printf("Instance.UnmarshalJSON ii:%+v\n", ii)
*i = Instance(ii)
i.Port, _ = strconv.Atoi(ii.PortJ.Number)
i.SecurePort, _ = strconv.Atoi(ii.SecurePortJ.Number)
//i.Port = ii.PortJ.Number
//i.SecurePort = ii.SecurePortJ.Number
return nil
}
return err
}

// UnmarshalJSON is a custom JSON unmarshaler for InstanceMetadata to handle squirreling away
// the raw JSON for later parsing.
func (i *InstanceMetadata) UnmarshalJSON(b []byte) error {
i.Raw = b
// TODO(cq) could actually parse Raw here, and in a parallel UnmarshalXML as well.
return nil
}
30 changes: 21 additions & 9 deletions metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fargo
// MIT Licensed (see README.md) - Copyright (c) 2013 Hudl <@Hudl>

import (
"encoding/json"
"fmt"
"github.com/clbanning/x2j"
)
Expand All @@ -12,27 +13,38 @@ func (a *Application) ParseAllMetadata() error {
for _, instance := range a.Instances {
err := instance.Metadata.parse()
if err != nil {
log.Error("Failed parsing metadata for Instance=%s of Application=%s: ", instance.HostName, a.Name, err.Error())
log.Error("Failed parsing metadata for Instance=%s of Application=%s: %s", instance.HostName, a.Name, err.Error())
return err
}
}
return nil
}

func (im *InstanceMetadata) parse() error {
// wrap in a BS xml tag so all metadata tags are pulled
if len(im.Raw) == 0 {
im.parsed = make(map[string]interface{})
log.Debug("len(Metadata)==0. Quitting parsing.")
return nil
}
fullDoc := append(append([]byte("<d>"), im.Raw...), []byte("</d>")...)
parsedDoc, err := x2j.ByteDocToMap(fullDoc, true)
if err != nil {
log.Error("Error unmarshalling: ", err.Error())
return fmt.Errorf("error unmarshalling: ", err.Error())
//log.Debug("InstanceMetadata.parse: %s", im.Raw)

if len(im.Raw) > 0 && im.Raw[0] == '{' {
// JSON
err := json.Unmarshal(im.Raw, &im.parsed)
if err != nil {
log.Error("Error unmarshalling: %s", err.Error())
return fmt.Errorf("error unmarshalling: %s", err.Error())
}
} else {
// XML: wrap in a BS xml tag so all metadata tags are pulled
fullDoc := append(append([]byte("<d>"), im.Raw...), []byte("</d>")...)
parsedDoc, err := x2j.ByteDocToMap(fullDoc, true)
if err != nil {
log.Error("Error unmarshalling: %s", err.Error())
return fmt.Errorf("error unmarshalling: %s", err.Error())
}
im.parsed = parsedDoc["d"].(map[string]interface{})
}
im.parsed = parsedDoc["d"].(map[string]interface{})
return nil
}

Expand All @@ -44,7 +56,7 @@ func (im *InstanceMetadata) GetMap() map[string]interface{} {
func (im *InstanceMetadata) getItem(key string) (interface{}, bool, error) {
err := im.parse()
if err != nil {
return "", false, fmt.Errorf("parsing error: ", err.Error())
return "", false, fmt.Errorf("parsing error: %s", err.Error())
}
val, present := im.parsed[key]
return val, present, nil
Expand Down
Loading

0 comments on commit d62037f

Please sign in to comment.