parse.go raw

   1  package doapi
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/json"
   6  	"errors"
   7  	"fmt"
   8  	"io/ioutil"
   9  	"net/http"
  10  	"net/url"
  11  	"reflect"
  12  	"strings"
  13  	"text/template"
  14  
  15  	log "github.com/sirupsen/logrus"
  16  
  17  	"github.com/iij/doapi/protocol"
  18  )
  19  
  20  func execTemplate(name string, tmplstr string, arg interface{}) string {
  21  	tmpl, err := template.New(name).Parse(tmplstr)
  22  	if err != nil {
  23  		panic(err)
  24  	}
  25  	buf := new(bytes.Buffer)
  26  	if err = tmpl.Execute(buf, arg); err != nil {
  27  		panic(err)
  28  	}
  29  	return buf.String()
  30  }
  31  
  32  // GetPath APIのURIのパス部分を求める
  33  func GetPath(arg protocol.CommonArg) string {
  34  	return "/r/" + APIVersion + execTemplate(arg.APIName(), arg.URI(), arg)
  35  }
  36  
  37  // GetParam APIのクエリストリング部分を求める
  38  func GetParam(api API, arg protocol.CommonArg) *url.URL {
  39  	param, err := url.Parse(api.Endpoint)
  40  	if err != nil {
  41  		panic(err)
  42  	}
  43  	param.Path = GetPath(arg)
  44  	q := param.Query()
  45  	_, toQuery, _ := ArgumentListType(arg)
  46  	typs := reflect.TypeOf(arg)
  47  	vals := reflect.ValueOf(arg)
  48  	for _, key := range toQuery {
  49  		if _, flag := typs.FieldByName(key); !flag {
  50  			log.Info("no field", key)
  51  			continue
  52  		}
  53  		if val := vals.FieldByName(key).String(); len(val) != 0 {
  54  			q.Set(key, val)
  55  		}
  56  	}
  57  	param.RawQuery = q.Encode()
  58  	return param
  59  }
  60  
  61  // GetBody API呼び出しのリクエストボディ(JSON文字列)を求める
  62  func GetBody(arg protocol.CommonArg) string {
  63  	b, err := json.Marshal(arg)
  64  	if err != nil {
  65  		panic(err)
  66  	}
  67  	return string(b)
  68  }
  69  
  70  // Call API呼び出しを実行し、レスポンスを得る
  71  func Call(api API, arg protocol.CommonArg, resp interface{}) (err error) {
  72  	if err = Validate(arg); err != nil {
  73  		return
  74  	}
  75  	var res *http.Response
  76  	param := GetParam(api, arg)
  77  	log.Debug("method", arg.Method(), "param", param, "arg", arg)
  78  	if res, err = api.PostSome(arg.Method(), *param, arg); err != nil {
  79  		log.Error("PostSome", err)
  80  		return
  81  	}
  82  	log.Debug("res", res, "err", err)
  83  	var b []byte
  84  	if b, err = ioutil.ReadAll(res.Body); err != nil {
  85  		log.Error("ioutil.ReadAll", err)
  86  		return
  87  	}
  88  	log.Debug("data", string(b))
  89  	if err = json.Unmarshal(b, &resp); err != nil {
  90  		log.Error("json.Unmarshal", err)
  91  		return
  92  	}
  93  	var cresp = protocol.CommonResponse{}
  94  	err = json.Unmarshal(b, &cresp)
  95  	if err == nil && cresp.ErrorResponse.ErrorType != "" {
  96  		return fmt.Errorf("%s: %s", cresp.ErrorResponse.ErrorType, cresp.ErrorResponse.ErrorMessage)
  97  	}
  98  	return
  99  }
 100  
 101  // Validate APIの必須引数が入っているかどうかをチェック
 102  func Validate(arg protocol.CommonArg) error {
 103  	var res []string
 104  	typs := reflect.TypeOf(arg)
 105  	vals := reflect.ValueOf(arg)
 106  	for i := 0; i < typs.NumField(); i++ {
 107  		fld := typs.Field(i)
 108  		tagstrJSON := fld.Tag.Get("json")
 109  		tagstrP2 := fld.Tag.Get("p2pub")
 110  		if strings.Contains(tagstrJSON, "omitempty") {
 111  			// optional argument
 112  			continue
 113  		}
 114  		if strings.Contains(tagstrP2, "query") {
 115  			// optional argument
 116  			continue
 117  		}
 118  		if val := vals.Field(i).String(); len(val) == 0 {
 119  			res = append(res, fld.Name)
 120  		}
 121  	}
 122  	if len(res) != 0 {
 123  		return fmt.Errorf("missing arguments: %+v", res)
 124  	}
 125  	return nil
 126  }
 127  
 128  // ArgumentList API引数のリストを求める。必須とオプションに分類
 129  func ArgumentList(arg protocol.CommonArg) (required, optional []string) {
 130  	typs := reflect.TypeOf(arg)
 131  	for i := 0; i < typs.NumField(); i++ {
 132  		fld := typs.Field(i)
 133  		tagstrJSON := fld.Tag.Get("json")
 134  		tagstrP2 := fld.Tag.Get("p2pub")
 135  		if strings.Contains(tagstrJSON, "omitempty") || strings.Contains(tagstrP2, "query") {
 136  			optional = append(optional, fld.Name)
 137  		} else {
 138  			required = append(required, fld.Name)
 139  		}
 140  	}
 141  	return
 142  }
 143  
 144  // ArgumentListType API引数のリストを求める。URI埋め込み、クエリストリング、JSONに分類
 145  func ArgumentListType(arg protocol.CommonArg) (toURI, toQuery, toJSON []string) {
 146  	typs := reflect.TypeOf(arg)
 147  	for i := 0; i < typs.NumField(); i++ {
 148  		fld := typs.Field(i)
 149  		tagstrJSON := fld.Tag.Get("json")
 150  		tagstrP2 := fld.Tag.Get("p2pub")
 151  		if strings.Contains(tagstrP2, "query") {
 152  			toQuery = append(toQuery, fld.Name)
 153  		} else if strings.HasPrefix(tagstrJSON, "-") {
 154  			toURI = append(toURI, fld.Name)
 155  		} else {
 156  			toJSON = append(toJSON, fld.Name)
 157  		}
 158  	}
 159  	return
 160  }
 161  
 162  func argumentAltKeyList(arg protocol.CommonArg) (toAltQuery, toAltJSON map[string]string) {
 163  	toAltQuery = make(map[string]string)
 164  	toAltJSON = make(map[string]string)
 165  	typs := reflect.TypeOf(arg)
 166  	for i := 0; i < typs.NumField(); i++ {
 167  		fld := typs.Field(i)
 168  		tagstrJSON := fld.Tag.Get("json")
 169  		tagstrP2 := fld.Tag.Get("p2pub")
 170  		altKey := strings.Split(tagstrJSON, ",")[0]
 171  		if altKey == "" || altKey == "-" {
 172  			continue
 173  		}
 174  		if strings.Contains(tagstrP2, "query") {
 175  			toAltQuery[fld.Name] = altKey
 176  		} else {
 177  			toAltJSON[fld.Name] = altKey
 178  		}
 179  	}
 180  	return
 181  }
 182  
 183  // ValidateMap APIの必須引数が入っているかどうかをチェック
 184  func ValidateMap(name string, data map[string]string) error {
 185  	var res []string
 186  	typs := protocol.TypeMap[name]
 187  	for i := 0; i < typs.NumField(); i++ {
 188  		fld := typs.Field(i)
 189  		tagstrJSON := fld.Tag.Get("json")
 190  		tagstrP2 := fld.Tag.Get("p2pub")
 191  		if strings.Contains(tagstrJSON, "omitempty") {
 192  			// optional argument
 193  			continue
 194  		}
 195  		if strings.Contains(tagstrP2, "query") {
 196  			// optional argument
 197  			continue
 198  		}
 199  		if data[fld.Name] == "" {
 200  			res = append(res, fld.Name)
 201  		}
 202  	}
 203  	if len(res) != 0 {
 204  		return fmt.Errorf("missing arguments: %+v", res)
 205  	}
 206  	return nil
 207  }
 208  
 209  // CallWithMap API呼び出しを実行する。引数と戻り値が構造体ではなくmap
 210  func CallWithMap(api API, name string, data map[string]string, resp map[string]interface{}) error {
 211  	if err := ValidateMap(name, data); err != nil {
 212  		return err
 213  	}
 214  	argt := protocol.TypeMap[name]
 215  	arg := reflect.Zero(argt).Interface().(protocol.CommonArg)
 216  	var res *http.Response
 217  	param, err := url.Parse(api.Endpoint)
 218  	if err != nil {
 219  		panic(err)
 220  	}
 221  	param.Path = "/r/" + APIVersion + execTemplate(name, arg.URI(), data)
 222  	q := param.Query()
 223  	_, toQuery, toJSON := ArgumentListType(arg)
 224  	log.Debug("query", toQuery, "json", toJSON, "path", param.Path)
 225  	toAltQuery, toAltJSON := argumentAltKeyList(arg)
 226  	log.Debug("query altkey - ", toAltQuery, " json altkey - ", toAltJSON)
 227  	var jsonmap = map[string]interface{}{}
 228  	for _, v := range toJSON {
 229  		if len(data[v]) != 0 {
 230  			if altkey, ok := toAltJSON[v]; ok {
 231  				jsonmap[altkey] = data[v]
 232  			} else {
 233  				jsonmap[v] = data[v]
 234  			}
 235  		}
 236  	}
 237  	for _, v := range toQuery {
 238  		if len(data[v]) != 0 {
 239  			if altkey, ok := toAltQuery[v]; ok {
 240  				q.Set(altkey, data[v])
 241  			} else {
 242  				q.Set(v, data[v])
 243  			}
 244  		}
 245  	}
 246  	param.RawQuery = q.Encode()
 247  	if res, err = api.PostSome(arg.Method(), *param, jsonmap); err != nil {
 248  		log.Error("PostSome", err)
 249  		return err
 250  	}
 251  	log.Debug("res", res)
 252  	var b []byte
 253  	if b, err = ioutil.ReadAll(res.Body); err != nil {
 254  		log.Error("ioutil.ReadAll", err)
 255  		return err
 256  	}
 257  	log.Debug("data", string(b))
 258  	if err = json.Unmarshal(b, &resp); err != nil {
 259  		log.Error("json.Unmarshal", err)
 260  		return err
 261  	}
 262  	if val, ok := resp["ErrorResponse"]; ok {
 263  		errstr := execTemplate("ErrorResponse", "{{.ErrorType}}: {{.ErrorMessage}}", val)
 264  		return errors.New(errstr)
 265  	}
 266  	return nil
 267  }
 268