github.com/josephspurrier/go-swagger@v0.2.1-0.20221129144919-1f672a142a00/scan/route_params.go (about)

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