Skip to content

Commit

Permalink
type tests for required props
Browse files Browse the repository at this point in the history
  • Loading branch information
blaine-arcjet committed Feb 7, 2024
1 parent 97bfdf7 commit 53d220f
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 5 deletions.
12 changes: 7 additions & 5 deletions arcjet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,8 +639,10 @@ export function tokenBucket<
options?: TokenBucketRateLimitOptions<Characteristics>,
...additionalOptions: TokenBucketRateLimitOptions<Characteristics>[]
): Primitive<
UnionToIntersection<
{ requested: number } | PropsForCharacteristic<Characteristics[number]>
Simplify<
UnionToIntersection<
{ requested: number } | PropsForCharacteristic<Characteristics[number]>
>
>
> {
const rules: ArcjetTokenBucketRateLimitRule<{ requested: number }>[] = [];
Expand Down Expand Up @@ -682,7 +684,7 @@ export function fixedWindow<
options?: FixedWindowRateLimitOptions<Characteristics>,
...additionalOptions: FixedWindowRateLimitOptions<Characteristics>[]
): Primitive<
UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>
Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>
> {
const rules: ArcjetFixedWindowRateLimitRule<{}>[] = [];

Expand Down Expand Up @@ -721,7 +723,7 @@ export function rateLimit<const Characteristics extends readonly string[] = []>(
options?: FixedWindowRateLimitOptions<Characteristics>,
...additionalOptions: FixedWindowRateLimitOptions<Characteristics>[]
): Primitive<
UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>
Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>
> {
// TODO(#195): We should also have a local rate limit using an in-memory data
// structure if the environment supports it
Expand All @@ -734,7 +736,7 @@ export function slidingWindow<
options?: SlidingWindowRateLimitOptions<Characteristics>,
...additionalOptions: SlidingWindowRateLimitOptions<Characteristics>[]
): Primitive<
UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>
Simplify<UnionToIntersection<PropsForCharacteristic<Characteristics[number]>>>
> {
const rules: ArcjetSlidingWindowRateLimitRule<{}>[] = [];

Expand Down
72 changes: 72 additions & 0 deletions arcjet/test/index.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,47 @@ import arcjet, {
fixedWindow,
tokenBucket,
slidingWindow,
Primitive,
} from "../index";

// Type helpers from https://github.com/sindresorhus/type-fest but adjusted for
// our use.
//
// IsEqual:
// https://github.com/sindresorhus/type-fest/blob/e02f228f6391bb2b26c32a55dfe1e3aa2386d515/source/is-equal.d.ts
//
// Licensed: MIT License Copyright (c) Sindre Sorhus <[email protected]>
// (https://sindresorhus.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: The above copyright
// notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends <G>() => G extends B
? 1
: 2
? true
: false;

// Type testing utilities
type Assert<T extends true> = T;
type Props<P extends Primitive> = P extends Primitive<infer Props>
? Props
: never;
type RequiredProps<P extends Primitive, E> = IsEqual<Props<P>, E>;

// Instances of Headers contain symbols that may be different depending
// on if they have been iterated or not, so we need this equality tester
// to only match the items inside the Headers instance.
Expand Down Expand Up @@ -1978,6 +2017,39 @@ describe("Primitive > tokenBucket", () => {
expect(rules[0]).toHaveProperty("capacity", 120);
});

test("can specify user-defined characteristics which are reflected in required props", async () => {
const rules = tokenBucket({
characteristics: ["userId"],
refillRate: 60,
interval: 60,
capacity: 120,
});
type Test = Assert<
RequiredProps<
typeof rules,
{ requested: number; userId: string | number | boolean }
>
>;
});

test("well-known characteristics don't affect the required props", async () => {
const rules = tokenBucket({
characteristics: [
"ip.src",
"http.host",
"http.method",
"http.request.uri.path",
`http.request.headers["abc"]`,
`http.request.cookie["xyz"]`,
`http.request.uri.args["foobar"]`,
],
refillRate: 60,
interval: 60,
capacity: 120,
});
type Test = Assert<RequiredProps<typeof rules, { requested: number }>>;
});

test("produces a rules based on single `limit` specified", async () => {
const options = {
match: "/test",
Expand Down

0 comments on commit 53d220f

Please sign in to comment.