• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

micro-lc / back-kit-engine / 7545378929

16 Jan 2024 05:34PM UTC coverage: 94.984% (-0.4%) from 95.365%
7545378929

push

github

web-flow
Http rerouting (#25)

* WIP

* rerout on fetch

* Add tests

* Collapse branches

* Remove branch

* v1.0.15-rc1

* Refactor

* Update changelog

* Extend interface

* Add tests

* coverage threshold to 90

* v1.0.15-rc2

---------

Co-authored-by: Claudio Benedetti <claudio.benedetti@mia-platform.eu>

320 of 352 branches covered (0.0%)

Branch coverage included in aggregate %.

61 of 67 new or added lines in 3 files covered. (91.04%)

854 of 884 relevant lines covered (96.61%)

21.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.68
/src/utils/http-client/with-rerouting.ts
1
import {HttpClientSupport, HttpMethods} from './http-client'
48✔
2

3
export type RerouteRule = {
4
  from: string | RegExp | {url: string | RegExp, method: HttpMethods}
5
  to: string
6
}
7
type ValidRerouteRule = {
8
  from: {url: RegExp, method: HttpMethods}
9
  to: string
10
}
11

12
type ReroutingFunction = (url: string, method: HttpMethods) => string
13

14
const methods: HttpMethods[] = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']
6✔
15

16
function isHttpMethod (input: unknown): input is HttpMethods {
17
  return typeof input === 'string' && methods.includes(input as HttpMethods)
270✔
18
}
19
function isValidObject (input: unknown): input is Record<string, unknown> {
20
  return typeof input === 'object'
496✔
21
    && !Array.isArray(input)
22
    && input !== null
23
}
24
function isValidRerouteRule (input: unknown): input is ValidRerouteRule { 
25
  const isValid = isValidObject(input) && 'from' in input && 'to' in input
224✔
26
  if (!isValid) {
224✔
27
    return false
4✔
28
  }
29
  const {from, to} = input
220✔
30
  return isValidObject(from)
220✔
31
    && 'method' in from && isHttpMethod(from.method)
32
    && 'url' in from && from.url instanceof RegExp
33
    && typeof to === 'string'
34
}
35

36
const getRegexGroups = (match: RegExpMatchArray) => {
6✔
37
  const namedGroups = {...match.groups}
56✔
38
  const positionalGroups = match.slice(1)?.filter((g) => !Object.values(namedGroups).includes(g)) ?? []
56!
39
  return positionalGroups.reduce((acc, group, idx) => ({...acc, [`${idx+1}`]: group}), namedGroups)
56✔
40
}
41

42
const getRouter: (rules: ValidRerouteRule[]) => ReroutingFunction  = (rules) => {
6✔
43
  return (inputUrl, inputMethod) => {
28✔
44
    let match: RegExpMatchArray | null = null
72✔
45
    for (const {to: targetUrl, from: {method, url: urlRegex}} of rules) {
72✔
46
      match = inputUrl.match(urlRegex)
446✔
47
      if (inputMethod === method && match) {
446✔
48
        const groups = getRegexGroups(match)
56✔
49
        if (Object.keys(groups).length > 0) {
56✔
50
          return Object
8✔
51
            .entries(groups)
52
            .reduce((acc, [toReplace, replaceWith]) => acc.replaceAll(`$${toReplace}`, replaceWith), targetUrl)
12✔
53
        }
54
        return targetUrl
48✔
55
      }
56
    }
57
    return inputUrl
16✔
58
  }
59
}
60

61

62
const completeRules = (rules: RerouteRule[]): ValidRerouteRule[] => {
6✔
63
  return rules.reduce<ValidRerouteRule[]>((acc, rule) => {
28✔
64
    if (isValidRerouteRule(rule)) {
54✔
65
      acc.push(rule)
2✔
66
    }
52✔
67
    else if (isValidObject(rule.from) && typeof rule.from.url === 'string') {
74✔
68
      rule.from.url = new RegExp(rule.from.url)
20✔
69
      if (isValidRerouteRule(rule)) {
20✔
70
        acc.push(rule)
18✔
71
      }
72
    }
32✔
73
    else if (typeof rule.from === 'string' || rule.from instanceof RegExp) {
36✔
74
      const url = typeof rule.from === 'string' ? new RegExp(rule.from) : rule.from
30✔
75
      acc.push(
30✔
76
        ...methods
77
          .map(method => ({from: {method, url}, to: rule.to}))
150✔
78
          .filter(isValidRerouteRule)
79
      )
80
    }
81
    return acc
54✔
82
  }, [])
83
}
84

85
export function withRerouting (this: HttpClientSupport): void | (() => void) {
86
  type Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
87
  
88
  const {proxyWindow = window, reroutingRules} = this
30✔
89
  const {fetch} = proxyWindow
30✔
90
  if (!Array.isArray(reroutingRules) || reroutingRules.length === 0) {
30✔
91
    return
2✔
92
  }
93

94
  const router = getRouter(completeRules(reroutingRules))
28✔
95
  const reroutedFetch: Fetch = (input, init, ...rest) => {
28✔
96
    if (init && isHttpMethod(init.method)) {
78✔
97
      if (typeof input === 'string' || input instanceof URL) {
72!
98
        const url = new URL(input)
72✔
99
        url.pathname = router(url.pathname, init.method)
72✔
100
        return fetch(typeof input === 'string' ? url.toString() : url, init, ...rest)
72!
NEW
101
      }
×
102
      else if (input instanceof Request) {
NEW
103
        const {url: reqUrl, ...reqRest} = input
×
NEW
104
        const url = new URL(reqUrl)
×
NEW
105
        url.pathname = router(url.pathname, init.method)
×
NEW
106
        const reroutedReq = {url: url.toString(), ...reqRest}
×
NEW
107
        return fetch(reroutedReq, init, ...rest)
×
108
      }
109
    }
110

111
    return fetch(input, init, ...rest)
6✔
112
  }
113
  
114
  this.proxyWindow = {...proxyWindow, fetch: reroutedFetch}
28✔
115
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc