ParameterBuilder.go raw
1 // Copyright 2018-2025 JDCLOUD.COM
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package core
16
17 import (
18 "encoding/json"
19 "errors"
20 "fmt"
21 urllib "net/url"
22 "reflect"
23 "regexp"
24 "strings"
25 )
26
27 var baseRequestFields []string
28
29 func init() {
30 req := JDCloudRequest{}
31 reqType := reflect.TypeOf(req)
32 for i := 0; i < reqType.NumField(); i++ {
33 baseRequestFields = append(baseRequestFields, reqType.Field(i).Name)
34 }
35 }
36
37 type ParameterBuilder interface {
38 BuildURL(url string, paramJson []byte) (string, error)
39 BuildBody(paramJson []byte) (string, error)
40 }
41
42 func GetParameterBuilder(method string, logger Logger) ParameterBuilder {
43 if method == MethodGet || method == MethodDelete || method == MethodHead {
44 return &WithoutBodyBuilder{logger}
45 } else {
46 return &WithBodyBuilder{logger}
47 }
48 }
49
50 // WithBodyBuilder supports PUT/POST/PATCH methods.
51 // It has path and body (json) parameters, but no query parameters.
52 type WithBodyBuilder struct {
53 Logger Logger
54 }
55
56 func (b WithBodyBuilder) BuildURL(url string, paramJson []byte) (string, error) {
57 paramMap := make(map[string]interface{})
58 err := json.Unmarshal(paramJson, ¶mMap)
59 if err != nil {
60 b.Logger.Log(LogError, err.Error())
61 return "", err
62 }
63
64 replacedUrl, err := replaceUrlWithPathParam(url, paramMap)
65 if err != nil {
66 b.Logger.Log(LogError, err.Error())
67 return "", err
68 }
69
70 encodedUrl, err := encodeUrl(replacedUrl, nil)
71 if err != nil {
72 return "", err
73 }
74
75 b.Logger.Log(LogInfo, "URL="+encodedUrl)
76 return encodedUrl, nil
77 }
78
79 func (b WithBodyBuilder) BuildBody(paramJson []byte) (string, error) {
80 paramMap := make(map[string]interface{})
81 err := json.Unmarshal(paramJson, ¶mMap)
82 if err != nil {
83 b.Logger.Log(LogError, err.Error())
84 return "", err
85 }
86
87 // remove base request fields
88 for k := range paramMap {
89 if includes(baseRequestFields, k) {
90 delete(paramMap, k)
91 }
92 }
93
94 body, _ := json.Marshal(paramMap)
95 b.Logger.Log(LogInfo, "Body=", string(body))
96 return string(body), nil
97 }
98
99 // WithoutBodyBuilder supports GET/DELETE methods.
100 // It only builds path and query parameters.
101 type WithoutBodyBuilder struct {
102 Logger Logger
103 }
104
105 func (b WithoutBodyBuilder) BuildURL(url string, paramJson []byte) (string, error) {
106 paramMap := make(map[string]interface{})
107 err := json.Unmarshal(paramJson, ¶mMap)
108 if err != nil {
109 b.Logger.Log(LogError, err.Error())
110 return "", err
111 }
112
113 resultUrl, err := replaceUrlWithPathParam(url, paramMap)
114 if err != nil {
115 b.Logger.Log(LogError, err.Error())
116 return "", err
117 }
118
119 queryParams := buildQueryParams(paramMap, url)
120 encodedUrl, err := encodeUrl(resultUrl, queryParams)
121 if err != nil {
122 return "", err
123 }
124
125 b.Logger.Log(LogInfo, string(paramJson))
126 b.Logger.Log(LogInfo, "URL="+encodedUrl)
127 return encodedUrl, nil
128 }
129
130 func (b WithoutBodyBuilder) BuildBody(paramJson []byte) (string, error) {
131 return "", nil
132 }
133
134 func replaceUrlWithPathParam(url string, paramMap map[string]interface{}) (string, error) {
135 r, _ := regexp.Compile("{[a-zA-Z0-9-_]+}")
136 matches := r.FindAllString(url, -1)
137 for _, match := range matches {
138 field := strings.TrimLeft(match, "{")
139 field = strings.TrimRight(field, "}")
140 value, ok := paramMap[field]
141 if !ok {
142 return "", errors.New("Can not find path parameter: " + field)
143 }
144
145 valueStr := fmt.Sprintf("%v", value)
146 url = strings.Replace(url, match, valueStr, -1)
147 }
148
149 return url, nil
150 }
151
152 func buildQueryParams(paramMap map[string]interface{}, url string) urllib.Values {
153 values := urllib.Values{}
154 accessMap(paramMap, url, "", values)
155 return values
156 }
157
158 func accessMap(paramMap map[string]interface{}, url, prefix string, values urllib.Values) {
159 for k, v := range paramMap {
160 // exclude fields of JDCloudRequest class and path parameters
161 if shouldIgnoreField(url, k) {
162 continue
163 }
164
165 switch e := v.(type) {
166 case []interface{}:
167 for i, n := range e {
168 switch f := n.(type) {
169 case map[string]interface{}:
170 subPrefix := fmt.Sprintf("%s.%d.", k, i+1)
171 accessMap(f, url, subPrefix, values)
172 case nil:
173 default:
174 values.Set(fmt.Sprintf("%s%s.%d", prefix, k, i+1), fmt.Sprintf("%s", n))
175 }
176 }
177 case nil:
178 default:
179 values.Set(fmt.Sprintf("%s%s", prefix, k), fmt.Sprintf("%v", v))
180 }
181 }
182 }
183
184 func shouldIgnoreField(url, field string) bool {
185 flag := "{" + field + "}"
186 if strings.Contains(url, flag) {
187 return true
188 }
189
190 if includes(baseRequestFields, field) {
191 return true
192 }
193
194 return false
195 }
196
197 func encodeUrl(requestUrl string, values urllib.Values) (string, error) {
198 urlObj, err := urllib.Parse(requestUrl)
199 if err != nil {
200 return "", err
201 }
202
203 urlObj.RawPath = EscapePath(urlObj.Path, false)
204 uri := urlObj.EscapedPath()
205
206 if values != nil {
207 queryParam := values.Encode()
208 // RFC 3986, ' ' should be encoded to 20%, '+' to 2B%
209 queryParam = strings.Replace(queryParam, "+", "%20", -1)
210 if queryParam != "" {
211 uri += "?" + queryParam
212 }
213 }
214
215 return uri, nil
216 }
217