Skip to content

Commit ddb2e16

Browse files
committed
feat: add support for parameter prefixes in order to be able to explicitly bind to a desired origin
This feature allows us to use parameters from different sources. See example for testing PUT methods in the tests. BREAKING CHANGE: every :parameter should be revised and modified to be :b.parameter to bind to request body or :p.parameter to bind to path parameter. See also #8
1 parent 26e4594 commit ddb2e16

File tree

6 files changed

+44
-35
lines changed

6 files changed

+44
-35
lines changed

README.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,23 @@ Generates the endpoints (or a whole app) from a mapping (SQL query -> URL)
2323
FROM categories
2424
post: >-
2525
INSERT INTO categories(name, slug, created_at, created_by, updated_at, updated_by)
26-
VALUES (:name, :slug, NOW(), :userId, NOW(), :userId)
26+
VALUES (:b.name, :b.slug, NOW(), :b.userId, NOW(), :b.userId)
2727

2828
- path: /v1/categories/:categoryId
2929
get: >-
3030
SELECT id, name, name_ru, slug
3131
FROM categories
32-
WHERE id = :categoryId
32+
WHERE id = :p.categoryId
3333
put: >-
3434
UPDATE categories
35-
SET name = :name, name_ru = :nameRu, slug = :slug, updated_at = NOW(), updated_by = :userId
36-
WHERE id = :categoryId
35+
SET name = :b.name, name_ru = :b.nameRu, slug = :b.slug, updated_at = NOW(), updated_by = :b.userId
36+
WHERE id = :p.categoryId
3737
delete: >-
3838
DELETE
3939
FROM categories
40-
WHERE id = :categoryId
40+
WHERE id = :p.categoryId
4141
```
42+
Note that the queries use a little unusual named parameters: `:b.name`, `p.categoryId`, etc The prefixes `b` (body) and `p` (path) are used here in order to bind to parameters from the appropriate sources. The prefixes are needed only during code generation and they will absent from the resulted code.
4243

4344
1. Generate code
4445
```console

examples/js/endpoints.yaml

+13-13
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
FROM collections_series cs
88
JOIN series s
99
ON s.id = cs.series_id
10-
WHERE cs.collection_id = :collectionId
10+
WHERE cs.collection_id = :p.collectionId
1111
1212
- path: /v1/categories
1313
get_list: >-
@@ -28,13 +28,13 @@
2828
, updated_by
2929
)
3030
VALUES
31-
( :name
32-
, :nameRu
33-
, :slug
31+
( :b.name
32+
, :b.nameRu
33+
, :b.slug
3434
, NOW()
35-
, :userId
35+
, :b.userId
3636
, NOW()
37-
, :userId
37+
, :b.userId
3838
)
3939
4040
- path: /v1/categories/:categoryId
@@ -44,16 +44,16 @@
4444
, name_ru
4545
, slug
4646
FROM categories
47-
WHERE id = :categoryId
47+
WHERE id = :p.categoryId
4848
put: >-
4949
UPDATE categories
50-
SET name = :name
51-
, name_ru = :nameRu
52-
, slug = :slug
50+
SET name = :b.name
51+
, name_ru = :b.nameRu
52+
, slug = :b.slug
5353
, updated_at = NOW()
54-
, updated_by = :userId
55-
WHERE id = :categoryId
54+
, updated_by = :b.userId
55+
WHERE id = :p.categoryId
5656
delete: >-
5757
DELETE
5858
FROM categories
59-
WHERE id = :categoryId
59+
WHERE id = :p.categoryId

examples/js/routes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ app.get('/v1/categories/:categoryId', (req, res) => {
7979
app.put('/v1/categories/:categoryId', (req, res) => {
8080
pool.query(
8181
'UPDATE categories SET name = :name , name_ru = :nameRu , slug = :slug , updated_at = NOW() , updated_by = :userId WHERE id = :categoryId',
82-
{ "name": req.body.name, "nameRu": req.body.nameRu, "slug": req.body.slug, "userId": req.body.userId, "categoryId": req.body.categoryId },
82+
{ "name": req.body.name, "nameRu": req.body.nameRu, "slug": req.body.slug, "userId": req.body.userId, "categoryId": req.params.categoryId },
8383
(err, rows, fields) => {
8484
if (err) {
8585
throw err

src/cli.js

+19-11
Original file line numberDiff line numberDiff line change
@@ -32,50 +32,58 @@ const createApp = async (destDir, fileName) => {
3232
// "SELECT *\n FROM foo" => "SELECT * FROM foo"
3333
const flattenQuery = (query) => query.replace(/\n[ ]*/g, ' ');
3434

35+
// "WHERE id = :p.categoryId OR id = :b.id" => "WHERE id = :categoryId OR id = :id"
36+
const removePlaceholders = (query) => query.replace(/:[pb]\./g, ':');
37+
3538
const createEndpoints = async (destDir, fileName, config) => {
3639
console.log('Generate', fileName);
3740
const resultFile = path.join(destDir, fileName);
3841

3942
for (let endpoint of config) {
4043
if (endpoint.hasOwnProperty('get')) {
41-
console.log('GET', endpoint.path, '=>', flattenQuery(endpoint.get));
44+
console.log('GET', endpoint.path, '=>', removePlaceholders(flattenQuery(endpoint.get)));
4245
} else if (endpoint.hasOwnProperty('get_list')) {
43-
console.log('GET', endpoint.path, '=>', flattenQuery(endpoint.get_list));
46+
console.log('GET', endpoint.path, '=>', removePlaceholders(flattenQuery(endpoint.get_list)));
4447
}
4548
if (endpoint.hasOwnProperty('post')) {
46-
console.log('POST', endpoint.path, '=>', flattenQuery(endpoint.post));
49+
console.log('POST', endpoint.path, '=>', removePlaceholders(flattenQuery(endpoint.post)));
4750
}
4851
if (endpoint.hasOwnProperty('put')) {
49-
console.log('PUT', endpoint.path, '=>', flattenQuery(endpoint.put));
52+
console.log('PUT', endpoint.path, '=>', removePlaceholders(flattenQuery(endpoint.put)));
5053
}
5154
if (endpoint.hasOwnProperty('delete')) {
52-
console.log('DELETE', endpoint.path, '=>', flattenQuery(endpoint.delete));
55+
console.log('DELETE', endpoint.path, '=>', removePlaceholders(flattenQuery(endpoint.delete)));
5356
}
5457
}
5558

59+
const placeholdersMap = {
60+
'p': 'req.params',
61+
'b': 'req.body'
62+
}
63+
5664
const resultedCode = await ejs.renderFile(
5765
__dirname + '/templates/routes.js.ejs',
5866
{
5967
"endpoints": config,
6068

61-
// "... WHERE id = :id" => [ ":id" ] => [ "id" ]
69+
// "... WHERE id = :p.id" => [ "p.id" ] => [ "p.id" ]
6270
"extractParams": (query) => {
63-
const params = query.match(/:\w+/g) || [];
71+
const params = query.match(/:[pb]\.\w+/g) || [];
6472
return params.length > 0
6573
? params.map(p => p.substring(1))
6674
: params;
6775
},
6876

69-
// [ "page", "num" ] => '{ "page" : req.params.page, "num": req.params.num }'
70-
"formatParams": (params, targetObject) => {
77+
// [ "p.page", "b.num" ] => '{ "page" : req.params.page, "num": req.body.num }'
78+
"formatParams": (params) => {
7179
return params.length > 0
72-
? '{ ' + Array.from(new Set(params), p => `"${p}": ${targetObject}.${p}`).join(', ') + ' }'
80+
? '{ ' + Array.from(new Set(params), p => `"${p.substring(2)}": ${placeholdersMap[p.substring(0, 1)]}.${p.substring(2)}`).join(', ') + ' }'
7381
: params;
7482
},
7583

7684
// "SELECT *\n FROM foo" => "'SELECT * FROM foo'"
7785
"formatQuery": (query) => {
78-
return "'" + flattenQuery(query) + "'";
86+
return "'" + removePlaceholders(flattenQuery(query)) + "'";
7987
}
8088
}
8189
);

src/templates/routes.js.ejs

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ endpoints.forEach(function(endpoint) {
1010
%>
1111
app.get('<%- endpoint.path %>', (req, res) => {
1212
pool.query(
13-
<%- formatQuery(sql) %>,<%- params.length > 0 ? '\n ' + formatParams(params, 'req.params') + ',' : '' %>
13+
<%- formatQuery(sql) %>,<%- params.length > 0 ? '\n ' + formatParams(params) + ',' : '' %>
1414
(err, rows, fields) => {
1515
if (err) {
1616
throw err
@@ -34,7 +34,7 @@ app.get('<%- endpoint.path %>', (req, res) => {
3434
%>
3535
app.post('<%- endpoint.path %>', (req, res) => {
3636
pool.query(
37-
<%- formatQuery(endpoint.post) %>,<%- params.length > 0 ? '\n ' + formatParams(params, 'req.body') + ',' : '' %>
37+
<%- formatQuery(endpoint.post) %>,<%- params.length > 0 ? '\n ' + formatParams(params) + ',' : '' %>
3838
(err, rows, fields) => {
3939
if (err) {
4040
throw err
@@ -50,7 +50,7 @@ app.post('<%- endpoint.path %>', (req, res) => {
5050
%>
5151
app.put('<%- endpoint.path %>', (req, res) => {
5252
pool.query(
53-
<%- formatQuery(endpoint.put) %>,<%- params.length > 0 ? '\n ' + formatParams(params, 'req.body') + ',' : '' %>
53+
<%- formatQuery(endpoint.put) %>,<%- params.length > 0 ? '\n ' + formatParams(params) + ',' : '' %>
5454
(err, rows, fields) => {
5555
if (err) {
5656
throw err
@@ -66,7 +66,7 @@ app.put('<%- endpoint.path %>', (req, res) => {
6666
%>
6767
app.delete('<%- endpoint.path %>', (req, res) => {
6868
pool.query(
69-
<%- formatQuery(endpoint.delete) %>,<%- params.length > 0 ? '\n ' + formatParams(params, 'req.params') + ',' : '' %>
69+
<%- formatQuery(endpoint.delete) %>,<%- params.length > 0 ? '\n ' + formatParams(params) + ',' : '' %>
7070
(err, rows, fields) => {
7171
if (err) {
7272
throw err

tests/crud.robot

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ GET should return not found
2929
Should Be Equal ${response.headers['Content-Type']} application/json; charset=utf-8
3030

3131
PUT should update an object
32-
&{payload}= Create Dictionary name=Fauna nameRu=Фауна slug=fauna userId=1 categoryId=1
32+
&{payload}= Create Dictionary name=Fauna nameRu=Фауна slug=fauna userId=1
3333
${response}= Put Request api /v1/categories/1 json=${payload}
3434
Status Should Be 204 ${response}
3535

0 commit comments

Comments
 (0)