Skip to content

Commit 0e1f447

Browse files
committed
Add switch utility to handle conditional logic
1 parent eccdad9 commit 0e1f447

File tree

3 files changed

+129
-1
lines changed

3 files changed

+129
-1
lines changed

src/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from './array'
88
export * from './semantic'
99
export * from './chain'
1010
export * from './object'
11+
export * from './switch'
1112

1213
import * as typed from './typed'
1314
import * as types from './types'
@@ -18,6 +19,7 @@ import * as array from './array'
1819
import * as text from './semantic'
1920
import * as chain from './chain'
2021
import * as object from './object'
22+
import * as switchOps from './switch'
2123
export default {
2224
...typed,
2325
...types,
@@ -27,5 +29,6 @@ export default {
2729
...array,
2830
...text,
2931
...chain,
30-
...object
32+
...object,
33+
...switchOps
3134
}

src/switch.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
class InlineSwitch<T, R = never, E = undefined> {
2+
private cases = new Map<T, () => any>();
3+
private defaultCase?: () => any;
4+
5+
constructor(private readonly value: T) { }
6+
7+
// Method to add a case
8+
case<U>(caseValue: T, result: () => U): InlineSwitch<T, R | U> {
9+
this.cases.set(caseValue, result);
10+
return this as any;
11+
}
12+
13+
// Method to set the default case
14+
default<U>(result: () => U): Omit<InlineSwitch<T, R | U, never>, 'default'> {
15+
if (this.defaultCase) {
16+
throw new Error("Default case already set.");
17+
}
18+
this.defaultCase = result;
19+
return this as any;
20+
}
21+
22+
// Method to execute the switch
23+
execute(): R | E {
24+
const result = this.cases.get(this.value);
25+
if (result) {
26+
return result();
27+
}
28+
29+
if (this.defaultCase) {
30+
return this.defaultCase();
31+
}
32+
33+
return undefined as any;
34+
}
35+
}
36+
37+
/**
38+
* Creates a new InlineSwitch instance for given value. This utility function
39+
* facilitates a fluent interface for conditional logic based on the value provided,
40+
* allowing for a more readable and expressive alternative to traditional switch
41+
* statements or if-else chains. The InlineSwitch class supports adding cases
42+
* with `.case()` method calls and optionally setting a default case with `.default()`.
43+
* The `.execute()` method evaluates the cases against the value and returns the
44+
* result of the matching case or the default case, if provided.
45+
*
46+
* @param value The value to be matched against the defined cases in the InlineSwitch instance.
47+
* @returns A new instance of InlineSwitch configured with the provided value.
48+
*
49+
* @example
50+
* // Using inlineSwitch to determine fruit colors.
51+
* const fruitColor = inlineSwitch('apple')
52+
* .case('apple', () => 'red')
53+
* .case('banana', () => 'yellow')
54+
* .case('orange', () => 'orange')
55+
* .default(() => 'unknown color')
56+
* .execute();
57+
*
58+
* console.log(fruitColor); // Outputs: 'red'
59+
*
60+
* @example
61+
* // Using inlineSwitch with mixed return types and a default case.
62+
* const processedValue = inlineSwitch('kiwi')
63+
* .case('apple', () => 42)
64+
* .case('banana', () => true)
65+
* .case('orange', () => 'orange')
66+
* .default(() => null)
67+
* .execute();
68+
*
69+
* console.log(processedValue); // Outputs: null
70+
*/
71+
export function inlineSwitch<T>(value: T) {
72+
return new InlineSwitch(value);
73+
}

tests/switch.test.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { describe, test, it, expect } from 'bun:test'
2+
import { inlineSwitch } from '../src/index';
3+
4+
describe('InlineSwitch', () => {
5+
it('should correctly handle a matching case', () => {
6+
const result = inlineSwitch('apple')
7+
.case('apple', () => 'red')
8+
.case('banana', () => 'yellow')
9+
.execute();
10+
11+
expect(result).toBe('red');
12+
});
13+
14+
it('should return the default case when no cases match', () => {
15+
const result = inlineSwitch('kiwi')
16+
.case('apple', () => 'red')
17+
.case('banana', () => 'yellow')
18+
.default(() => 'unknown color')
19+
.execute();
20+
21+
expect(result).toBe('unknown color');
22+
});
23+
24+
it('should handle mixed return types', () => {
25+
const result = inlineSwitch('banana')
26+
.case('apple', () => 42)
27+
.case('banana', () => true)
28+
.default(() => 'unknown')
29+
.execute();
30+
31+
expect(result).toBe(true);
32+
});
33+
34+
it('should throw an error if no cases match and no default case is provided', () => {
35+
const result = inlineSwitch('kiwi')
36+
.case('apple', () => 'red')
37+
.case('banana', () => 'yellow')
38+
.execute()
39+
40+
expect(result).toBeUndefined();
41+
});
42+
43+
it('should prevent setting multiple default cases', () => {
44+
expect(() => {
45+
inlineSwitch('kiwi')
46+
.default(() => 'default 1')
47+
// @ts-ignore
48+
.default(() => 'default 2')
49+
.execute();
50+
}).toThrow("Default case already set.");
51+
});
52+
});

0 commit comments

Comments
 (0)