435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
205 lines
4.3 KiB
Go
205 lines
4.3 KiB
Go
package xmlrpc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type encodeFunc func(reflect.Value) ([]byte, error)
|
|
|
|
func marshal(v interface{}) ([]byte, error) {
|
|
if v == nil {
|
|
return []byte{}, nil
|
|
}
|
|
|
|
val := reflect.ValueOf(v)
|
|
return encodeValue(val)
|
|
}
|
|
|
|
func encodeValue(val reflect.Value) ([]byte, error) {
|
|
var b []byte
|
|
var err error
|
|
|
|
if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
|
|
if val.IsNil() {
|
|
return []byte("<value/>"), nil
|
|
}
|
|
|
|
val = val.Elem()
|
|
}
|
|
|
|
switch val.Kind() {
|
|
case reflect.Struct:
|
|
switch val.Interface().(type) {
|
|
case time.Time:
|
|
t := val.Interface().(time.Time)
|
|
b = []byte(fmt.Sprintf("<dateTime.iso8601>%s</dateTime.iso8601>", t.Format(iso8601)))
|
|
default:
|
|
b, err = encodeStruct(val)
|
|
}
|
|
case reflect.Map:
|
|
b, err = encodeMap(val)
|
|
case reflect.Slice:
|
|
b, err = encodeSlice(val)
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
b = []byte(fmt.Sprintf("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
|
|
case reflect.Float32, reflect.Float64:
|
|
b = []byte(fmt.Sprintf("<double>%s</double>",
|
|
strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits())))
|
|
case reflect.Bool:
|
|
if val.Bool() {
|
|
b = []byte("<boolean>1</boolean>")
|
|
} else {
|
|
b = []byte("<boolean>0</boolean>")
|
|
}
|
|
case reflect.String:
|
|
var buf bytes.Buffer
|
|
|
|
xml.Escape(&buf, []byte(val.String()))
|
|
|
|
if _, ok := val.Interface().(Base64); ok {
|
|
b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
|
|
} else {
|
|
b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
|
|
}
|
|
|
|
func encodeStruct(value reflect.Value) ([]byte, error) {
|
|
var b bytes.Buffer
|
|
|
|
b.WriteString("<struct>")
|
|
|
|
vals := []reflect.Value{value}
|
|
for j := 0; j < len(vals); j++ {
|
|
val := vals[j]
|
|
t := val.Type()
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
tag := f.Tag.Get("xmlrpc")
|
|
name := f.Name
|
|
fieldVal := val.FieldByName(f.Name)
|
|
fieldValKind := fieldVal.Kind()
|
|
|
|
// Omit unexported fields
|
|
if !fieldVal.CanInterface() {
|
|
continue
|
|
}
|
|
|
|
// Omit fields who are structs that contain no fields themselves
|
|
if fieldValKind == reflect.Struct && fieldVal.NumField() == 0 {
|
|
continue
|
|
}
|
|
|
|
// Omit empty slices
|
|
if fieldValKind == reflect.Slice && fieldVal.Len() == 0 {
|
|
continue
|
|
}
|
|
|
|
// Omit empty fields (defined as nil pointers)
|
|
if tag != "" {
|
|
parts := strings.Split(tag, ",")
|
|
name = parts[0]
|
|
if len(parts) > 1 && parts[1] == "omitempty" {
|
|
if fieldValKind == reflect.Ptr && fieldVal.IsNil() {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// Drill down into anonymous/embedded structs and do not expose the
|
|
// containing embedded struct in request.
|
|
// This will effectively pull up fields in embedded structs to look
|
|
// as part of the original struct in the request.
|
|
if f.Anonymous {
|
|
vals = append(vals, fieldVal)
|
|
continue
|
|
}
|
|
|
|
b.WriteString("<member>")
|
|
b.WriteString(fmt.Sprintf("<name>%s</name>", name))
|
|
|
|
p, err := encodeValue(fieldVal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.Write(p)
|
|
|
|
b.WriteString("</member>")
|
|
}
|
|
}
|
|
|
|
b.WriteString("</struct>")
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func encodeMap(val reflect.Value) ([]byte, error) {
|
|
var t = val.Type()
|
|
|
|
if t.Key().Kind() != reflect.String {
|
|
return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported")
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
|
|
b.WriteString("<struct>")
|
|
|
|
keys := val.MapKeys()
|
|
|
|
for i := 0; i < val.Len(); i++ {
|
|
key := keys[i]
|
|
kval := val.MapIndex(key)
|
|
|
|
b.WriteString("<member>")
|
|
b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
|
|
|
|
p, err := encodeValue(kval)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b.Write(p)
|
|
b.WriteString("</member>")
|
|
}
|
|
|
|
b.WriteString("</struct>")
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func encodeSlice(val reflect.Value) ([]byte, error) {
|
|
var b bytes.Buffer
|
|
|
|
b.WriteString("<array><data>")
|
|
|
|
for i := 0; i < val.Len(); i++ {
|
|
p, err := encodeValue(val.Index(i))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b.Write(p)
|
|
}
|
|
|
|
b.WriteString("</data></array>")
|
|
|
|
return b.Bytes(), nil
|
|
}
|