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