qingcloud-sdk-go/request/builder.go

278 lines
7.0 KiB
Go

// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or at:
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +-------------------------------------------------------------------------
package request
import (
"fmt"
"net/http"
"net/url"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/yunify/qingcloud-sdk-go/logger"
"github.com/yunify/qingcloud-sdk-go/request/data"
"github.com/yunify/qingcloud-sdk-go/utils"
)
// Builder is the request builder for QingCloud service.
type Builder struct {
parsedURL string
parsedForm url.Values
parsedProperties *map[string]string
parsedParams *map[string]string
operation *data.Operation
input *reflect.Value
}
// BuildHTTPRequest builds http request with an operation and an input.
func (b *Builder) BuildHTTPRequest(o *data.Operation, i *reflect.Value) (*http.Request, error) {
b.operation = o
b.input = i
err := b.parse()
if err != nil {
return nil, err
}
return b.build()
}
func (b *Builder) build() (*http.Request, error) {
httpRequest, err := http.NewRequest(b.operation.RequestMethod, b.parsedURL, nil)
httpRequest.Form = b.parsedForm
if err != nil {
return nil, err
}
if b.operation.RequestMethod == "POST" {
httpRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
logger.Info(fmt.Sprintf(
"Built QingCloud request: [%d] %s \n %s ",
utils.StringToUnixInt(httpRequest.Header.Get("Date"), "RFC 822"),
httpRequest.URL.String(), b.parsedForm))
return httpRequest, nil
}
func (b *Builder) parse() error {
err := b.parseRequestProperties()
if err != nil {
return err
}
err = b.parseRequestParams()
if err != nil {
return err
}
err = b.parseRequestURL()
if err != nil {
return err
}
err = b.parseRequestForm()
if err != nil {
return err
}
return nil
}
func (b *Builder) parseRequestProperties() error {
propertiesMap := map[string]string{}
fields := reflect.ValueOf(b.operation.Properties).Elem()
for i := 0; i < fields.NumField(); i++ {
switch value := fields.Field(i).Interface().(type) {
case *string:
if value != nil {
propertiesMap[fields.Type().Field(i).Tag.Get("name")] = *value
}
case *int:
if value != nil {
propertiesMap[fields.Type().Field(i).Tag.Get("name")] = strconv.Itoa(int(*value))
}
}
}
b.parsedProperties = &propertiesMap
return nil
}
func (b *Builder) parseRequestParams() error {
var requestParams map[string]string
if b.parsedParams != nil {
requestParams = *b.parsedParams
} else {
requestParams = map[string]string{}
}
b.parsedParams = &requestParams
requestParams["action"] = b.operation.APIName
if !b.input.Elem().IsValid() {
return nil
}
for i := 0; i < b.input.Elem().NumField(); i++ {
tagName := b.input.Elem().Type().Field(i).Tag.Get("name")
tagLocation := b.input.Elem().Type().Field(i).Tag.Get("location")
tagDefault := b.input.Elem().Type().Field(i).Tag.Get("default")
if tagName != "" && tagLocation != "" && requestParams != nil {
switch value := b.input.Elem().Field(i).Interface().(type) {
case *string:
if tagDefault != "" {
requestParams[tagName] = tagDefault
}
if value != nil {
requestParams[tagName] = *value
}
case *int:
if tagDefault != "" {
requestParams[tagName] = tagDefault
}
if value != nil {
requestParams[tagName] = strconv.Itoa(int(*value))
}
case *bool:
case *time.Time:
if tagDefault != "" {
requestParams[tagName] = tagDefault
}
if value != nil {
format := b.input.Elem().Type().Field(i).Tag.Get("format")
requestParams[tagName] = utils.TimeToString(*value, format)
}
case []*string:
for index, item := range value {
key := tagName + "." + strconv.Itoa(index+1)
if tagDefault != "" {
requestParams[tagName] = tagDefault
}
if item != nil {
requestParams[key] = *item
}
}
case []*int:
for index, item := range value {
key := tagName + "." + strconv.Itoa(index+1)
if tagDefault != "" {
requestParams[tagName] = tagDefault
}
if item != nil {
requestParams[key] = strconv.Itoa(int(*item))
}
}
default:
if value != nil {
value = value.(interface{})
typeName := reflect.TypeOf(value.(interface{})).String()
if strings.HasPrefix(typeName, "[]*") {
valueArray := reflect.ValueOf(value)
for i := 0; i < valueArray.Len(); i++ {
item := valueArray.Index(i).Elem()
for j := 0; j < item.NumField(); j++ {
fieldTagName := item.Type().Field(j).Tag.Get("name")
tagKey := tagName + "." + strconv.Itoa(i+1) + "." + fieldTagName
switch fieldValue := item.Field(j).Interface().(type) {
case *int:
if fieldValue != nil {
requestParams[tagKey] = strconv.Itoa(int(*fieldValue))
}
case *string:
if fieldValue != nil {
requestParams[tagKey] = *fieldValue
}
case []*string:
dst := make([]string, len(fieldValue))
for i := 0; i < len(fieldValue); i++ {
if fieldValue[i] != nil {
dst[i] = *(fieldValue[i])
}
}
if len(dst) != 0 {
requestParams[tagKey] = strings.Join(dst, ",")
}
}
}
}
}
}
}
}
}
return nil
}
func (b *Builder) parseRequestURL() error {
conf := b.operation.Config
endpoint := conf.Protocol + "://" + conf.Host + ":" + strconv.Itoa(conf.Port)
requestURI := regexp.MustCompile(`/+`).ReplaceAllString(conf.URI, "/")
b.parsedURL = endpoint + requestURI
if b.parsedParams != nil && b.operation.RequestMethod == "GET" {
if _, ok := (*b.parsedParams)["zone"]; !ok {
zone := (*b.parsedProperties)["zone"]
if zone != "" {
(*b.parsedParams)["zone"] = zone
}
}
paramsParts := []string{}
for key, value := range *b.parsedParams {
paramsParts = append(paramsParts, fmt.Sprintf("%s=%s", key, url.QueryEscape(value)))
}
joined := strings.Join(paramsParts, "&")
if joined != "" {
b.parsedURL += "?" + joined
}
}
return nil
}
func (b *Builder) parseRequestForm() error {
if b.parsedParams != nil && b.operation.RequestMethod == "POST" {
var values = make(url.Values)
if _, ok := (*b.parsedParams)["zone"]; !ok {
zone := (*b.parsedProperties)["zone"]
if zone != "" {
(*b.parsedParams)["zone"] = zone
}
}
for key, value := range *b.parsedParams {
values.Set(key, value)
}
b.parsedForm = values
}
return nil
}