github.com/rzurga/go-swagger@v0.28.1-0.20211109195225-5d1f453ffa3a/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  	s.parameters = append(s.parameters, param)
   157  }
   158  
   159  func processSchema(data map[string]string, param *spec.Parameter) {
   160  	if param.Schema == nil {
   161  		return
   162  	}
   163  
   164  	var enumValues []string
   165  
   166  	for key, value := range data {
   167  		switch key {
   168  		case SchemaMinKey:
   169  			if t := getType(param.Schema); t == TypeNumber || t == TypeInteger {
   170  				v, _ := strconv.ParseFloat(value, 64)
   171  				param.Schema.Minimum = &v
   172  			}
   173  		case SchemaMaxKey:
   174  			if t := getType(param.Schema); t == TypeNumber || t == TypeInteger {
   175  				v, _ := strconv.ParseFloat(value, 64)
   176  				param.Schema.Maximum = &v
   177  			}
   178  		case SchemaMinLenKey:
   179  			if getType(param.Schema) == TypeArray {
   180  				v, _ := strconv.ParseInt(value, 10, 64)
   181  				param.Schema.MinLength = &v
   182  			}
   183  		case SchemaMaxLenKey:
   184  			if getType(param.Schema) == TypeArray {
   185  				v, _ := strconv.ParseInt(value, 10, 64)
   186  				param.Schema.MaxLength = &v
   187  			}
   188  		case SchemaEnumKey:
   189  			enumValues = strings.Split(value, ",")
   190  		case SchemaFormatKey:
   191  			param.Schema.Format = value
   192  		case SchemaDefaultKey:
   193  			param.Schema.Default = convert(param.Type, value)
   194  		}
   195  	}
   196  
   197  	if param.Description != "" {
   198  		param.Schema.Description = param.Description
   199  	}
   200  
   201  	convertEnum(param.Schema, enumValues)
   202  }
   203  
   204  func convertEnum(schema *spec.Schema, enumValues []string) {
   205  	if len(enumValues) == 0 {
   206  		return
   207  	}
   208  
   209  	var finalEnum []interface{}
   210  	for _, v := range enumValues {
   211  		finalEnum = append(finalEnum, convert(schema.Type[0], strings.TrimSpace(v)))
   212  	}
   213  	schema.Enum = finalEnum
   214  }
   215  
   216  func convert(typeStr, valueStr string) interface{} {
   217  	switch typeStr {
   218  	case TypeInteger:
   219  		fallthrough
   220  	case TypeNumber:
   221  		if num, err := strconv.ParseFloat(valueStr, 64); err == nil {
   222  			return num
   223  		}
   224  	case TypeBoolean:
   225  		fallthrough
   226  	case TypeBool:
   227  		if b, err := strconv.ParseBool(valueStr); err == nil {
   228  			return b
   229  		}
   230  	}
   231  	return valueStr
   232  }
   233  
   234  func getType(schema *spec.Schema) string {
   235  	if len(schema.Type) == 0 {
   236  		return ""
   237  	}
   238  	return schema.Type[0]
   239  }
   240  
   241  func contains(arr []string, obj string) bool {
   242  	for _, v := range arr {
   243  		if v == obj {
   244  			return true
   245  		}
   246  	}
   247  	return false
   248  }