@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@architect/inventory",
3
- "version": "2.2.0",
3
+ "version": "2.2.1-RC.0",
4
4
  "description": "Architect project resource enumeration utility",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -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 => {
@@ -0,0 +1,2 @@
1
+ // Any must come last for Sandbox route sorting purposes
2
+ module.exports = [ 'get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'any' ]