@architect/inventory 2.2.0 → 2.2.1-RC.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## [2.2.1] 2021-11-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Adds HTTP route sorting, which should ensure Sandbox behaves much more like API Gateway despite how you've organized your `@http` pragma; fixes #977
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
5
13
|
## [2.2.0] 2021-11-16
|
|
6
14
|
|
|
7
15
|
### Added
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ let { join } = require('path')
|
|
|
2
2
|
let populate = require('./populate-lambda')
|
|
3
3
|
let asapSrc = require('../../lib/asap-src')
|
|
4
4
|
let validate = require('./validate')
|
|
5
|
+
let sort = require('./sort/http')
|
|
5
6
|
|
|
6
7
|
module.exports = function configureHTTP ({ arc, inventory, errors }) {
|
|
7
8
|
if (!arc.http) return null
|
|
@@ -67,7 +68,9 @@ module.exports = function configureHTTP ({ arc, inventory, errors }) {
|
|
|
67
68
|
// Impure but it's way less complicated to just do this
|
|
68
69
|
inventory._project.rootHandler = rootHandler
|
|
69
70
|
|
|
71
|
+
// Final steps: validate, then ensure the route order works as API Gateway would
|
|
70
72
|
validate.http(http, errors)
|
|
73
|
+
http = sort(http)
|
|
71
74
|
|
|
72
75
|
return http
|
|
73
76
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
let methods = require('../../../lib/http-methods')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP route sorter; this is a multifurcating tree, so we'll do a few passes
|
|
5
|
+
* Broadly in the theme of most to least specific:
|
|
6
|
+
* - First, by method, with `any` last
|
|
7
|
+
* - Then by path depth (descending)
|
|
8
|
+
* - Within each depth downrank for contained params
|
|
9
|
+
* - Then sort alphabetically
|
|
10
|
+
* - Then, ensure trailing captures are last
|
|
11
|
+
* - Finally, ensure root captures rank below a root literal
|
|
12
|
+
*/
|
|
13
|
+
module.exports = function sortHTTP (http) {
|
|
14
|
+
// Construct the tree from HTTP methods
|
|
15
|
+
let tree = {}
|
|
16
|
+
http.forEach(({ method, path }) => {
|
|
17
|
+
if (!tree[method]) tree[method] = []
|
|
18
|
+
let parts = path.split('/').filter(Boolean)
|
|
19
|
+
let depth = parts.length
|
|
20
|
+
let item = { depth, path }
|
|
21
|
+
let param = /\/:/
|
|
22
|
+
if (path.match(param)) {
|
|
23
|
+
item.hasParam = true
|
|
24
|
+
item.paramIndex = path.match(param).index // If multiple, we want the earliest
|
|
25
|
+
}
|
|
26
|
+
if (parts.length) {
|
|
27
|
+
if (parts[depth - 1] === '*') item.trailingCapture = 'catchall'
|
|
28
|
+
if (parts[depth - 1].startsWith(':')) item.trailingCapture = 'param'
|
|
29
|
+
}
|
|
30
|
+
tree[method].push(item)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Multi-pass route sort
|
|
34
|
+
let sorted = []
|
|
35
|
+
methods.forEach(method => {
|
|
36
|
+
if (!tree[method]) return
|
|
37
|
+
/* istanbul ignore next: random test shuffles may not trigger all paths */
|
|
38
|
+
tree[method]
|
|
39
|
+
// Sort by depth
|
|
40
|
+
.sort((a, b) => b.depth - a.depth)
|
|
41
|
+
// Sort within a given depth
|
|
42
|
+
.sort((a, b) => {
|
|
43
|
+
// Handle root (depth: 0)
|
|
44
|
+
if (a.depth - b.depth < 0) return
|
|
45
|
+
if (a.hasParam && b.hasParam) {
|
|
46
|
+
// Sort at the earliest param
|
|
47
|
+
if (a.paramIndex < b.paramIndex) return 1
|
|
48
|
+
if (a.paramIndex > b.paramIndex) return -1
|
|
49
|
+
// Then sort alphabetically
|
|
50
|
+
if (a.path < b.path) return -1
|
|
51
|
+
if (a.path > b.path) return 1
|
|
52
|
+
}
|
|
53
|
+
if (a.hasParam) return 1
|
|
54
|
+
if (b.hasParam) return -1
|
|
55
|
+
if (a.path < b.path) return -1
|
|
56
|
+
if (a.path > b.path) return 1
|
|
57
|
+
})
|
|
58
|
+
.sort((a, b) => {
|
|
59
|
+
if (!a.depth && b.depth === 1 && b.trailingCapture) return -1
|
|
60
|
+
if (a.depth - b.depth < 0) return
|
|
61
|
+
if (a.trailingCapture) return 1
|
|
62
|
+
if (b.trailingCapture) return -1
|
|
63
|
+
})
|
|
64
|
+
tree[method].forEach(({ path }) => {
|
|
65
|
+
let route = http.find(i => i.method === method && i.path === path)
|
|
66
|
+
sorted.push(route)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return sorted
|
|
71
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
let { unique } = require('./_lib')
|
|
2
|
+
let methods = require('../../../lib/http-methods')
|
|
2
3
|
|
|
3
4
|
module.exports = function validateHTTP (http, errors) {
|
|
4
5
|
if (http.length) {
|
|
5
6
|
unique(http, '@http routes', errors)
|
|
6
7
|
|
|
7
|
-
let methods = [ 'get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'any' ]
|
|
8
8
|
let validMethod = str => methods.includes(str.toLowerCase())
|
|
9
9
|
let validPath = str => str.match(/^\/[a-zA-Z0-9/\-:._\*]*$/)
|
|
10
10
|
http.forEach(route => {
|