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, &paramMap)
  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, &paramMap)
  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, &paramMap)
 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