Skip to content

Commit

Permalink
feat: add options for configuring more complex types (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm committed Sep 12, 2022
1 parent 0833fd0 commit 5307b1e
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-rocks-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'abitype': patch
---

Added `AddressType` and `BytesType` to configuration. Renamed `NumberType` to `IntType`.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,19 +369,21 @@ import { TypedData } from 'abitype'

ABIType tries to strike a balance between type exhaustiveness and speed with sensible defaults. In some cases, you might want to tune your configuration (e.g. fixed array length). To do this, the following configuration options are available:

| Option | Type | Default | Description |
| ---------------------------- | ----------------- | ------------------ | -------------------------------------------------------------------------------------------------------- |
| `ArrayMaxDepth` | `number \| false` | `2` | Maximum depth for nested array types (e.g. `string[][]`). When `false`, there is no maximum array depth. |
| `FixedArrayLengthLowerBound` | `number` | `1` | Lower bound for fixed array length |
| `FixedArrayLengthUpperBound` | `number` | `5` | Upper bound for fixed array length |
| `NumberType` | TypeScript type | `number \| bigint` | TypeScript type to use for `int` and `uint` values. |
| Option | Type | Default | Description |
| --------------------- | ----------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------- |
| `AddressType` | `any` | `` `0x${string}` `` | TypeScript type to use for `address` values. |
| `ArrayMaxDepth` | `number \| false` | `2` | Maximum depth for nested array types (e.g. `string[][]`). When `false`, there is no maximum array depth. |
| `BytesType` | `any` | `string \| ArrayLike<number>` | TypeScript type to use for `bytes<M>` values. |
| `FixedArrayMinLength` | `number` | `1` | Lower bound for fixed-length arrays |
| `FixedArrayMaxLength` | `number` | `5` | Upper bound for fixed-length arrays |
| `IntType` | `any` | `number \| bigint` | TypeScript type to use for `int<M>` and `uint<M>` values. |

Configuration options are customizable using [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html). Just extend the `Config` interface either directly in your code or in a `d.ts` file (e.g. `abi.d.ts`):

```ts
declare module 'abitype' {
export interface Config {
FixedArrayLengthUpperBound: 6
FixedArrayMaxLength: 6
}
}
```
Expand Down
4 changes: 2 additions & 2 deletions src/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export type SolidityInt = `${'u' | ''}int${MBits}` // `(u)int<M>`: (un)signed in
// | `${'u' | ''}fixed${MBits}x${Range<1, 20>[number]}`

export type SolidityFixedArrayRange = Range<
ResolvedConfig['FixedArrayLengthLowerBound'],
ResolvedConfig['FixedArrayLengthUpperBound']
ResolvedConfig['FixedArrayMinLength'],
ResolvedConfig['FixedArrayMaxLength']
>[number]
export type SolidityFixedArraySizeLookup = {
[Prop in SolidityFixedArrayRange as `${Prop}`]: Prop
Expand Down
24 changes: 24 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expectType, test } from '../test'
import { ResolvedConfig } from './config'

// declare module './config' {
// export interface Config {
// FixedArrayMaxLength: 6
// }
// }

test('Config', () => {
expectType<ResolvedConfig['ArrayMaxDepth']>(2)
expectType<ResolvedConfig['FixedArrayMinLength']>(1)
expectType<ResolvedConfig['FixedArrayMaxLength']>(5)

type AddressType = ResolvedConfig['AddressType']
expectType<AddressType>('0x0000000000000000000000000000000000000000')

type BytesType = ResolvedConfig['BytesType']
expectType<BytesType>('foo bar baz')

type IntType = ResolvedConfig['IntType']
expectType<IntType>(123)
expectType<IntType>(123n)
})
41 changes: 28 additions & 13 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Address } from './abi'
import { IsUnknown } from './types'

/**
* Override `Config` to customize type options
*
* @example
* declare module 'abitype' {
* export interface Config {
* FixedArrayLengthUpperBound: 6
* FixedArrayMaxLength: 6
* }
* }
*/
Expand All @@ -19,11 +22,16 @@ export interface DefaultConfig {
/** Maximum depth for nested array types (e.g. string[][]) */
ArrayMaxDepth: 2
/** Lower bound for fixed array length */
FixedArrayLengthLowerBound: 1
FixedArrayMinLength: 1
/** Upper bound for fixed array length */
FixedArrayLengthUpperBound: 5
FixedArrayMaxLength: 5

/** TypeScript type to use for `address` values */
AddressType: Address
/** TypeScript type to use for `bytes` values */
BytesType: string | ArrayLike<number>
/** TypeScript type to use for `int` and `uint` values */
NumberType: number | bigint
IntType: number | bigint
}

/**
Expand All @@ -38,13 +46,20 @@ export interface ResolvedConfig {
ArrayMaxDepth: Config['ArrayMaxDepth'] extends number | false
? Config['ArrayMaxDepth']
: DefaultConfig['ArrayMaxDepth']
FixedArrayLengthLowerBound: Config['FixedArrayLengthLowerBound'] extends number
? Config['FixedArrayLengthLowerBound']
: DefaultConfig['FixedArrayLengthLowerBound']
FixedArrayLengthUpperBound: Config['FixedArrayLengthUpperBound'] extends number
? Config['FixedArrayLengthUpperBound']
: DefaultConfig['FixedArrayLengthUpperBound']
NumberType: Config['NumberType'] extends number | bigint
? Config['NumberType']
: DefaultConfig['NumberType']
FixedArrayMinLength: Config['FixedArrayMinLength'] extends number
? Config['FixedArrayMinLength']
: DefaultConfig['FixedArrayMinLength']
FixedArrayMaxLength: Config['FixedArrayMaxLength'] extends number
? Config['FixedArrayMaxLength']
: DefaultConfig['FixedArrayMaxLength']

AddressType: IsUnknown<Config['AddressType']> extends true
? DefaultConfig['AddressType']
: Config['AddressType']
BytesType: IsUnknown<Config['BytesType']> extends true
? DefaultConfig['BytesType']
: Config['BytesType']
IntType: IsUnknown<Config['IntType']> extends true
? DefaultConfig['IntType']
: Config['IntType']
}
15 changes: 14 additions & 1 deletion src/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { expectType, test } from '../test'
import { Range, Tuple } from './types'
import { IsUnknown, Merge, Range, Tuple } from './types'

test('IsUnknown', () => {
expectType<IsUnknown<unknown>>(true)
expectType<IsUnknown<number | bigint>>(false)
})

test('Merge', () => {
expectType<Merge<{ foo: number }, { bar: string }>>({ foo: 123, bar: 'abc' })
expectType<Merge<{ foo: number }, { foo: string; bar: string }>>({
foo: 'xyz',
bar: 'abc',
})
})

test('Range', () => {
expectType<Range<0, 2>>([0, 1, 2])
Expand Down
13 changes: 12 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
/**
* Merges two types into new type
* Checks if {@link T} is `unknown`
*
* @param T - Type to check
* @returns `true` if `T` is `unknown`, otherwise `false`
*
* @example
* type Result = IsUnknown<unknown>
*/
export type IsUnknown<T> = unknown extends T ? true : false

/**
* Merges two object types into new type
*
* @param Object1 - Object to merge into
* @param Object2 - Object to merge and override keys from {@link Object1}
Expand Down
9 changes: 4 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
AbiParameter,
AbiStateMutability,
AbiType,
Address,
SolidityAddress,
SolidityArray,
SolidityBool,
Expand Down Expand Up @@ -37,15 +36,15 @@ export type AbiTypeToPrimitiveType<TAbiType extends AbiType> =
// Using a map to look up types is faster, than nested conditional types
// s/o https://twitter.com/SeaRyanC/status/1538971176357113858
type PrimitiveTypeLookup = {
[_ in SolidityAddress]: Address
[_ in SolidityAddress]: ResolvedConfig['AddressType']
} & {
[_ in SolidityBool]: boolean
} & {
[_ in SolidityBytes]: string | ArrayLike<number>
[_ in SolidityBytes]: ResolvedConfig['BytesType']
} & {
[_ in SolidityFunction]: `${Address}${string}`
[_ in SolidityFunction]: `${ResolvedConfig['AddressType']}${string}`
} & {
[_ in SolidityInt]: ResolvedConfig['NumberType']
[_ in SolidityInt]: ResolvedConfig['IntType']
} & {
[_ in SolidityString]: string
} & {
Expand Down

0 comments on commit 5307b1e

Please sign in to comment.