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

pmcelhaney / counterfact / 14072284100

25 Mar 2025 11:47PM UTC coverage: 82.574% (+0.03%) from 82.544%
14072284100

push

github

web-flow
Merge pull request #1239 from pmcelhaney/interceptors

Middleware

1123 of 1254 branches covered (89.55%)

Branch coverage included in aggregate %.

154 of 165 new or added lines in 6 files covered. (93.33%)

7 existing lines in 3 files now uncovered.

3336 of 4146 relevant lines covered (80.46%)

63.12 hits per line

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

91.83
/src/server/module-tree.ts
1
import type { Module, MiddlewareFunction } from "./registry.js";
2✔
2

2✔
3
interface Route {
2✔
4
  methods: { [key: string]: string };
2✔
5
  path: string;
2✔
6
}
2✔
7

2✔
8
interface File {
2✔
9
  isWildcard: boolean;
2✔
10
  module: Module;
2✔
11
  name: string;
2✔
12
  rawName: string;
2✔
13
}
2✔
14

2✔
15
interface Directory {
2✔
16
  directories: { [key: string]: Directory };
2✔
17
  files: { [key: string]: File };
2✔
18
  isWildcard: boolean;
2✔
19
  name: string;
2✔
20
  rawName: string;
2✔
21
  middleware?: MiddlewareFunction;
2✔
22
}
2✔
23

2✔
24
interface Match {
2✔
25
  matchedPath: string;
2✔
26
  module: Module;
2✔
27
  pathVariables: { [key: string]: string };
2✔
28
}
2✔
29

2✔
30
function isDirectory(test: Directory | undefined): test is Directory {
108✔
31
  return test !== undefined;
108✔
32
}
108✔
33

2✔
34
export class ModuleTree {
2✔
35
  public readonly root: Directory = {
128✔
36
    directories: {},
128✔
37
    files: {},
128✔
38
    isWildcard: false,
128✔
39
    name: "",
128✔
40
    rawName: "",
128✔
41
  };
128✔
42

128✔
43
  private putDirectory(directory: Directory, segments: string[]): Directory {
128✔
44
    const [segment, ...remainingSegments] = segments;
268✔
45

268✔
46
    if (segment === undefined) {
268!
47
      throw new Error("segments array is empty");
×
48
    }
×
49

268✔
50
    if (remainingSegments.length === 0) {
268✔
51
      return directory;
162✔
52
    }
162✔
53

106✔
54
    const nextDirectory = (directory.directories[segment.toLowerCase()] ??= {
106✔
55
      directories: {},
106✔
56
      files: {},
106✔
57
      isWildcard: segment.startsWith("{"),
106✔
58
      name: segment.replace(/^\{(?<name>.*)\}$/u, "$<name>"),
106✔
59
      rawName: segment,
106✔
60
    });
106✔
61

106✔
62
    return this.putDirectory(nextDirectory, remainingSegments);
106✔
63
  }
106✔
64

128✔
65
  private addModuleToDirectory(
128✔
66
    directory: Directory | undefined,
162✔
67
    segments: string[],
162✔
68
    module: Module,
162✔
69
  ) {
162✔
70
    if (directory === undefined) {
162!
NEW
71
      return;
×
NEW
72
    }
×
73

162✔
74
    const targetDirectory = this.putDirectory(directory, segments);
162✔
75

162✔
76
    const filename = segments.at(-1);
162✔
77

162✔
78
    if (filename === undefined) {
162!
NEW
79
      throw new Error(
×
NEW
80
        "The file name (the last segment of the URL) is undefined. This is theoretically impossible but TypeScript can't enforce it.",
×
NEW
81
      );
×
NEW
82
    }
×
83

162✔
84
    targetDirectory.files[filename] = {
162✔
85
      isWildcard: filename.startsWith("{"),
162✔
86
      module,
162✔
87
      name: filename.replace(/^\{(?<name>.*)\}$/u, "$<name>"),
162✔
88
      rawName: filename,
162✔
89
    };
162✔
90
  }
162✔
91

128✔
92
  public add(url: string, module: Module) {
128✔
93
    this.addModuleToDirectory(this.root, url.split("/").slice(1), module);
162✔
94
  }
162✔
95

128✔
96
  private removeModuleFromDirectory(
128✔
97
    directory: Directory | undefined,
8✔
98
    segments: string[],
8✔
99
  ) {
8✔
100
    if (!isDirectory(directory)) {
8!
101
      return;
×
102
    }
×
103

8✔
104
    const [segment, ...remainingSegments] = segments;
8✔
105

8✔
106
    if (segment === undefined) {
8!
107
      return;
×
108
    }
×
109

8✔
110
    if (remainingSegments.length === 0) {
8✔
111
      delete directory.files[segment.toLowerCase()];
6✔
112

6✔
113
      return;
6✔
114
    }
6✔
115

2✔
116
    this.removeModuleFromDirectory(
2✔
117
      directory.directories[segment.toLowerCase()],
2✔
118
      remainingSegments,
2✔
119
    );
2✔
120
  }
2✔
121

128✔
122
  public remove(url: string) {
128✔
123
    const segments = url.split("/").slice(1);
6✔
124

6✔
125
    this.removeModuleFromDirectory(this.root, segments);
6✔
126
  }
6✔
127

128✔
128
  private fileModuleDefined(file: File, method: string) {
128✔
129
    return (file.module as { [key: string]: any })[method] !== undefined;
30✔
130
  }
30✔
131

128✔
132
  private buildMatch(
128✔
133
    directory: Directory,
210✔
134
    segment: string,
210✔
135
    pathVariables: { [key: string]: string },
210✔
136
    matchedPath: string,
210✔
137
    method: string,
210✔
138
  ) {
210✔
139
    const match =
210✔
140
      directory.files[segment.toLowerCase()] ??
210✔
141
      Object.values(directory.files).find(
48✔
142
        (file) => file.isWildcard && this.fileModuleDefined(file, method),
48✔
143
      );
210✔
144

210✔
145
    if (match === undefined) {
210✔
146
      return undefined;
20✔
147
    }
20✔
148

190✔
149
    if (match.isWildcard) {
210✔
150
      return {
28✔
151
        ...match,
28✔
152

28✔
153
        matchedPath: `${matchedPath}/${match.rawName}`,
28✔
154

28✔
155
        pathVariables: {
28✔
156
          ...pathVariables,
28✔
157
          [match.name]: segment,
28✔
158
        },
28✔
159
      };
28✔
160
    }
28✔
161

162✔
162
    return {
162✔
163
      ...match,
162✔
164

162✔
165
      matchedPath: `${matchedPath}/${match.rawName}`,
162✔
166

162✔
167
      pathVariables,
162✔
168
    };
162✔
169
  }
162✔
170

128✔
171
  private matchWithinDirectory(
128✔
172
    directory: Directory,
310✔
173
    segments: string[],
310✔
174
    pathVariables: { [key: string]: string },
310✔
175
    matchedPath: string,
310✔
176
    method: string,
310✔
177
  ): Match | undefined {
310✔
178
    if (segments.length === 0) {
310!
179
      return undefined;
×
180
    }
×
181

310✔
182
    const [segment, ...remainingSegments] = segments;
310✔
183

310✔
184
    if (segment === undefined) {
310!
185
      throw new Error(
×
186
        "segment cannot be undefined but TypeScript doesn't know that",
×
187
      );
×
188
    }
×
189

310✔
190
    if (
310✔
191
      remainingSegments.length === 0 ||
310✔
192
      (remainingSegments.length === 1 && remainingSegments[0] === "")
102✔
193
    ) {
310✔
194
      return this.buildMatch(
210✔
195
        directory,
210✔
196
        segment,
210✔
197
        pathVariables,
210✔
198
        matchedPath,
210✔
199
        method,
210✔
200
      );
210✔
201
    }
210✔
202

100✔
203
    const exactMatch = directory.directories[segment.toLowerCase()];
100✔
204

100✔
205
    if (isDirectory(exactMatch)) {
254✔
206
      return this.matchWithinDirectory(
68✔
207
        exactMatch,
68✔
208
        remainingSegments,
68✔
209
        pathVariables,
68✔
210
        `${matchedPath}/${segment}`,
68✔
211
        method,
68✔
212
      );
68✔
213
    }
68✔
214

32✔
215
    const wildcardDirectories = Object.values(directory.directories).filter(
32✔
216
      (subdirectory) => subdirectory.isWildcard,
32✔
217
    );
32✔
218

32✔
219
    for (const wildcardDirectory of wildcardDirectories) {
216✔
220
      const match = this.matchWithinDirectory(
30✔
221
        wildcardDirectory,
30✔
222
        remainingSegments,
30✔
223
        {
30✔
224
          ...pathVariables,
30✔
225
          [wildcardDirectory.name]: segment,
30✔
226
        },
30✔
227
        `${matchedPath}/${wildcardDirectory.rawName}`,
30✔
228
        method,
30✔
229
      );
30✔
230

30✔
231
      if (match !== undefined) {
30✔
232
        return match;
20✔
233
      }
20✔
234
    }
30✔
235

12✔
236
    return undefined;
12✔
237
  }
12✔
238

128✔
239
  public match(url: string, method: string) {
128✔
240
    return this.matchWithinDirectory(
212✔
241
      this.root,
212✔
242
      url.split("/").slice(1),
212✔
243
      {},
212✔
244
      "",
212✔
245
      method,
212✔
246
    );
212✔
247
  }
212✔
248

128✔
249
  public get routes(): Route[] {
128✔
250
    const routes: Route[] = [];
4✔
251

4✔
252
    function traverse(directory: Directory, path: string) {
4✔
253
      Object.values(directory.directories).forEach((subdirectory) => {
18✔
254
        traverse(subdirectory, `${path}/${subdirectory.rawName}`);
14✔
255
      });
14✔
256

18✔
257
      Object.values(directory.files).forEach((file) => {
18✔
258
        const methods: [string, string][] = Object.entries(file.module).map(
26✔
259
          ([method, implementation]) => [method, String(implementation)],
26✔
260
        );
26✔
261

26✔
262
        routes.push({
26✔
263
          methods: Object.fromEntries(methods),
26✔
264
          path: `${path}/${file.rawName}`,
26✔
265
        });
26✔
266
      });
26✔
267
    }
18✔
268

4✔
269
    function stripBrackets(string: string) {
4✔
270
      return string.replaceAll(/\{|\}/gu, "");
100✔
271
    }
100✔
272

4✔
273
    traverse(this.root, "");
4✔
274

4✔
275
    return routes.sort((first, second) =>
4✔
276
      stripBrackets(first.path).localeCompare(stripBrackets(second.path)),
50✔
277
    );
4✔
278
  }
4✔
279
}
128✔
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