Skip to content

Commit

Permalink
feat: arrays and dictionaries
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerAberbach committed Dec 7, 2024
1 parent f9c8af7 commit fd5ba3f
Show file tree
Hide file tree
Showing 17 changed files with 189 additions and 106 deletions.
71 changes: 33 additions & 38 deletions src/arbitrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,93 +5,88 @@ export type ArbitraryNamespace = {
arbitraryToName: Map<Arbitrary, string>
}

export type Arbitrary = BaseArbitrary &
(
| NullArbitrary
| UndefinedArbitrary
| NeverArbitrary
| UnknownArbitrary
| BooleanArbitrary
| NumberArbitrary
| BigIntArbitrary
| StringArbitrary
| BytesArbitrary
| EnumArbitrary
| ArrayArbitrary
| DictionaryArbitrary
| UnionArbitrary
| RecordArbitrary
)
export type Arbitrary =
| IntrinsicArbitrary
| ScalarArbitrary
| EnumArbitrary
| ArrayArbitrary
| DictionaryArbitrary
| UnionArbitrary
| RecordArbitrary

export type BaseArbitrary = {
name: string
}

export type NullArbitrary = {
export type IntrinsicArbitrary =
| NullArbitrary
| UndefinedArbitrary
| NeverArbitrary
| UnknownArbitrary
export type NullArbitrary = BaseArbitrary & {
type: `null`
}

export type UndefinedArbitrary = {
export type UndefinedArbitrary = BaseArbitrary & {
type: `undefined`
}

export type NeverArbitrary = {
export type NeverArbitrary = BaseArbitrary & {
type: `never`
}

export type UnknownArbitrary = {
export type UnknownArbitrary = BaseArbitrary & {
type: `unknown`
}

export type BooleanArbitrary = {
export type ScalarArbitrary =
| BooleanArbitrary
| NumberArbitrary
| BigIntArbitrary
| StringArbitrary
| BytesArbitrary
export type BooleanArbitrary = BaseArbitrary & {
type: `boolean`
}

export type NumberArbitrary = {
export type NumberArbitrary = BaseArbitrary & {
type: `number`
min: number
max: number
isInteger: boolean
}

export type BigIntArbitrary = {
export type BigIntArbitrary = BaseArbitrary & {
type: `bigint`
min?: bigint
max?: bigint
}

export type StringArbitrary = {
export type StringArbitrary = BaseArbitrary & {
type: `string`
minLength?: number
maxLength?: number
}

export type BytesArbitrary = {
export type BytesArbitrary = BaseArbitrary & {
type: `bytes`
}

export type EnumArbitrary = {
export type EnumArbitrary = BaseArbitrary & {
type: `enum`
values: string[]
}

export type ArrayArbitrary = {
export type ArrayArbitrary = BaseArbitrary & {
type: `array`
value: Arbitrary
}

export type DictionaryArbitrary = {
export type DictionaryArbitrary = BaseArbitrary & {
type: `dictionary`
key: Arbitrary
value: Arbitrary
}

export type UnionArbitrary = {
export type UnionArbitrary = BaseArbitrary & {
type: `union`
variants: Arbitrary[]
}

export type RecordArbitrary = {
export type RecordArbitrary = BaseArbitrary & {
type: `record`
properties: Map<string, Arbitrary>
}
92 changes: 74 additions & 18 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
Enum,
IntrinsicType,
Model,
ModelIndexer,
Namespace,
Numeric,
Program,
Expand All @@ -34,7 +35,25 @@ import {
import pascalcase from 'pascalcase'
import keyalesce from 'keyalesce'
import toposort from 'toposort'
import type { Arbitrary, ArbitraryNamespace } from './arbitrary.ts'
import type {
Arbitrary,
ArbitraryNamespace,
ArrayArbitrary,
BigIntArbitrary,
BytesArbitrary,
DictionaryArbitrary,
EnumArbitrary,
IntrinsicArbitrary,
NeverArbitrary,
NullArbitrary,
NumberArbitrary,
RecordArbitrary,
ScalarArbitrary,
StringArbitrary,
UndefinedArbitrary,
UnionArbitrary,
UnknownArbitrary,
} from './arbitrary.ts'
import { numerics } from './numerics.ts'

const convertProgram = (
Expand Down Expand Up @@ -113,7 +132,7 @@ type ConvertTypeOptions = {
constraints: Constraints
}

const convertIntrinsic = (intrinsic: IntrinsicType): Arbitrary => {
const convertIntrinsic = (intrinsic: IntrinsicType): IntrinsicArbitrary => {
switch (intrinsic.name) {
case `null`:
return convertNull(intrinsic)
Expand All @@ -128,23 +147,23 @@ const convertIntrinsic = (intrinsic: IntrinsicType): Arbitrary => {
}
}

const convertNull = ($null: IntrinsicType): Arbitrary =>
const convertNull = ($null: IntrinsicType): NullArbitrary =>
memoize({ type: `null`, name: $null.name })

const convertVoid = ($void: IntrinsicType): Arbitrary =>
const convertVoid = ($void: IntrinsicType): UndefinedArbitrary =>
memoize({ type: `undefined`, name: $void.name })

const convertNever = (never: IntrinsicType): Arbitrary =>
const convertNever = (never: IntrinsicType): NeverArbitrary =>
memoize({ type: `never`, name: never.name })

const convertUnknown = (unknown: IntrinsicType): Arbitrary =>
const convertUnknown = (unknown: IntrinsicType): UnknownArbitrary =>
memoize({ type: `unknown`, name: unknown.name })

const convertScalar = (
program: Program,
scalar: Scalar,
options: ConvertTypeOptions,
): Arbitrary => {
): ScalarArbitrary => {
switch (scalar.name) {
case `int8`:
case `int16`:
Expand All @@ -170,7 +189,7 @@ const convertScalar = (
return convertBoolean(scalar)
default:
if (scalar.baseScalar) {
return convertType(program, scalar.baseScalar, {
return convertScalar(program, scalar.baseScalar, {
...options,
constraints: {
...options.constraints,
Expand All @@ -187,7 +206,7 @@ const convertNumber = (
number: Scalar,
{ constraints }: ConvertTypeOptions,
{ min, max, isInteger }: { min: number; max: number; isInteger: boolean },
): Arbitrary =>
): NumberArbitrary =>
memoize({
type: `number`,
name: number.name,
Expand All @@ -200,35 +219,35 @@ const convertBigInt = (
bigint: Scalar,
{ constraints }: ConvertTypeOptions,
{ min, max }: { min?: bigint; max?: bigint } = {},
): Arbitrary =>
): BigIntArbitrary =>
memoize({
type: `bigint`,
name: bigint.name,
min: maxOrUndefined(constraints.min?.asBigInt() ?? undefined, min),
max: minOrUndefined(constraints.max?.asBigInt() ?? undefined, max),
})

const convertBytes = (bytes: Scalar): Arbitrary =>
const convertBytes = (bytes: Scalar): BytesArbitrary =>
memoize({ type: `bytes`, name: bytes.name })

const convertString = (
string: Scalar,
{ constraints }: ConvertTypeOptions,
): Arbitrary =>
): StringArbitrary =>
memoize({
type: `string`,
name: string.name,
minLength: constraints.minLength?.asNumber() ?? undefined,
maxLength: constraints.maxLength?.asNumber() ?? undefined,
})

const convertBoolean = (boolean: Scalar): Arbitrary =>
const convertBoolean = (boolean: Scalar): ScalarArbitrary =>
memoize({ type: `boolean`, name: boolean.name })

const convertEnum = (
$enum: Enum,
{ propertyName }: ConvertTypeOptions,
): Arbitrary =>
): EnumArbitrary =>
memoize({
type: `enum`,
name: pascalcase($enum.name || propertyName || `Enum`),
Expand All @@ -243,7 +262,7 @@ const convertUnion = (
program: Program,
union: Union,
{ propertyName, constraints }: ConvertTypeOptions,
): Arbitrary =>
): UnionArbitrary =>
memoize({
type: `union`,
name: pascalcase(union.name || propertyName || `Union`),
Expand All @@ -260,10 +279,47 @@ const convertUnion = (
})

const convertModel = (
program: Program,
model: Model,
options: ConvertTypeOptions,
): Arbitrary => {
if (!model.indexer) {
return convertRecord(program, model, options)
}

return (
model.indexer.key.name === `integer` ? convertArray : convertDictionary
)(program, model as Model & { indexer: ModelIndexer }, options)
}

const convertArray = (
program: Program,
model: Model & { indexer: ModelIndexer },
options: ConvertTypeOptions,
): ArrayArbitrary =>
memoize({
type: `array`,
name: pascalcase(model.name || options.propertyName || `Array`),
value: convertType(program, model.indexer.value, options),
})

const convertDictionary = (
program: Program,
model: Model & { indexer: ModelIndexer },
options: ConvertTypeOptions,
): DictionaryArbitrary =>
memoize({
type: `dictionary`,
name: pascalcase(model.name || options.propertyName || `Dictionary`),
key: convertType(program, model.indexer.key, options),
value: convertType(program, model.indexer.value, options),
})

const convertRecord = (
program: Program,
model: Model,
{ propertyName, constraints }: ConvertTypeOptions,
): Arbitrary =>
): RecordArbitrary =>
memoize({
type: `record`,
name: pascalcase(model.name || propertyName || `Record`),
Expand Down Expand Up @@ -298,14 +354,14 @@ type Constraints = {
maxLength?: Numeric
}

const memoize = (arbitrary: Arbitrary): Arbitrary => {
const memoize = <A extends Arbitrary>(arbitrary: A): A => {
const arbitraryKey = getArbitraryKey(arbitrary)
let cachedArbitrary = cachedArbitraries.get(arbitraryKey)
if (!cachedArbitrary) {
cachedArbitrary = arbitrary
cachedArbitraries.set(arbitraryKey, cachedArbitrary)
}
return cachedArbitrary
return cachedArbitrary as A
}

const getArbitraryKey = (arbitrary: Arbitrary): ArbitraryKey => {
Expand Down
22 changes: 12 additions & 10 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,31 @@ test.each([
{
name: `null`,
code: `
model M {
model $Model {
property: null
}
`,
},
{
name: `void`,
code: `
model M {
model $Model {
property: void
}
`,
},
{
name: `never`,
code: `
model M {
model $Model {
property: never
}
`,
},
{
name: `unknown`,
code: `
model M {
model $Model {
property: unknown
}
`,
Expand Down Expand Up @@ -354,13 +354,15 @@ test.each([

// Models
{
name: `model`,
name: `array`,
code: `
model Dog {
@minLength(1)
name: string;
age: int32;
}
model $Array is Array<string>;
`,
},
{
name: `dictionary`,
code: `
model Dictionary is Record<int32>;
`,
},
] satisfies TestCase[])(`$name`, async ({ name, code }) => {
Expand Down
3 changes: 3 additions & 0 deletions test/snapshots/array/arbitraries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as fc from 'fast-check';

export const $Array = fc.array(fc.string());
Loading

0 comments on commit fd5ba3f

Please sign in to comment.