mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
617 lines
12 KiB
Go
617 lines
12 KiB
Go
package gou
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"math"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Convert a slice of bytes into an array by ensuring it is wrapped
|
|
// with []
|
|
func MakeJsonList(b []byte) []byte {
|
|
if !bytes.HasPrefix(b, []byte{'['}) {
|
|
b = append([]byte{'['}, b...)
|
|
b = append(b, ']')
|
|
}
|
|
return b
|
|
}
|
|
|
|
func JsonString(v interface{}) string {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
return `""`
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func firstNonWsRune(by []byte) (r rune, ok bool) {
|
|
for {
|
|
if len(by) == 0 {
|
|
return 0, false
|
|
}
|
|
r, numBytes := utf8.DecodeRune(by)
|
|
switch r {
|
|
case '\t', '\n', '\r', ' ':
|
|
by = by[numBytes:] // advance past the current whitespace rune and continue
|
|
continue
|
|
case utf8.RuneError: // This is returned when invalid UTF8 is found
|
|
return 0, false
|
|
}
|
|
return r, true
|
|
}
|
|
}
|
|
|
|
// Determines if the bytes is a json array, only looks at prefix
|
|
// not parsing the entire thing
|
|
func IsJson(by []byte) bool {
|
|
firstRune, ok := firstNonWsRune(by)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if firstRune == '[' || firstRune == '{' {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Determines if the bytes is a json array, only looks at prefix
|
|
// not parsing the entire thing
|
|
func IsJsonArray(by []byte) bool {
|
|
firstRune, ok := firstNonWsRune(by)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if firstRune == '[' {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func IsJsonObject(by []byte) bool {
|
|
firstRune, ok := firstNonWsRune(by)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if firstRune == '{' {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
type JsonRawWriter struct {
|
|
bytes.Buffer
|
|
}
|
|
|
|
func (m *JsonRawWriter) MarshalJSON() ([]byte, error) {
|
|
return m.Bytes(), nil
|
|
}
|
|
|
|
func (m *JsonRawWriter) Raw() json.RawMessage {
|
|
return json.RawMessage(m.Bytes())
|
|
}
|
|
|
|
// A wrapper around a map[string]interface{} to facilitate coercion
|
|
// of json data to what you want
|
|
//
|
|
// allows usage such as this
|
|
//
|
|
// jh := NewJsonHelper([]byte(`{
|
|
// "name":"string",
|
|
// "ints":[1,5,9,11],
|
|
// "int":1,
|
|
// "int64":1234567890,
|
|
// "MaxSize" : 1048576,
|
|
// "strings":["string1"],
|
|
// "nested":{
|
|
// "nest":"string2",
|
|
// "strings":["string1"],
|
|
// "int":2,
|
|
// "list":["value"],
|
|
// "nest2":{
|
|
// "test":"good"
|
|
// }
|
|
// },
|
|
// "nested2":[
|
|
// {"sub":5}
|
|
// ]
|
|
// }`)
|
|
//
|
|
// i := jh.Int("nested.int") // 2
|
|
// i2 := jh.Int("ints[1]") // 5 array position 1 from [1,5,9,11]
|
|
// s := jh.String("nested.nest") // "string2"
|
|
//
|
|
type JsonHelper map[string]interface{}
|
|
|
|
func NewJsonHelper(b []byte) JsonHelper {
|
|
jh := make(JsonHelper)
|
|
json.Unmarshal(b, &jh)
|
|
return jh
|
|
}
|
|
|
|
func NewJsonHelperReader(r io.Reader) (jh JsonHelper, err error) {
|
|
jh = make(JsonHelper)
|
|
err = json.NewDecoder(r).Decode(&jh)
|
|
return
|
|
}
|
|
|
|
func NewJsonHelpers(b []byte) []JsonHelper {
|
|
var jhl []JsonHelper
|
|
json.Unmarshal(MakeJsonList(b), &jhl)
|
|
return jhl
|
|
}
|
|
|
|
func NewJsonHelperMapString(m map[string]string) JsonHelper {
|
|
jh := make(JsonHelper)
|
|
for k, v := range m {
|
|
jh[k] = v
|
|
}
|
|
return jh
|
|
}
|
|
|
|
// Make a JsonHelper from http response. This will automatically
|
|
// close the response body
|
|
func NewJsonHelperFromResp(resp *http.Response) (JsonHelper, error) {
|
|
jh := make(JsonHelper)
|
|
if resp == nil || resp.Body == nil {
|
|
return jh, fmt.Errorf("No response or response body to read")
|
|
}
|
|
defer resp.Body.Close()
|
|
respBytes, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(respBytes) == 0 {
|
|
return jh, fmt.Errorf("No data in response")
|
|
}
|
|
if err := json.Unmarshal(respBytes, &jh); err != nil {
|
|
return jh, err
|
|
}
|
|
return jh, nil
|
|
}
|
|
|
|
func jsonList(v interface{}) []interface{} {
|
|
switch v.(type) {
|
|
case []interface{}:
|
|
return v.([]interface{})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func jsonEntry(name string, v interface{}) (interface{}, bool) {
|
|
switch val := v.(type) {
|
|
case map[string]interface{}:
|
|
if root, ok := val[name]; ok {
|
|
return root, true
|
|
} else {
|
|
return nil, false
|
|
}
|
|
case JsonHelper:
|
|
return val.Get(name), true
|
|
case []interface{}:
|
|
return v, true
|
|
default:
|
|
Debug("no type? ", name, " ", v)
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
// Get the key (or keypath) value as interface, mostly used
|
|
// internally through String, etc methods
|
|
//
|
|
// jh.Get("name.subname")
|
|
// jh.Get("name/subname")
|
|
// jh.Get("name.arrayname[1]")
|
|
// jh.Get("name.arrayname[]")
|
|
func (j JsonHelper) Get(n string) interface{} {
|
|
if len(j) == 0 {
|
|
return nil
|
|
}
|
|
var parts []string
|
|
if strings.Contains(n, "/") {
|
|
parts = strings.Split(n, "/")
|
|
if strings.HasPrefix(n, "/") && len(parts) > 0 {
|
|
parts = parts[1:]
|
|
}
|
|
} else {
|
|
parts = strings.Split(n, ".")
|
|
}
|
|
var root interface{}
|
|
var err error
|
|
var ok, isList, listEntry bool
|
|
var ln, st, idx int
|
|
for ict, name := range parts {
|
|
isList = strings.HasSuffix(name, "[]")
|
|
listEntry = strings.HasSuffix(name, "]") && !isList
|
|
ln, idx = len(name), -1
|
|
if isList || listEntry {
|
|
st = strings.Index(name, "[")
|
|
idx, err = strconv.Atoi(name[st+1 : ln-1])
|
|
name = name[:st]
|
|
}
|
|
if ict == 0 {
|
|
root, ok = j[name]
|
|
} else {
|
|
root, ok = jsonEntry(name, root)
|
|
}
|
|
if !ok {
|
|
if len(parts) > 0 {
|
|
// lets ensure the actual json-value doesn't have period in key
|
|
root, ok = j[n]
|
|
if !ok {
|
|
return nil
|
|
} else {
|
|
return root
|
|
}
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
}
|
|
if isList {
|
|
return jsonList(root)
|
|
} else if listEntry && err == nil {
|
|
if lst := jsonList(root); lst != nil && len(lst) > idx {
|
|
root = lst[idx]
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
}
|
|
return root
|
|
}
|
|
|
|
// Get a Helper from a string path
|
|
func (j JsonHelper) Helper(n string) JsonHelper {
|
|
v := j.Get(n)
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
switch vt := v.(type) {
|
|
case map[string]interface{}:
|
|
cn := JsonHelper{}
|
|
for n, val := range vt {
|
|
cn[n] = val
|
|
}
|
|
return cn
|
|
case map[string]string:
|
|
cn := JsonHelper{}
|
|
for n, val := range vt {
|
|
cn[n] = val
|
|
}
|
|
return cn
|
|
case JsonHelper:
|
|
return vt
|
|
default:
|
|
//Infof("wrong type: %T", v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Get list of Helpers at given name. Trys to coerce into
|
|
// proper Helper type
|
|
func (j JsonHelper) Helpers(n string) []JsonHelper {
|
|
v := j.Get(n)
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
switch val := v.(type) {
|
|
case []map[string]interface{}:
|
|
hl := make([]JsonHelper, 0)
|
|
for _, mapVal := range val {
|
|
hl = append(hl, mapVal)
|
|
}
|
|
return hl
|
|
case []interface{}:
|
|
jhl := make([]JsonHelper, 0)
|
|
for _, item := range val {
|
|
if jh, ok := item.(map[string]interface{}); ok {
|
|
jhl = append(jhl, jh)
|
|
}
|
|
}
|
|
return jhl
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Gets slice of interface{}
|
|
func (j JsonHelper) List(n string) []interface{} {
|
|
v := j.Get(n)
|
|
switch val := v.(type) {
|
|
case []string:
|
|
il := make([]interface{}, len(val))
|
|
for i, val := range val {
|
|
il[i] = val
|
|
}
|
|
return il
|
|
case []interface{}:
|
|
return val
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (j JsonHelper) String(n string) string {
|
|
if v := j.Get(n); v != nil {
|
|
val, _ := CoerceString(v)
|
|
return val
|
|
}
|
|
return ""
|
|
}
|
|
func (j JsonHelper) Strings(n string) []string {
|
|
if v := j.Get(n); v != nil {
|
|
return CoerceStrings(v)
|
|
}
|
|
return nil
|
|
}
|
|
func (j JsonHelper) Ints(n string) []int {
|
|
v := j.Get(n)
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
return CoerceInts(v)
|
|
}
|
|
func (j JsonHelper) StringSafe(n string) (string, bool) {
|
|
v := j.Get(n)
|
|
if v != nil {
|
|
if s, ok := v.(string); ok {
|
|
return s, ok
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (j JsonHelper) Int(n string) int {
|
|
i, ok := j.IntSafe(n)
|
|
if !ok {
|
|
return -1
|
|
}
|
|
return i
|
|
}
|
|
|
|
func (j JsonHelper) IntSafe(n string) (int, bool) {
|
|
v := j.Get(n)
|
|
return valToInt(v)
|
|
}
|
|
|
|
func (j JsonHelper) Int64(n string) int64 {
|
|
i64, ok := j.Int64Safe(n)
|
|
if !ok {
|
|
return -1
|
|
}
|
|
return i64
|
|
}
|
|
|
|
func (j JsonHelper) Int64Safe(n string) (int64, bool) {
|
|
v := j.Get(n)
|
|
return valToInt64(v)
|
|
}
|
|
|
|
func (j JsonHelper) Float64(n string) float64 {
|
|
v := j.Get(n)
|
|
f64, err := CoerceFloat(v)
|
|
if err != nil {
|
|
return math.NaN()
|
|
}
|
|
return f64
|
|
}
|
|
|
|
func (j JsonHelper) Float64Safe(n string) (float64, bool) {
|
|
v := j.Get(n)
|
|
if v == nil {
|
|
return math.NaN(), true
|
|
}
|
|
fv, err := CoerceFloat(v)
|
|
if err != nil {
|
|
return math.NaN(), false
|
|
}
|
|
return fv, true
|
|
}
|
|
|
|
func (j JsonHelper) Uint64(n string) uint64 {
|
|
v := j.Get(n)
|
|
if v != nil {
|
|
return CoerceUintShort(v)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (j JsonHelper) Uint64Safe(n string) (uint64, bool) {
|
|
v := j.Get(n)
|
|
if v != nil {
|
|
if uv, err := CoerceUint(v); err == nil {
|
|
return uv, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (j JsonHelper) BoolSafe(n string) (val bool, ok bool) {
|
|
v := j.Get(n)
|
|
if v != nil {
|
|
switch v.(type) {
|
|
case bool:
|
|
return v.(bool), true
|
|
case string:
|
|
if s := v.(string); len(s) > 0 {
|
|
if b, err := strconv.ParseBool(s); err == nil {
|
|
return b, true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false, false
|
|
}
|
|
|
|
func (j JsonHelper) Bool(n string) bool {
|
|
val, ok := j.BoolSafe(n)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
func (j JsonHelper) Map(n string) map[string]interface{} {
|
|
v := j.Get(n)
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
m, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (j JsonHelper) MapSafe(n string) (map[string]interface{}, bool) {
|
|
v := j.Get(n)
|
|
if v == nil {
|
|
return nil, false
|
|
}
|
|
m, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return m, true
|
|
}
|
|
|
|
func (j JsonHelper) PrettyJson() []byte {
|
|
jsonPretty, _ := json.MarshalIndent(j, " ", " ")
|
|
return jsonPretty
|
|
}
|
|
func (j JsonHelper) Keys() []string {
|
|
keys := make([]string, 0)
|
|
for key := range j {
|
|
keys = append(keys, key)
|
|
}
|
|
return keys
|
|
}
|
|
func (j JsonHelper) HasKey(name string) bool {
|
|
if val := j.Get(name); val != nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GobDecode overwrites the receiver, which must be a pointer,
|
|
// with the value represented by the byte slice, which was written
|
|
// by GobEncode, usually for the same concrete type.
|
|
// GobDecode([]byte) error
|
|
func (j *JsonHelper) GobDecode(data []byte) error {
|
|
var mv map[string]interface{}
|
|
if err := json.Unmarshal(data, &mv); err != nil {
|
|
return err
|
|
}
|
|
*j = JsonHelper(mv)
|
|
return nil
|
|
}
|
|
func (j *JsonHelper) GobEncode() ([]byte, error) {
|
|
by, err := json.Marshal(j)
|
|
return by, err
|
|
}
|
|
|
|
// The following consts are from http://code.google.com/p/go-bit/ (Apache licensed). It
|
|
// lets us figure out how wide go ints are, and determine their max and min values.
|
|
|
|
// Note the use of << to create an untyped constant.
|
|
const bitsPerWord = 32 << uint(^uint(0)>>63)
|
|
|
|
// Implementation-specific size of int and uint in bits.
|
|
const BitsPerWord = bitsPerWord // either 32 or 64
|
|
|
|
// Implementation-specific integer limit values.
|
|
const (
|
|
MaxInt = 1<<(BitsPerWord-1) - 1 // either 1<<31 - 1 or 1<<63 - 1
|
|
MinInt = -MaxInt - 1 // either -1 << 31 or -1 << 63
|
|
MaxUint = 1<<BitsPerWord - 1 // either 1<<32 - 1 or 1<<64 - 1
|
|
)
|
|
|
|
func flattenHelper(uv url.Values, jh JsonHelper, pre string) error {
|
|
for k, v := range jh {
|
|
if err := flattenJsonValue(uv, v, pre+k); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func flattenJsonMap(uv url.Values, jsonMap map[string]interface{}, pre string) error {
|
|
for k, v := range jsonMap {
|
|
if err := flattenJsonValue(uv, v, pre+k); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func flattenJsonValue(uv url.Values, v interface{}, k string) error {
|
|
// TODO: all these ints aren't possible are they?
|
|
switch x := v.(type) {
|
|
case []string:
|
|
uv[k] = x
|
|
// case []interface{}:
|
|
// sva := make([]string, 0)
|
|
// for _, av := range x {
|
|
// if err := flattenJsonValue(n, av, k); err != nil {
|
|
// return nil
|
|
// }
|
|
// }
|
|
// if len(sva) > 0 {
|
|
// uv[k] = sva
|
|
// }
|
|
case map[string]bool:
|
|
// what to do?
|
|
Info("not implemented: [string]bool")
|
|
case map[string]interface{}:
|
|
if len(x) > 0 {
|
|
if err := flattenJsonMap(uv, x, k+"."); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case string:
|
|
uv.Set(k, x)
|
|
case bool:
|
|
if x == true {
|
|
uv.Set(k, "t")
|
|
} else {
|
|
uv.Set(k, "f")
|
|
}
|
|
case int:
|
|
uv.Set(k, strconv.FormatInt(int64(x), 10))
|
|
case int8:
|
|
uv.Set(k, strconv.FormatInt(int64(x), 10))
|
|
case int16:
|
|
uv.Set(k, strconv.FormatInt(int64(x), 10))
|
|
case int32:
|
|
uv.Set(k, strconv.FormatInt(int64(x), 10))
|
|
case int64:
|
|
uv.Set(k, strconv.FormatInt(x, 10))
|
|
case uint:
|
|
uv.Set(k, strconv.FormatUint(uint64(x), 10))
|
|
case uint8:
|
|
uv.Set(k, strconv.FormatUint(uint64(x), 10))
|
|
case uint16:
|
|
uv.Set(k, strconv.FormatUint(uint64(x), 10))
|
|
case uint32:
|
|
uv.Set(k, strconv.FormatUint(uint64(x), 10))
|
|
case uint64:
|
|
uv.Set(k, strconv.FormatUint(x, 10))
|
|
case float32:
|
|
uv.Set(k, strconv.FormatFloat(float64(x), 'f', -1, 64))
|
|
case float64:
|
|
uv.Set(k, strconv.FormatFloat(x, 'f', -1, 64))
|
|
default:
|
|
// what types don't we support?
|
|
// []interface{}
|
|
}
|
|
return nil
|
|
}
|