Skip to content

Commit

Permalink
feat: make output closer to typespec files
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerAberbach committed Dec 8, 2024
1 parent a195075 commit 3e5ef9e
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 94 deletions.
132 changes: 68 additions & 64 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import type {
RecordArbitrary,
ReferenceArbitrary,
StringArbitrary,
UnionArbitrary,
} from './arbitrary.ts'
import { numerics } from './numerics.ts'

Expand Down Expand Up @@ -86,8 +85,7 @@ const convertNamespace = (
name: namespace.name,
namespaces: pipe(
values(namespace.namespaces),
// Don't convert the built-in namespace.
filter(namespace => namespace.name !== `TypeSpec`),
filter(namespace => !isTypeSpecNamespace(namespace)),
map(namespace => convertNamespace(program, namespace)),
reduce(toArray()),
),
Expand All @@ -112,10 +110,13 @@ const convertType = (
case `Intrinsic`:
return convertIntrinsic(type)
case `Scalar`:
return convertScalar(program, type, constraints)
case `Enum`:
return convertEnum(type)
case `Union`:
return convertUnion(program, type, constraints)
case `Model`:
return convertReference(program, type, constraints)
return convertModel(program, type, constraints)
}

throw new Error(`Unhandled type: ${type.kind}`)
Expand All @@ -136,69 +137,59 @@ const convertIntrinsic = (intrinsic: IntrinsicType): IntrinsicArbitrary => {
}
}

const convertReference = (
program: Program,
type: Scalar | Enum | Union | Model,
constraints: Constraints,
): Arbitrary => {
let arbitrary: Arbitrary
switch (type.kind) {
case `Scalar`:
arbitrary = convertScalar(program, type, constraints)
break
case `Enum`:
arbitrary = convertEnum(type)
break
case `Union`:
arbitrary = convertUnion(program, type, constraints)
break
case `Model`:
arbitrary = convertModel(program, type, constraints)
break
}

const { name } = type
return name ? ref(name, arbitrary) : arbitrary
}

const convertScalar = (
program: Program,
scalar: Scalar,
constraints: Constraints,
): Arbitrary => {
let arbitrary: Arbitrary | undefined
switch (scalar.name) {
case `int8`:
case `int16`:
case `int32`:
case `safeint`:
case `float32`:
case `float64`:
return convertNumber(constraints, numerics[scalar.name])
arbitrary = convertNumber(constraints, numerics[scalar.name])
break
case `float`:
case `decimal128`:
case `decimal`:
case `numeric`:
return convertNumber(constraints, numerics.float64)
arbitrary = convertNumber(constraints, numerics.float64)
break
case `int64`:
return convertBigInt(constraints, numerics.int64)
arbitrary = convertBigInt(constraints, numerics.int64)
break
case `integer`:
return convertBigInt(constraints)
arbitrary = convertBigInt(constraints)
break
case `bytes`:
return memoize({ type: `bytes` })
arbitrary = memoize({ type: `bytes` })
break
case `string`:
return convertString(constraints)
arbitrary = convertString(constraints)
break
case `boolean`:
return memoize({ type: `boolean` })
arbitrary = memoize({ type: `boolean` })
break
default:
if (scalar.baseScalar) {
return convertType(program, scalar.baseScalar, {
arbitrary = convertType(program, scalar.baseScalar, {
...constraints,
...getConstraints(program, scalar),
})
}
break
}

if (!arbitrary) {
throw new Error(`Unhandled Scalar: ${scalar.name}`)
}

throw new Error(`Unhandled Scalar: ${scalar.name}`)
return isTypeSpecNamespace(scalar.namespace)
? arbitrary
: ref(scalar.name, arbitrary)
}

const convertNumber = (
Expand Down Expand Up @@ -230,23 +221,26 @@ const convertString = (constraints: Constraints): StringArbitrary =>
})

const convertEnum = ($enum: Enum): Arbitrary =>
memoize({
type: `enum`,
values: pipe(
$enum.members,
map(([, { name, value }]) =>
JSON.stringify(value === undefined ? name : value),
ref(
$enum.name,
memoize({
type: `enum`,
values: pipe(
$enum.members,
map(([, { name, value }]) =>
JSON.stringify(value === undefined ? name : value),
),
reduce(toArray()),
),
reduce(toArray()),
),
})
}),
)

const convertUnion = (
program: Program,
union: Union,
constraints: Constraints,
): UnionArbitrary =>
memoize({
): Arbitrary => {
const arbitrary = memoize({
type: `union`,
variants: pipe(
union.variants,
Expand All @@ -262,6 +256,8 @@ const convertUnion = (
reduce(toArray()),
),
})
return union.name ? ref(union.name, arbitrary) : arbitrary
}

const convertModel = (
program: Program,
Expand All @@ -287,31 +283,36 @@ const convertModel = (
)

const baseModel = model.baseModel ?? model.sourceModel
let arbitrary: Arbitrary
if (baseModel && concreteProperties.size === 0) {
// The model is just `model A extends B` or `model A is B` so we can just
// convert `B`.
return convertType(program, baseModel, {
arbitrary = convertType(program, baseModel, {
...constraints,
...getConstraints(program, model),
})
}

if (sourceModels.size === 0) {
return model.indexer
} else if (sourceModels.size === 0) {
arbitrary = model.indexer
? convertModelIndexer(program, model.indexer, constraints)
: convertRecord(program, model, constraints)
} else {
arbitrary = memoize({
type: `merged`,
arbitraries: pipe(
concat(
map(model => convertType(program, model, constraints), sourceModels),
concreteProperties.size > 0
? [convertRecord(program, model, constraints, concreteProperties)]
: [],
),
reduce(toArray()),
),
})
}

const arbitraries = pipe(
concat(
map(model => convertType(program, model, constraints), sourceModels),
concreteProperties.size > 0
? [convertRecord(program, model, constraints, concreteProperties)]
: [],
),
reduce(toArray()),
)
return memoize({ type: `merged`, arbitraries })
return isTypeSpecNamespace(model.namespace)
? arbitrary
: ref(model.name, arbitrary)
}

const convertModelIndexer = (
Expand Down Expand Up @@ -549,6 +550,9 @@ const getDirectArbitraryDependencies = (
}
}

const isTypeSpecNamespace = (namespace?: Namespace): boolean =>
namespace?.name === `TypeSpec`

const minOrUndefined = <
const A extends bigint | number | undefined,
const B extends bigint | number | undefined,
Expand Down
14 changes: 5 additions & 9 deletions test/snapshots/array/arbitraries.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import * as fc from 'fast-check';

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

const Array = fc.array(string);

export const $Array = Array;

export const MinItemsArray = fc.array(string, {
export const MinItemsArray = fc.array(fc.string(), {
minLength: 3,
});

export const Min0ItemsArray = Array;
export const Min0ItemsArray = fc.array(fc.string());

export const MaxItemsArray = fc.array(string, {
export const MaxItemsArray = fc.array(fc.string(), {
maxLength: 12,
});

export const MinMaxItemsArray = fc.array(string, {
export const MinMaxItemsArray = fc.array(fc.string(), {
minLength: 3,
maxLength: 12,
});
6 changes: 2 additions & 4 deletions test/snapshots/float32/arbitraries.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as fc from 'fast-check';

const float32 = fc.float();

export const Float32 = float32;
export const Float32 = fc.float();

export const MinValueFloat32 = fc.float({
min: -3.140000104904175,
Expand All @@ -21,4 +19,4 @@ export const MinMaxValueFloat32 = fc.float({
max: 3.140000104904175,
});

export const RedundantlyMinMaxValueFloat32 = float32;
export const RedundantlyMinMaxValueFloat32 = fc.float();
9 changes: 5 additions & 4 deletions test/snapshots/int16/arbitraries.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import * as fc from 'fast-check';

const int16 = fc.integer({
export const Int16 = fc.integer({
min: -32768,
max: 32767,
});

export const Int16 = int16;

export const MinValueInt16 = fc.integer({
min: -200,
max: 32767,
Expand All @@ -26,4 +24,7 @@ export const MinMaxValueInt16 = fc.integer({
max: 345,
});

export const RedundantlyMinMaxValueInt16 = int16;
export const RedundantlyMinMaxValueInt16 = fc.integer({
min: -32768,
max: 32767,
});
6 changes: 2 additions & 4 deletions test/snapshots/int32/arbitraries.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as fc from 'fast-check';

const int32 = fc.integer();

export const Int32 = int32;
export const Int32 = fc.integer();

export const MinValueInt32 = fc.integer({
min: -40000,
Expand All @@ -19,4 +17,4 @@ export const MinMaxValueInt32 = fc.integer({
max: 40000,
});

export const RedundantlyMinMaxValueInt32 = int32;
export const RedundantlyMinMaxValueInt32 = fc.integer();
9 changes: 5 additions & 4 deletions test/snapshots/int8/arbitraries.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import * as fc from 'fast-check';

const int8 = fc.integer({
export const Int8 = fc.integer({
min: -128,
max: 127,
});

export const Int8 = int8;

export const MinValueInt8 = fc.integer({
min: -10,
max: 127,
Expand All @@ -26,4 +24,7 @@ export const MinMaxValueInt8 = fc.integer({
max: 20,
});

export const RedundantlyMinMaxValueInt8 = int8;
export const RedundantlyMinMaxValueInt8 = fc.integer({
min: -128,
max: 127,
});
8 changes: 3 additions & 5 deletions test/snapshots/model-spread/arbitraries.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import * as fc from 'fast-check';

const string = fc.string();

export const Model2 = fc.record({
b: string,
b: fc.string(),
});

export const Model1 = fc.record({
Expand All @@ -14,9 +12,9 @@ export const $Model = fc
.tuple(
Model1,
Model2,
fc.dictionary(string, fc.boolean()),
fc.dictionary(fc.string(), fc.boolean()),
fc.record({
c: string,
c: fc.string(),
}),
)
.map(values => Object.assign(...values));

0 comments on commit 3e5ef9e

Please sign in to comment.