Skip to content

Commit d70e621

Browse files
authored
feat: Router should sort routes by specificity (#14)
* chore: Update eslint * feat: Router should sort routes by specificity * chore: move types to the end
1 parent 23741d2 commit d70e621

11 files changed

+357
-116
lines changed

.eslintrc

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"parserOptions": {
44
"project": "tsconfig.eslint.json"
55
},
6-
"plugins": ["@typeofweb/eslint-plugin"],
76
"extends": ["plugin:@typeofweb/recommended"],
87
"overrides": [
98
{

CODE_OF_CONDUCT.md

+132-76
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,132 @@
1-
# Przymierze Współpracy — Kodeks Postępowania
2-
3-
## Nasza Przysięga
4-
5-
W celu wspierania otwartego i przyjaznego środowiska, my — jako współpracownicy i
6-
opiekunowie projektu — zobowiązujemy się dopilnować, aby uczestnictwo w naszym projekcie
7-
oraz przynależność do jego społeczności były pozbawione nękania bez względu na budowę
8-
ciała, doświadczenie, kolor skóry, narodowość, niepełnosprawność, orientację i
9-
identyfikację seksualną, pochodzenie etniczne, religię, wiek czy wygląd zewnętrzny.
10-
11-
## Nasze Standardy
12-
13-
Wśród przykładów zachowania, które przyczyniają się do tworzenia pozytywnego
14-
środowiska, są:
15-
16-
- Używanie języka, który jest przyjazny i nie wyklucza innych uczestników projektu,
17-
- Okazywanie szacunku dla różnych punktów widzenia i osobistych doświadczeń,
18-
- Przyjmowanie konstruktywnej krytyki z wdzięcznością,
19-
- Skupianie się na tym, co dobre dla społeczności projektu,
20-
- Wykazywanie empatii wobec innych członków społeczności.
21-
22-
Wśród przykładów zachowania, którego nie będziemy akceptować, są:
23-
24-
- Używanie języka i grafik o podtekście seksualnym, okazywanie
25-
niepożądanego zainteresowania seksualnego, a także zaloty,
26-
- Trollowanie, obraźliwe bądź urągające komentarze oraz ataki osobiste bądź
27-
polityczne,
28-
- Nękanie, zarówno na forum publicznym jak i prywatnym,
29-
- Publikowanie danych osobistych innych osób — takich jak
30-
adres fizyczny czy elektroniczny — bez ich wyraźnej zgody,
31-
- Inne zachowania, które mogłyby zostać uznane za nieodpowiednie w kontekście
32-
profesjonalnym.
33-
34-
## Nasza Odpowiedzialność
35-
36-
Opiekunowie projektu są odpowiedzialni za objaśnianie standardów akceptowalnego
37-
zachowania i reagowanie na wszelkie przypadki nieodpowiedniego zachowania, w sposób
38-
sprawiedliwy i adekwatny do sytuacji.
39-
40-
Opiekunowie projektu mają prawo do i są odpowiedzialni za usuwanie, edycję oraz odrzucanie
41-
komentarzy, zmian w kodzie (ang. _commit_), edycji Wiki, oraz innych treści, które
42-
łamią niniejszy Kodeks Postępowania, oraz mają prawo tymczasowo lub na stałe zablokować
43-
dostęp do projektu każdemu ze współpracowników: w przypadku gróźb bądź obraźliwych
44-
komentarzy pod adresem innych współpracowników, a także innych zachowań, które uznają
45-
za nieodpowiednie lub szkodliwe.
46-
47-
## Zakres działania
48-
49-
Ten Kodeks Postępowania ma zastosowanie wewnątrz projektu, a w przypadku
50-
reprezentowania projektu lub jego społeczności przez współpracownika — także na zewnątrz
51-
projektu. Do przykładów reprezentowania projektu zalicza się używanie oficjalnego adresu
52-
e-mail projektu, publikowanie na oficjalnym koncie w mediach społecznościowych lub branie
53-
udziału w wydarzeniu (zarówno w Internecie, jak i poza nim) jako oficjalny przedstawiciel
54-
projektu. Dokładny zakres pojęcia reprezentowania projektu może zostać objaśniony
55-
w bardziej szczegółowy sposób przez opiekunów projektu.
56-
57-
## Egzekwowanie
58-
59-
Przypadki nękania, gróźb oraz innych form nieakceptowalnego zachowania mogą
60-
być zgłaszane do zespołu projektu pod adresem [email protected]. Wszystkie
61-
skargi zostaną odpowiednio rozpatrzone oraz podjęte zostaną działania uznane za
62-
konieczne i odpowiednie do sytuacji. Zespół projektu ma obowiązek zachowania tożsamości
63-
osoby zgłaszającej incydent w tajemnicy. Bardziej szczegółowe zasady egzekwowania Kodeksu
64-
Postępowania mogą zostać udostępnione w osobnym dokumencie.
65-
66-
Opiekunowie projektu, którzy nie respektują i nie egzekwują w dobrej wierze Kodeksu Postępowania,
67-
mogą zostać na stałe lub tymczasowo ukarani, zgodnie z decyzją innych liderów projektu.
68-
69-
## Autorstwo
70-
71-
Ten Kodeks Postępowania został zaadaptowany z [Contributor Covenant][homepage] w
72-
wersji 1.4, dostępnej pod adresem https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
73-
(w języku angielskim) oraz https://www.contributor-covenant.org/pl/version/1/4/code-of-conduct.html
74-
(w języku polskim).
75-
76-
[homepage]: https://www.contributor-covenant.org/
1+
# Contributor Covenant Code of Conduct
2+
3+
## Our Pledge
4+
5+
We as members, contributors, and leaders pledge to make participation in our
6+
community a harassment-free experience for everyone, regardless of age, body
7+
size, visible or invisible disability, ethnicity, sex characteristics, gender
8+
identity and expression, level of experience, education, socio-economic status,
9+
nationality, personal appearance, race, caste, color, religion, or sexual identity
10+
and orientation.
11+
12+
We pledge to act and interact in ways that contribute to an open, welcoming,
13+
diverse, inclusive, and healthy community.
14+
15+
## Our Standards
16+
17+
Examples of behavior that contributes to a positive environment for our
18+
community include:
19+
20+
- Demonstrating empathy and kindness toward other people
21+
- Being respectful of differing opinions, viewpoints, and experiences
22+
- Giving and gracefully accepting constructive feedback
23+
- Accepting responsibility and apologizing to those affected by our mistakes,
24+
and learning from the experience
25+
- Focusing on what is best not just for us as individuals, but for the
26+
overall community
27+
28+
Examples of unacceptable behavior include:
29+
30+
- The use of sexualized language or imagery, and sexual attention or
31+
advances of any kind
32+
- Trolling, insulting or derogatory comments, and personal or political attacks
33+
- Public or private harassment
34+
- Publishing others' private information, such as a physical or email
35+
address, without their explicit permission
36+
- Other conduct which could reasonably be considered inappropriate in a
37+
professional setting
38+
39+
## Enforcement Responsibilities
40+
41+
Community leaders are responsible for clarifying and enforcing our standards of
42+
acceptable behavior and will take appropriate and fair corrective action in
43+
response to any behavior that they deem inappropriate, threatening, offensive,
44+
or harmful.
45+
46+
Community leaders have the right and responsibility to remove, edit, or reject
47+
comments, commits, code, wiki edits, issues, and other contributions that are
48+
not aligned to this Code of Conduct, and will communicate reasons for moderation
49+
decisions when appropriate.
50+
51+
## Scope
52+
53+
This Code of Conduct applies within all community spaces, and also applies when
54+
an individual is officially representing the community in public spaces.
55+
Examples of representing our community include using an official e-mail address,
56+
posting via an official social media account, or acting as an appointed
57+
representative at an online or offline event.
58+
59+
## Enforcement
60+
61+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
62+
reported to the community leaders responsible for enforcement at
63+
[INSERT CONTACT METHOD].
64+
All complaints will be reviewed and investigated promptly and fairly.
65+
66+
All community leaders are obligated to respect the privacy and security of the
67+
reporter of any incident.
68+
69+
## Enforcement Guidelines
70+
71+
Community leaders will follow these Community Impact Guidelines in determining
72+
the consequences for any action they deem in violation of this Code of Conduct:
73+
74+
### 1. Correction
75+
76+
**Community Impact**: Use of inappropriate language or other behavior deemed
77+
unprofessional or unwelcome in the community.
78+
79+
**Consequence**: A private, written warning from community leaders, providing
80+
clarity around the nature of the violation and an explanation of why the
81+
behavior was inappropriate. A public apology may be requested.
82+
83+
### 2. Warning
84+
85+
**Community Impact**: A violation through a single incident or series
86+
of actions.
87+
88+
**Consequence**: A warning with consequences for continued behavior. No
89+
interaction with the people involved, including unsolicited interaction with
90+
those enforcing the Code of Conduct, for a specified period of time. This
91+
includes avoiding interactions in community spaces as well as external channels
92+
like social media. Violating these terms may lead to a temporary or
93+
permanent ban.
94+
95+
### 3. Temporary Ban
96+
97+
**Community Impact**: A serious violation of community standards, including
98+
sustained inappropriate behavior.
99+
100+
**Consequence**: A temporary ban from any sort of interaction or public
101+
communication with the community for a specified period of time. No public or
102+
private interaction with the people involved, including unsolicited interaction
103+
with those enforcing the Code of Conduct, is allowed during this period.
104+
Violating these terms may lead to a permanent ban.
105+
106+
### 4. Permanent Ban
107+
108+
**Community Impact**: Demonstrating a pattern of violation of community
109+
standards, including sustained inappropriate behavior, harassment of an
110+
individual, or aggression toward or disparagement of classes of individuals.
111+
112+
**Consequence**: A permanent ban from any sort of public interaction within
113+
the community.
114+
115+
## Attribution
116+
117+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118+
version 2.0, available at
119+
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
120+
121+
Community Impact Guidelines were inspired by
122+
[Mozilla's code of conduct enforcement ladder][mozilla coc].
123+
124+
For answers to common questions about this code of conduct, see the FAQ at
125+
[https://www.contributor-covenant.org/faq][faq]. Translations are available
126+
at [https://www.contributor-covenant.org/translations][translations].
127+
128+
[homepage]: https://www.contributor-covenant.org
129+
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
130+
[mozilla coc]: https://github.com/mozilla/diversity
131+
[faq]: https://www.contributor-covenant.org/faq
132+
[translations]: https://www.contributor-covenant.org/translations

__tests__/routeSpecificity.test.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { calculateSpecificity } from '../src/utils/routeSpecificity';
2+
3+
describe('routeSpecificity', () => {
4+
test.each(
5+
// prettier-ignore
6+
[
7+
['/', '0'],
8+
['/a', '1'],
9+
['/b', '1'],
10+
['/ab', '1'],
11+
['/:p', '2'],
12+
['/a/b', '11'],
13+
['/a/:p', '12'],
14+
['/b/', '10'],
15+
['/a/b/c', '111'],
16+
['/a/b/:p', '112'],
17+
['/a/:p/b', '121'],
18+
['/a/:p/c', '121'],
19+
['/a/b/c/d', '1111'],
20+
['/a/:p/b/:x', '1212'],
21+
['/a/b/*', '113'],
22+
['/:a/b/*', '213'],
23+
['/*', '3'],
24+
['/m/n/*', '113'],
25+
['/m/:n/:o', '122'],
26+
['/n/:p/*', '123'],
27+
],
28+
)('calculateSpecificity(%s)', (route, expected) => {
29+
expect(calculateSpecificity(route)).toEqual(expected);
30+
});
31+
});

__tests__/router.test.ts

+87
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,91 @@ describe('router', () => {
135135
expect(result.statusCode).toEqual(202);
136136
});
137137
});
138+
139+
describe('specificity', () => {
140+
const routePaths = [
141+
'/',
142+
'/a',
143+
'/b',
144+
'/ab',
145+
'/:p',
146+
'/a/b',
147+
'/a/:p',
148+
'/b/',
149+
'/a/b/c',
150+
'/a/b/:p',
151+
'/a/:p/b',
152+
'/a/:p/c',
153+
'/a/b/c/d',
154+
'/a/:p/b/:x',
155+
'/a/b/*',
156+
'/:a/b/*',
157+
'/*',
158+
'/m/n/*',
159+
'/m/:n/:o',
160+
'/n/:p/*',
161+
].sort(() => Math.random() - 0.5);
162+
163+
const requests = [
164+
['/', '/'],
165+
['/a', '/a'],
166+
['/b', '/b'],
167+
['/ab', '/ab'],
168+
['/c', '/:p'],
169+
['/a/b', '/a/b'],
170+
['/a/c', '/a/:p'],
171+
['/b/', '/b/'],
172+
['/a/b/c', '/a/b/c'],
173+
['/a/b/d', '/a/b/:p'],
174+
['/a/a/b', '/a/:p/b'],
175+
['/a/d/c', '/a/:p/c'],
176+
['/a/b/c/d', '/a/b/c/d'],
177+
['/a/c/b/d', '/a/:p/b/:x'],
178+
['/a/b/c/d/e', '/a/b/*'],
179+
['/a/b/c/d/e/f', '/a/b/*'],
180+
['/x/b/c/d/e/f/g', '/:a/b/*'],
181+
['/x/y/c/d/e/f/g', '/*'],
182+
['/m/n/o', '/m/n/*'],
183+
['/m/o/p', '/m/:n/:o'],
184+
['/n/a/b/c', '/n/:p/*'],
185+
['/n/a/', '/n/:p/*'],
186+
];
187+
188+
const strictApp = createApp({ router: { strictTrailingSlash: true } });
189+
routePaths.forEach((routePath) =>
190+
strictApp.route({
191+
path: routePath,
192+
method: 'get',
193+
validation: {},
194+
handler: (req) => req.path,
195+
}),
196+
);
197+
198+
const looseApp = createApp({ router: { strictTrailingSlash: false } });
199+
routePaths.forEach((routePath) =>
200+
looseApp.route({
201+
path: routePath,
202+
method: 'get',
203+
validation: {},
204+
handler: (req) => req.path,
205+
}),
206+
);
207+
208+
it.each(requests)('strict: %p → %p', async (requestPath, requestRoute) => {
209+
const result = await strictApp.inject({
210+
method: 'get',
211+
path: requestPath,
212+
});
213+
expect(result.body).toEqual(requestRoute);
214+
});
215+
216+
it.each(requests)('loose: %p → %p', async (requestPath, requestRoute) => {
217+
const result = await looseApp.inject({
218+
method: 'get',
219+
path: requestPath,
220+
});
221+
// trim trailing slash
222+
expect(result.body).toEqual(requestRoute.replace(/(.+)\/$/, '$1'));
223+
});
224+
});
138225
});

package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,24 @@
5050
"@rollup/plugin-commonjs": "19.0.0",
5151
"@rollup/plugin-json": "4.1.0",
5252
"@rollup/plugin-typescript": "8.2.1",
53-
"@tsconfig/node12": "1.0.8",
53+
"@tsconfig/node12": "1.0.9",
5454
"@tsconfig/node14": "1.0.1",
55-
"@typeofweb/eslint-plugin": "0.1.10",
55+
"@typeofweb/eslint-plugin": "0.2.2",
5656
"@types/jest": "26.0.23",
5757
"@types/node": "12",
5858
"@types/stoppable": "1.1.1",
5959
"all-contributors-cli": "6.20.0",
6060
"builtin-modules": "3.2.0",
61-
"eslint": "7.28.0",
61+
"eslint": "7.29.0",
6262
"fast-check": "2.16.0",
63-
"globby": "11.0.3",
63+
"globby": "11.0.4",
6464
"husky": "6.0.0",
6565
"jest": "27.0.4",
6666
"lint-staged": "11.0.0",
6767
"nodemon": "2.0.7",
6868
"prettier": "2.3.1",
6969
"rimraf": "3.0.2",
70-
"rollup": "2.51.2",
70+
"rollup": "2.52.2",
7171
"rollup-plugin-filesize": "9.1.1",
7272
"rollup-plugin-license": "2.5.0",
7373
"rollup-plugin-prettier": "2.1.0",
@@ -76,7 +76,7 @@
7676
"ts-node": "10.0.0",
7777
"tsd": "https://github.com/typeofweb/tsd#pkg",
7878
"tslib": "2.3.0",
79-
"typescript": "4.3.2"
79+
"typescript": "4.3.4"
8080
},
8181
"scripts": {
8282
"prejest": "yarn build",

src/modules/app.ts

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const defaultAppOptions: AppOptions = {
3131
sameSite: 'lax',
3232
secret: '',
3333
},
34+
router: {
35+
strictTrailingSlash: false,
36+
},
3437
};
3538

3639
export function createApp(opts: DeepPartial<AppOptions>): TypeOfWebApp {

src/modules/http.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const initApp = () => {
88
const app = Express();
99

1010
app.use(BodyParser.json());
11+
app.disable('x-powered-by');
1112

1213
return app;
1314
};

0 commit comments

Comments
 (0)