github.com/go-swagger/go-swagger@v0.31.0/codescan/route_params.go (about)

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