github.com/AngusLu/go-swagger@v0.28.0/scan/route_params.go (about)

     1  // +build !go1.11
     2  
     3  package scan
     4  
     5  import (
     6  	"errors"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/go-openapi/spec"
    11  )
    12  
    13  const (
    14  	// ParamDescriptionKey indicates the tag used to define a parameter description in swagger:route
    15  	ParamDescriptionKey = "description"
    16  	// ParamNameKey indicates the tag used to define a parameter name in swagger:route
    17  	ParamNameKey = "name"
    18  	// ParamInKey indicates the tag used to define a parameter location in swagger:route
    19  	ParamInKey = "in"
    20  	// ParamRequiredKey indicates the tag used to declare whether a parameter is required in swagger:route
    21  	ParamRequiredKey = "required"
    22  	// ParamTypeKey indicates the tag used to define the parameter type in swagger:route
    23  	ParamTypeKey = "type"
    24  	// ParamAllowEmptyKey indicates the tag used to indicate whether a parameter allows empty values in swagger:route
    25  	ParamAllowEmptyKey = "allowempty"
    26  
    27  	// SchemaMinKey indicates the tag used to indicate the minimum value allowed for this type in swagger:route
    28  	SchemaMinKey = "min"
    29  	// SchemaMaxKey indicates the tag used to indicate the maximum value allowed for this type in swagger:route
    30  	SchemaMaxKey = "max"
    31  	// SchemaEnumKey indicates the tag used to specify the allowed values for this type in swagger:route
    32  	SchemaEnumKey = "enum"
    33  	// SchemaFormatKey indicates the expected format for this field in swagger:route
    34  	SchemaFormatKey = "format"
    35  	// SchemaDefaultKey indicates the default value for this field in swagger:route
    36  	SchemaDefaultKey = "default"
    37  	// SchemaMinLenKey indicates the minimum length this field in swagger:route
    38  	SchemaMinLenKey = "minlength"
    39  	// SchemaMaxLenKey indicates the minimum length this field in swagger:route
    40  	SchemaMaxLenKey = "maxlength"
    41  
    42  	// TypeArray is the identifier for an array type in swagger:route
    43  	TypeArray = "array"
    44  	// TypeNumber is the identifier for a number type in swagger:route
    45  	TypeNumber = "number"
    46  	// TypeInteger is the identifier for an integer type in swagger:route
    47  	TypeInteger = "integer"
    48  	// TypeBoolean is the identifier for a boolean type in swagger:route
    49  	TypeBoolean = "boolean"
    50  	// TypeBool is the identifier for a boolean type in swagger:route
    51  	TypeBool = "bool"
    52  	// TypeObject is the identifier for an object type in swagger:route
    53  	TypeObject = "object"
    54  	// TypeString is the identifier for a string type in swagger:route
    55  	TypeString = "string"
    56  )
    57  
    58  var (
    59  	validIn    = []string{"path", "query", "header", "body", "form"}
    60  	basicTypes = []string{TypeInteger, TypeNumber, TypeString, TypeBoolean, TypeBool, TypeArray}
    61  )
    62  
    63  func newSetParams(params []*spec.Parameter, setter func([]*spec.Parameter)) *setOpParams {
    64  	return &setOpParams{
    65  		set:        setter,
    66  		parameters: params,
    67  	}
    68  }
    69  
    70  type setOpParams struct {
    71  	set        func([]*spec.Parameter)
    72  	parameters []*spec.Parameter
    73  }
    74  
    75  func (s *setOpParams) Matches(line string) bool {
    76  	return rxParameters.MatchString(line)
    77  }
    78  
    79  func (s *setOpParams) Parse(lines []string) error {
    80  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
    81  		return nil
    82  	}
    83  
    84  	var current *spec.Parameter
    85  	var extraData map[string]string
    86  
    87  	for _, line := range lines {
    88  		l := strings.TrimSpace(line)
    89  
    90  		if strings.HasPrefix(l, "+") {
    91  			s.finalizeParam(current, extraData)
    92  			current = new(spec.Parameter)
    93  			extraData = make(map[string]string)
    94  			l = strings.TrimPrefix(l, "+")
    95  		}
    96  
    97  		kv := strings.SplitN(l, ":", 2)
    98  
    99  		if len(kv) <= 1 {
   100  			continue
   101  		}
   102  
   103  		key := strings.ToLower(strings.TrimSpace(kv[0]))
   104  		value := strings.TrimSpace(kv[1])
   105  
   106  		if current == nil {
   107  			return errors.New("invalid route/operation schema provided")
   108  		}
   109  
   110  		switch key {
   111  		case ParamDescriptionKey:
   112  			current.Description = value
   113  		case ParamNameKey:
   114  			current.Name = value
   115  		case ParamInKey:
   116  			v := strings.ToLower(value)
   117  			if contains(validIn, v) {
   118  				current.In = v
   119  			}
   120  		case ParamRequiredKey:
   121  			if v, err := strconv.ParseBool(value); err == nil {
   122  				current.Required = v
   123  			}
   124  		case ParamTypeKey:
   125  			if current.Schema == nil {
   126  				current.Schema = new(spec.Schema)
   127  			}
   128  			if contains(basicTypes, value) {
   129  				current.Type = strings.ToLower(value)
   130  				if current.Type == TypeBool {
   131  					current.Type = TypeBoolean
   132  				}
   133  			} else {
   134  				if ref, err := spec.NewRef("#/definitions/" + value); err == nil {
   135  					current.Type = TypeObject
   136  					current.Schema.Ref = ref
   137  				}
   138  			}
   139  			current.Schema.Type = spec.StringOrArray{current.Type}
   140  		case ParamAllowEmptyKey:
   141  			if v, err := strconv.ParseBool(value); err == nil {
   142  				current.AllowEmptyValue = v
   143  			}
   144  		default:
   145  			extraData[key] = value
   146  		}
   147  	}
   148  
   149  	s.finalizeParam(current, extraData)
   150  	s.set(s.parameters)
   151  	return nil
   152  }
   153  
   154  func (s *setOpParams) finalizeParam(param *spec.Parameter, data map[string]string) {
   155  	if param == nil {
   156  		return
   157  	}
   158  
   159  	processSchema(data, param)
   160  	s.parameters = append(s.parameters, param)
   161  }
   162  
   163  func processSchema(data map[string]string, param *spec.Parameter) {
   164  	if param.Schema == nil {
   165  		return
   166  	}
   167  
   168  	var enumValues []string
   169  
   170  	for key, value := range data {
   171  		switch key {
   172  		case SchemaMinKey:
   173  			if t := getType(param.Schema); t == TypeNumber || t == TypeInteger {
   174  				v, _ := strconv.ParseFloat(value, 64)
   175  				param.Schema.Minimum = &v
   176  			}
   177  		case SchemaMaxKey:
   178  			if t := getType(param.Schema); t == TypeNumber || t == TypeInteger {
   179  				v, _ := strconv.ParseFloat(value, 64)
   180  				param.Schema.Maximum = &v
   181  			}
   182  		case SchemaMinLenKey:
   183  			if getType(param.Schema) == TypeArray {
   184  				v, _ := strconv.ParseInt(value, 10, 64)
   185  				param.Schema.MinLength = &v
   186  			}
   187  		case SchemaMaxLenKey:
   188  			if getType(param.Schema) == TypeArray {
   189  				v, _ := strconv.ParseInt(value, 10, 64)
   190  				param.Schema.MaxLength = &v
   191  			}
   192  		case SchemaEnumKey:
   193  			enumValues = strings.Split(value, ",")
   194  		case SchemaFormatKey:
   195  			param.Schema.Format = value
   196  		case SchemaDefaultKey:
   197  			param.Schema.Default = convert(param.Type, value)
   198  		}
   199  	}
   200  
   201  	if param.Description != "" {
   202  		param.Schema.Description = param.Description
   203  	}
   204  
   205  	convertEnum(param.Schema, enumValues)
   206  }
   207  
   208  func convertEnum(schema *spec.Schema, enumValues []string) {
   209  	if len(enumValues) == 0 {
   210  		return
   211  	}
   212  
   213  	var finalEnum []interface{}
   214  	for _, v := range enumValues {
   215  		finalEnum = append(finalEnum, convert(schema.Type[0], strings.TrimSpace(v)))
   216  	}
   217  	schema.Enum = finalEnum
   218  }
   219  
   220  func convert(typeStr, valueStr string) interface{} {
   221  	switch typeStr {
   222  	case TypeInteger:
   223  		fallthrough
   224  	case TypeNumber:
   225  		if num, err := strconv.ParseFloat(valueStr, 64); err == nil {
   226  			return num
   227  		}
   228  	case TypeBoolean:
   229  		fallthrough
   230  	case TypeBool:
   231  		if b, err := strconv.ParseBool(valueStr); err == nil {
   232  			return b
   233  		}
   234  	}
   235  	return valueStr
   236  }
   237  
   238  func getType(schema *spec.Schema) string {
   239  	if len(schema.Type) == 0 {
   240  		return ""
   241  	}
   242  	return schema.Type[0]
   243  }
   244  
   245  func contains(arr []string, obj string) bool {
   246  	for _, v := range arr {
   247  		if v == obj {
   248  			return true
   249  		}
   250  	}
   251  	return false
   252  }