/
params.go
160 lines (137 loc) · 4.45 KB
/
params.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright 2020 Torben Schinke
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package http
import (
"fmt"
"github.com/golangee/reflectplus"
"regexp"
)
var regexParamNames = regexp.MustCompile(":\\w+")
type paramType int
const (
ptUnknown paramType = 0
ptCtx = 1
ptPath = 2
ptQuery = 3
ptHeader = 4
ptForm = 5
ptBody = 6
ptRequest = 7
ptResponseWriter = 8
)
type methodParam struct {
paramType paramType
idx int
param reflectplus.Param
alias string
}
func (m methodParam) Alias() string {
if len(m.alias) > 0 {
return m.alias
}
return m.param.Name
}
// scanMethodParams validates the annotated method and returns unified meta data about the kind of input
func scanMethodParams(parent reflectplus.Struct, method reflectplus.Method) ([]methodParam, error) {
paramsToDefine := map[string]methodParam{}
for idx, p := range method.Params {
paramsToDefine[p.Name] = methodParam{
idx: idx,
param: p,
}
}
var res []methodParam
// pick up hardcoded injection parameter
for _, p := range method.Params {
if p.Type.ImportPath == "context" && p.Type.Identifier == "Context" {
tmp := paramsToDefine[p.Name]
tmp.paramType = ptCtx
res = append(res, tmp)
delete(paramsToDefine, p.Name)
}
if p.Type.ImportPath == "net/http" && p.Type.Identifier == "ResponseWriter" {
tmp := paramsToDefine[p.Name]
tmp.paramType = ptResponseWriter
res = append(res, tmp)
delete(paramsToDefine, p.Name)
}
if p.Type.ImportPath == "net/http" && p.Type.Identifier == "Request" && p.Type.Stars == 1 {
tmp := paramsToDefine[p.Name]
tmp.paramType = ptRequest
res = append(res, tmp)
delete(paramsToDefine, p.Name)
}
}
// collect prefix route variables from parent
for _, p := range parent.GetAnnotations().FindAll(AnnotationRoute) {
// collect postfix route variables from method
for _, a := range method.GetAnnotations().FindAll(AnnotationRoute) {
actualRoute := joinPaths(p.Value(), a.Value())
if len(actualRoute) == 0 {
return nil, fmt.Errorf("method has an empty route")
}
for _, routeParam := range paramNamesFromRoute(actualRoute) {
if _, has := paramsToDefine[routeParam]; !has {
return nil, fmt.Errorf("the named route variable '%s' has no matching method parameter", routeParam)
}
tmp := paramsToDefine[routeParam]
tmp.paramType = ptPath
res = append(res, tmp)
delete(paramsToDefine, routeParam)
}
}
}
// collect query params
for _, a := range method.GetAnnotations().FindAll(AnnotationQueryParam) {
name := a.Value()
if len(name) == 0 {
return nil, fmt.Errorf("value of '%s' must not be empty", AnnotationQueryParam)
}
if _, has := paramsToDefine[name]; !has {
return nil, fmt.Errorf("the query parameter '%s' has no matching method parameter", name)
}
tmp := paramsToDefine[name]
tmp.alias = a.AsString("alias")
tmp.paramType = ptQuery
res = append(res, tmp)
delete(paramsToDefine, name)
}
// collect header params
for _, a := range method.GetAnnotations().FindAll(AnnotationHeaderParam) {
name := a.Value()
if len(name) == 0 {
return nil, fmt.Errorf("value of '%s' must not be empty", AnnotationHeaderParam)
}
if _, has := paramsToDefine[name]; !has {
return nil, fmt.Errorf("the header parameter '%s' has no matching method parameter", name)
}
tmp := paramsToDefine[name]
tmp.alias = a.AsString("alias")
tmp.paramType = ptHeader
res = append(res, tmp)
delete(paramsToDefine, name)
}
// check for parameters, which have not been defined yet
for _, p := range paramsToDefine {
return nil, fmt.Errorf("method parameter '%s' has not been mapped to a request parameter", p.param.Name)
}
return res, nil
}
func paramNamesFromRoute(route string) []string {
names := regexParamNames.FindAllString(string(route), -1)
for i, n := range names {
names[i] = n[1:]
}
return names
}