forked from EconomistDigitalSolutions/ramlapi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathramlapi.go
144 lines (122 loc) · 3.5 KB
/
ramlapi.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
package ramlapi
import (
"errors"
"fmt"
"log"
"regexp"
"strings"
"github.com/buddhamagnet/raml"
)
var vizer *regexp.Regexp
func init() {
vizer = regexp.MustCompile("[^A-Za-z0-9]+")
}
// Parameter is a path or query string parameter.
type Parameter struct {
Key string
Type string
Pattern string
Required bool
}
// Endpoint describes an API endpoint.
type Endpoint struct {
Verb string
Handler string
Path string
Description string
URIParameters []*Parameter
QueryParameters []*Parameter
}
// String returns the string representation of an Endpoint.
func (e *Endpoint) String() string {
return fmt.Sprintf("verb: %s handler: %s path:%s\n", e.Verb, e.Handler, e.Path)
}
func (e *Endpoint) setQueryParameters(method *raml.Method) {
for name, param := range method.QueryParameters {
e.QueryParameters = append(e.QueryParameters, newParam(name, ¶m))
}
}
// Build takes a RAML API definition, a router and a routing map,
// and wires them all together.
func Build(api *raml.APIDefinition, routerFunc func(s *Endpoint)) error {
for name, resource := range api.Resources {
var resourceParams []*Parameter
err := processResource("", name, &resource, resourceParams, routerFunc)
if err != nil {
return err
}
}
return nil
}
// Process processes a RAML file and returns an API definition.
func Process(file string) (*raml.APIDefinition, error) {
routes, err := raml.ParseFile(file)
if err != nil {
return nil, fmt.Errorf("Failed parsing RAML file: %s\n", err.Error())
}
return routes, nil
}
// Variableize normalises RAML display names.
func Variableize(s string) string {
return vizer.ReplaceAllString(strings.Title(s), "")
}
func newParam(name string, param *raml.NamedParameter) *Parameter {
p := &Parameter{
Key: name,
Type: param.Type,
Required: param.Required,
}
if param.Pattern != nil {
p.Pattern = *param.Pattern
}
return p
}
func appendEndpoint(s []*Endpoint, method *raml.Method, params []*Parameter) ([]*Endpoint, error) {
if method.DisplayName == "" {
return s, errors.New("DisplayName property not set in RAML method")
}
if method != nil {
ep := &Endpoint{
Verb: method.Name,
Handler: Variableize(method.DisplayName),
Description: method.Description,
}
// set query parameters
ep.setQueryParameters(method)
// set uri parameters
for _, param := range params {
ep.URIParameters = append(ep.URIParameters, param)
}
s = append(s, ep)
}
return s, nil
}
// processResource recursively process resources and their nested children
// and returns the path so far for the children. The function takes a routerFunc
// as an argument that is invoked with the verb, resource path and handler as
// the resources are processed, so the calling code can use pat, mux, httprouter
// or whatever router they desire and we don't need to know about it.
func processResource(parent, name string, resource *raml.Resource, params []*Parameter, routerFunc func(s *Endpoint)) error {
var path = parent + name
var err error
for name, param := range resource.UriParameters {
params = append(params, newParam(name, ¶m))
}
s := make([]*Endpoint, 0, 6)
for _, m := range resource.Methods() {
s, err = appendEndpoint(s, m, params)
if err != nil {
return err
}
}
for _, ep := range s {
ep.Path = path
log.Println("processing", ep)
routerFunc(ep)
}
// Get all children.
for nestname, nested := range resource.Nested {
return processResource(path, nestname, nested, params, routerFunc)
}
return nil
}