github.com/josephspurrier/go-swagger@v0.2.1-0.20221129144919-1f672a142a00/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 }