Skip to content

Commit

Permalink
Merge pull request #857 from BitGo/DX-637-support-keys-in-record-codecs
Browse files Browse the repository at this point in the history
feat: support `domain` for `t.record` codec
  • Loading branch information
anshchaturvedi authored Aug 1, 2024
2 parents 4373ca8 + d041033 commit 87de81f
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/openapi-generator/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type Object = {

export type RecordObject = {
type: 'record';
domain?: Schema;
codomain: Schema;
};

Expand Down
4 changes: 2 additions & 2 deletions packages/openapi-generator/src/knownImports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ export const KNOWN_IMPORTS: KnownImports = {
required: Object.keys(props),
});
},
record: (_, _domain, codomain) => {
record: (_, domain, codomain) => {
if (!codomain) {
return E.left('Codomain of record must be specified');
} else {
return E.right({ type: 'record', codomain });
return E.right({ type: 'record', domain, codomain });
}
},
union: (_, schema) => {
Expand Down
19 changes: 17 additions & 2 deletions packages/openapi-generator/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,24 @@ function schemaToOpenAPI(
}
case 'record':
const additionalProperties = schemaToOpenAPI(schema.codomain);
if (additionalProperties === undefined) {
return undefined;
if (additionalProperties === undefined) return undefined;

if (schema.domain !== undefined) {
const keys = schemaToOpenAPI(schema.domain) as OpenAPIV3.SchemaObject;
if (keys.type === 'string' && keys.enum !== undefined) {
const properties = keys.enum.reduce((acc, key) => {
return { ...acc, [key]: additionalProperties };
}, {});

return {
type: 'object',
properties,
...defaultOpenAPIObject,
required: keys.enum,
};
}
}

return {
type: 'object',
additionalProperties,
Expand Down
14 changes: 6 additions & 8 deletions packages/openapi-generator/src/optimize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,12 @@ export function optimize(schema: Schema): Schema {
}
return { type: 'array', items: optimized };
} else if (schema.type === 'record') {
if (schema.comment) {
return {
type: 'record',
codomain: optimize(schema.codomain),
comment: schema.comment,
};
}
return { type: 'record', codomain: optimize(schema.codomain) };
return {
type: 'record',
...(schema.domain ? { domain: optimize(schema.domain) } : {}),
codomain: optimize(schema.codomain),
...(schema.comment ? { comment: schema.comment } : {}),
};
} else if (schema.type === 'tuple') {
const schemas = schema.schemas.map(optimize);
return { type: 'tuple', schemas };
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-generator/test/codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export const FOO = t.record(t.string, t.number);
`;

testCase('record type is parsed', RECORD, {
FOO: { type: 'record', codomain: { type: 'number', primitive: true } },
FOO: { type: 'record', domain: {type: 'string', primitive: true}, codomain: { type: 'number', primitive: true } },
});

const ENUM = `
Expand Down
140 changes: 139 additions & 1 deletion packages/openapi-generator/test/openapi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3938,4 +3938,142 @@ testCase("route with nested array examples", ROUTE_WITH_NESTED_ARRAY_EXAMPLES, {
}
}
}
});
});

const ROUTE_WITH_RECORD_TYPES = `
import * as t from 'io-ts';
import * as h from '@api-ts/io-ts-http';
const ValidKeys = t.keyof({ name: "name", age: "age", address: "address" });
const PersonObject = t.type({ bigName: t.string, bigAge: t.number });
export const route = h.httpRoute({
path: '/foo',
method: 'GET',
request: h.httpRequest({
query: {
name: t.string,
},
}),
response: {
200: {
person: t.record(ValidKeys, t.string),
anotherPerson: t.record(ValidKeys, PersonObject),
bigPerson: t.record(t.string, t.string),
anotherBigPerson: t.record(t.string, PersonObject),
}
},
});
`;

testCase("route with record types", ROUTE_WITH_RECORD_TYPES, {
openapi: '3.0.3',
info: {
title: 'Test',
version: '1.0.0'
},
paths: {
'/foo': {
get: {
parameters: [
{
name: 'name',
in: 'query',
required: true,
schema: {
type: 'string'
}
}
],
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
// becomes t.type()
person: {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'string' },
address: { type: 'string' }
},
required: [ 'name', 'age', 'address' ]
},
// becomes t.type()
anotherPerson: {
type: 'object',
properties: {
name: {
type: 'object',
properties: {
bigName: { type: 'string' },
bigAge: { type: 'number' }
},
required: [ 'bigName', 'bigAge' ]
},
age: {
type: 'object',
properties: {
bigName: { type: 'string' },
bigAge: { type: 'number' }
},
required: [ 'bigName', 'bigAge' ]
},
address: {
type: 'object',
properties: {
bigName: { type: 'string' },
bigAge: { type: 'number' }
},
required: [ 'bigName', 'bigAge' ]
}
},
required: [ 'name', 'age', 'address' ]
},
bigPerson: {
// stays as t.record()
type: 'object',
additionalProperties: { type: 'string' }
},
anotherBigPerson: {
// stays as t.record()
type: 'object',
additionalProperties: {
type: 'object',
properties: {
bigName: { type: 'string' },
bigAge: { type: 'number' }
},
required: [ 'bigName', 'bigAge' ]
}
}
},
required: [ 'person', 'anotherPerson', 'bigPerson', 'anotherBigPerson' ]
}
}
}
}
}
}
}
},
components: {
schemas: {
ValidKeys: {
title: 'ValidKeys',
type: 'string',
enum: [ 'name', 'age', 'address' ]
},
PersonObject: {
title: 'PersonObject',
type: 'object',
properties: { bigName: { type: 'string' }, bigAge: { type: 'number' } },
required: [ 'bigName', 'bigAge' ]
}
}
}
});

0 comments on commit 87de81f

Please sign in to comment.