github.com/kaisawind/go-swagger@v0.19.0/scan/route_params.go (about)

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