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

pmcelhaney / counterfact / 15326901599

29 May 2025 03:01PM UTC coverage: 82.613% (+0.04%) from 82.574%
15326901599

Pull #1296

github

dethell
fix(server): Minor fix to buildMatch logic
Pull Request #1296: fix(server): Minor fix to buildMatch logic

1127 of 1258 branches covered (89.59%)

Branch coverage included in aggregate %.

10 of 10 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

3344 of 4154 relevant lines covered (80.5%)

63.33 hits per line

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

92.08
/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!
71
      return;
×
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!
79
      throw new Error(
×
80
        "The file name (the last segment of the URL) is undefined. This is theoretically impossible but TypeScript can't enforce it.",
×
81
      );
×
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
    let fileMatch = "";
210✔
140
    for (const file in directory.files) {
210✔
141
      if (file.toLowerCase() === segment.toLowerCase()) {
214✔
142
        fileMatch = file;
162✔
143
        break;
162✔
144
      }
162✔
145
    }
214✔
146

210✔
147
    const match =
210✔
148
      directory.files[fileMatch] ??
210✔
149
      Object.values(directory.files).find(
48✔
150
        (file) => file.isWildcard && this.fileModuleDefined(file, method),
48✔
151
      );
210✔
152

210✔
153
    if (match === undefined) {
210✔
154
      return undefined;
20✔
155
    }
20✔
156

190✔
157
    if (match.isWildcard) {
210✔
158
      return {
28✔
159
        ...match,
28✔
160

28✔
161
        matchedPath: `${matchedPath}/${match.rawName}`,
28✔
162

28✔
163
        pathVariables: {
28✔
164
          ...pathVariables,
28✔
165
          [match.name]: segment,
28✔
166
        },
28✔
167
      };
28✔
168
    }
28✔
169

162✔
170
    return {
162✔
171
      ...match,
162✔
172

162✔
173
      matchedPath: `${matchedPath}/${match.rawName}`,
162✔
174

162✔
175
      pathVariables,
162✔
176
    };
162✔
177
  }
162✔
178

128✔
179
  private matchWithinDirectory(
128✔
180
    directory: Directory,
310✔
181
    segments: string[],
310✔
182
    pathVariables: { [key: string]: string },
310✔
183
    matchedPath: string,
310✔
184
    method: string,
310✔
185
  ): Match | undefined {
310✔
186
    if (segments.length === 0) {
310!
UNCOV
187
      return undefined;
×
188
    }
×
189

310✔
190
    const [segment, ...remainingSegments] = segments;
310✔
191

310✔
192
    if (segment === undefined) {
310!
UNCOV
193
      throw new Error(
×
194
        "segment cannot be undefined but TypeScript doesn't know that",
×
195
      );
×
196
    }
×
197

310✔
198
    if (
310✔
199
      remainingSegments.length === 0 ||
310✔
200
      (remainingSegments.length === 1 && remainingSegments[0] === "")
102✔
201
    ) {
310✔
202
      return this.buildMatch(
210✔
203
        directory,
210✔
204
        segment,
210✔
205
        pathVariables,
210✔
206
        matchedPath,
210✔
207
        method,
210✔
208
      );
210✔
209
    }
210✔
210

100✔
211
    const exactMatch = directory.directories[segment.toLowerCase()];
100✔
212

100✔
213
    if (isDirectory(exactMatch)) {
254✔
214
      return this.matchWithinDirectory(
68✔
215
        exactMatch,
68✔
216
        remainingSegments,
68✔
217
        pathVariables,
68✔
218
        `${matchedPath}/${segment}`,
68✔
219
        method,
68✔
220
      );
68✔
221
    }
68✔
222

32✔
223
    const wildcardDirectories = Object.values(directory.directories).filter(
32✔
224
      (subdirectory) => subdirectory.isWildcard,
32✔
225
    );
32✔
226

32✔
227
    for (const wildcardDirectory of wildcardDirectories) {
216✔
228
      const match = this.matchWithinDirectory(
30✔
229
        wildcardDirectory,
30✔
230
        remainingSegments,
30✔
231
        {
30✔
232
          ...pathVariables,
30✔
233
          [wildcardDirectory.name]: segment,
30✔
234
        },
30✔
235
        `${matchedPath}/${wildcardDirectory.rawName}`,
30✔
236
        method,
30✔
237
      );
30✔
238

30✔
239
      if (match !== undefined) {
30✔
240
        return match;
20✔
241
      }
20✔
242
    }
30✔
243

12✔
244
    return undefined;
12✔
245
  }
12✔
246

128✔
247
  public match(url: string, method: string) {
128✔
248
    return this.matchWithinDirectory(
212✔
249
      this.root,
212✔
250
      url.split("/").slice(1),
212✔
251
      {},
212✔
252
      "",
212✔
253
      method,
212✔
254
    );
212✔
255
  }
212✔
256

128✔
257
  public get routes(): Route[] {
128✔
258
    const routes: Route[] = [];
4✔
259

4✔
260
    function traverse(directory: Directory, path: string) {
4✔
261
      Object.values(directory.directories).forEach((subdirectory) => {
18✔
262
        traverse(subdirectory, `${path}/${subdirectory.rawName}`);
14✔
263
      });
14✔
264

18✔
265
      Object.values(directory.files).forEach((file) => {
18✔
266
        const methods: [string, string][] = Object.entries(file.module).map(
26✔
267
          ([method, implementation]) => [method, String(implementation)],
26✔
268
        );
26✔
269

26✔
270
        routes.push({
26✔
271
          methods: Object.fromEntries(methods),
26✔
272
          path: `${path}/${file.rawName}`,
26✔
273
        });
26✔
274
      });
26✔
275
    }
18✔
276

4✔
277
    function stripBrackets(string: string) {
4✔
278
      return string.replaceAll(/\{|\}/gu, "");
100✔
279
    }
100✔
280

4✔
281
    traverse(this.root, "");
4✔
282

4✔
283
    return routes.sort((first, second) =>
4✔
284
      stripBrackets(first.path).localeCompare(stripBrackets(second.path)),
50✔
285
    );
4✔
286
  }
4✔
287
}
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