Line | Hits | Source |
---|---|---|
1 | 1 | fs = require 'fs' |
2 | ||
3 | 1 | protagonist = require 'protagonist' |
4 | 1 | express = require 'express' |
5 | 1 | walker = require './walker' |
6 | 1 | SslSupport = require './ssl-support' |
7 | 1 | CorsSupport = require './cors-support' |
8 | ||
9 | 1 | class ApiMock |
10 | 1 | constructor: (config) -> |
11 | 10 | protagonist = config['protagonist'] if config['protagonist'] |
12 | 10 | express = config['express'] if config['express'] |
13 | 10 | @blueprintPath = config['blueprintPath'] if config['blueprintPath'] |
14 | ||
15 | 10 | if not @blueprintPath? |
16 | 1 | throw new Error "No blueprint path provided." |
17 | ||
18 | 9 | @configuration = config |
19 | 9 | @app = express() |
20 | ||
21 | 9 | if @configuration.options['ssl-enable'] |
22 | 1 | sslSupport = new SslSupport( |
23 | @app, | |
24 | port: @configuration.options['ssl-port'], | |
25 | host: @configuration.options['ssl-host'], | |
26 | cert: @configuration.options['ssl-cert'], | |
27 | key: @configuration.options['ssl-key'] | |
28 | ) | |
29 | ||
30 | 9 | if !@configuration.options['cors-disable'] |
31 | 8 | corsSupport = new CorsSupport @app |
32 | ||
33 | run: () -> | |
34 | 6 | app = @app |
35 | ||
36 | 6 | try |
37 | 6 | data = fs.readFileSync @blueprintPath, 'utf8' |
38 | catch e | |
39 | 1 | throw e |
40 | ||
41 | # Get JSON representation of the blueprint file | |
42 | 5 | ast_json = "" |
43 | 5 | protagonist.parse data, (error, result) => |
44 | 5 | if error? then throw error |
45 | 4 | ast_json = result.ast |
46 | ||
47 | # Walk AST, add routes to app | |
48 | 4 | try |
49 | 4 | walker app, ast_json['resourceGroups'] |
50 | catch error | |
51 | 1 | throw error |
52 | ||
53 | # start server | |
54 | 3 | try |
55 | 3 | app.listen( if @configuration?.options?.port? then @configuration.options.port else 3000 ) |
56 | catch error | |
57 | ||
58 | ||
59 | 1 | module.exports = ApiMock |
60 |
Line | Hits | Source |
---|---|---|
1 | 1 | inheritHeaders = require './inherit-headers' |
2 | ||
3 | # Transforms API Blueprint example to an array of Expected | |
4 | # HTTP Request and Response body and headers | |
5 | 1 | exampleToHttpPayloadPair = (example, inheritingHeaders = {}) -> |
6 | ||
7 | 7 | result = |
8 | warnings: [] | |
9 | errors: [] | |
10 | pair: {} | |
11 | ||
12 | 7 | request = {} |
13 | 7 | responses = {} |
14 | ||
15 | 7 | if example['requests'].length > 1 |
16 | 1 | text = "Multiple requests, using first." |
17 | 1 | result['warnings'].push text |
18 | ||
19 | 7 | if example['responses'].length == 0 |
20 | 1 | text = "No response available. Can't create HTTP transaction." |
21 | 1 | result['warnings'].push text |
22 | else | |
23 | 6 | selectedRequest = example['requests'][0] |
24 | ||
25 | 6 | if example['requests'].length == 0 |
26 | 1 | selectedRequest = |
27 | body: "" | |
28 | headers: {} | |
29 | ||
30 | 6 | request['body'] = selectedRequest['body'] |
31 | 6 | request['headers'] = inheritHeaders selectedRequest['headers'], inheritingHeaders |
32 | ||
33 | 6 | for selectedResponse in example['responses'] |
34 | 7 | response = {} |
35 | ||
36 | 7 | response['body'] = selectedResponse['body'] |
37 | 7 | response['headers'] = inheritHeaders selectedResponse['headers'], inheritingHeaders |
38 | 7 | response['status'] = selectedResponse['name'] |
39 | 7 | if selectedResponse['schema'] != "" |
40 | 6 | response['schema'] = selectedResponse['schema'] |
41 | ||
42 | 7 | responses[response['status']] = response |
43 | ||
44 | 6 | result['pair']['request'] = request |
45 | 6 | result['pair']['responses'] = responses |
46 | ||
47 | 7 | return result |
48 | ||
49 | 1 | module.exports = exampleToHttpPayloadPair |
50 | ||
51 |
Line | Hits | Source |
---|---|---|
1 | 1 | ut = require 'uri-template' |
2 | ||
3 | 1 | expandUriTemplateWithParameters = (uriTemplate, parameters) -> |
4 | 13 | result = |
5 | errors: [] | |
6 | warnings: [] | |
7 | uri: null | |
8 | 13 | try |
9 | 13 | parsed = ut.parse uriTemplate |
10 | catch e | |
11 | 1 | text = 'Failed to parse URI template' |
12 | 1 | result['errors'].push text |
13 | 1 | return result |
14 | ||
15 | # get parameters from expression object | |
16 | 12 | uriParameters = [] |
17 | 12 | for expression in parsed['expressions'] |
18 | 10 | for param in expression['params'] |
19 | 10 | uriParameters.push param['name'] |
20 | ||
21 | # check if all parameters have an expression in URI | |
22 | 12 | for parameter in Object.keys(parameters) |
23 | 11 | if uriParameters.indexOf(parameter) == -1 |
24 | 2 | text = "URI template doesn\'t contain expression for parameter" + \ |
25 | " '" + parameter + "'" | |
26 | 2 | result['warnings'].push text |
27 | ||
28 | 12 | if parsed['expressions'].length == 0 |
29 | 2 | result['uri'] = uriTemplate |
30 | else | |
31 | 10 | ambigous = false |
32 | ||
33 | 10 | for uriParameter in uriParameters |
34 | 10 | if Object.keys(parameters).indexOf(uriParameter) == -1 |
35 | 1 | ambigous = true |
36 | 1 | text = "Ambigous URI template. " + \ |
37 | "Parameter not defined:" + \ | |
38 | "'" + uriParameter + "'" | |
39 | 1 | result['warnings'].push text |
40 | ||
41 | 10 | if ambigous == false |
42 | 9 | toExpand = {} |
43 | 9 | for uriParameter in uriParameters |
44 | 9 | param = parameters[uriParameter] |
45 | 9 | if param['required'] == true |
46 | 5 | if param['example'] == undefined |
47 | 1 | ambigous = true |
48 | 1 | text = "Ambigous URI template. " + \ |
49 | "No example value for parameter:" + \ | |
50 | "'" + uriParameter + "'" | |
51 | 1 | result['warnings'].push text |
52 | else | |
53 | 4 | toExpand[uriParameter] = param['example'] |
54 | else | |
55 | 4 | if param['example'] != undefined |
56 | 2 | toExpand[uriParameter] = param['example'] |
57 | else if param['default'] != undefined | |
58 | 1 | toExpand[uriParameter] = param['default'] |
59 | ||
60 | 10 | if ambigous == false |
61 | 8 | result['uri'] = parsed.expand toExpand |
62 | ||
63 | 12 | return result |
64 | ||
65 | 1 | module.exports = expandUriTemplateWithParameters |
Line | Hits | Source |
---|---|---|
1 | 1 | inheritHeaders = (actualHeaders, inheritingHeaders) -> |
2 | 39 | for name, params of inheritingHeaders |
3 | 2 | if actualHeaders[name] == undefined |
4 | 1 | actualHeaders[name] = params |
5 | ||
6 | 39 | return actualHeaders |
7 | ||
8 | 1 | module.exports = inheritHeaders |
Line | Hits | Source |
---|---|---|
1 | 1 | inheritParameters = (actualParameters, inheritingParameters) -> |
2 | 26 | for name, params of inheritingParameters |
3 | 2 | if actualParameters[name] == undefined |
4 | 1 | actualParameters[name] = params |
5 | ||
6 | 26 | return actualParameters |
7 | ||
8 | 1 | module.exports = inheritParameters |
Line | Hits | Source |
---|---|---|
1 | 1 | inheritHeaders = require './inherit-headers' |
2 | 1 | inheritParameters = require './inherit-parameters' |
3 | 1 | expandUriTemplateWithParameters = require './expand-uri-template-with-parameters' |
4 | 1 | exampleToHttpPayloadPair = require './example-to-http-payload-pair' |
5 | ||
6 | 1 | ut = require 'uri-template' |
7 | 1 | winston = require 'winston' |
8 | ||
9 | 1 | walker = (app, resourceGroups) -> |
10 | ||
11 | 5 | sendResponse = (responses) -> |
12 | 25 | (req, res) -> |
13 | # default response | |
14 | 3 | response = responses[Object.keys(responses)[0]] |
15 | ||
16 | # try to find matching response based on PREFER header | |
17 | 3 | if 'prefer' of req.headers |
18 | 2 | if req.headers['prefer'] of responses |
19 | 1 | response = responses[req.headers['prefer']] |
20 | else | |
21 | 1 | winston.warn("[#{req.url}] Preferrered response #{req.headers['prefer']} not found. Falling back to #{response.status}") |
22 | ||
23 | 3 | for header, value of response.headers |
24 | 3 | headerName = value['name'] |
25 | 3 | headerValue = value['value'] |
26 | 3 | res.setHeader headerName, headerValue |
27 | 3 | res.setHeader 'Content-Length', Buffer.byteLength(response.body) |
28 | 3 | res.send response.status, response.body |
29 | ||
30 | 5 | responses = [] |
31 | ||
32 | 5 | for group in resourceGroups |
33 | 5 | for resource in group['resources'] |
34 | 25 | for action in resource['actions'] |
35 | ||
36 | # headers and parameters can be specified higher up in the ast and inherited | |
37 | 25 | action['headers'] = inheritHeaders action['headers'], resource['headers'] |
38 | 25 | action['parameters'] = inheritParameters action['parameters'], resource['parameters'] |
39 | ||
40 | 25 | if resource['uriTemplate']? |
41 | # removes query parameters, and converts uri template params into what express expects | |
42 | # e.g. /templates/{templateId}/?status=good would become /templates/:templateId/ | |
43 | # TODO: replate with uri template processing | |
44 | 25 | path = resource['uriTemplate'].split('{?')[0].replace(new RegExp("}","g"), "").replace(new RegExp("{","g"), ":") |
45 | ||
46 | # the routes are generated | |
47 | 25 | for example in action['examples'] |
48 | 25 | payload = exampleToHttpPayloadPair example, action['headers'] |
49 | ||
50 | 25 | for warning in payload['warnings'] |
51 | 5 | winston.warn("[#{path}] #{warning}") |
52 | ||
53 | 25 | for error in payload['errors'] |
54 | 5 | winston.error("[#{path}] #{error}") |
55 | ||
56 | 25 | responses.push { |
57 | method: action.method | |
58 | path: path | |
59 | responses: payload['pair']['responses'] | |
60 | } | |
61 | ||
62 | #sort routes | |
63 | 5 | responses.sort (a,b) -> |
64 | 30 | if (a.path > b.path) |
65 | 10 | return -1 |
66 | 20 | if (a.path < b.path) |
67 | 10 | return 1 |
68 | 10 | return 0 |
69 | ||
70 | 5 | for response in responses |
71 | 25 | switch response.method |
72 | when 'GET' | |
73 | 5 | app.get response.path, sendResponse(response.responses) |
74 | when 'POST' | |
75 | 5 | app.post response.path, sendResponse(response.responses) |
76 | when 'PUT' | |
77 | 5 | app.put response.path, sendResponse(response.responses) |
78 | when 'DELETE' | |
79 | 5 | app.delete response.path, sendResponse(response.responses) |
80 | when 'PATCH' | |
81 | 5 | app.patch response.path, sendResponse(response.responses) |
82 | ||
83 | ||
84 | ||
85 | 1 | module.exports = walker |
86 |