Skip to content

Commit

Permalink
fix: handle additionalProperties boolean in experimental parser
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos committed Nov 6, 2024
1 parent 23a7484 commit 7a1a419
Show file tree
Hide file tree
Showing 51 changed files with 569 additions and 92 deletions.
5 changes: 5 additions & 0 deletions .changeset/six-boxes-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': patch
---

fix: handle additionalProperties: boolean in experimental parser
12 changes: 11 additions & 1 deletion packages/openapi-ts/src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,23 @@ export const toParameterDeclarations = (parameters: FunctionParameter[]) =>
export const createKeywordTypeNode = ({
keyword,
}: {
keyword: 'any' | 'boolean' | 'number' | 'string' | 'undefined' | 'unknown';
keyword:
| 'any'
| 'boolean'
| 'never'
| 'number'
| 'string'
| 'undefined'
| 'unknown';
}) => {
let kind: ts.KeywordTypeSyntaxKind = ts.SyntaxKind.AnyKeyword;
switch (keyword) {
case 'boolean':
kind = ts.SyntaxKind.BooleanKeyword;
break;
case 'never':
kind = ts.SyntaxKind.NeverKeyword;
break;
case 'number':
kind = ts.SyntaxKind.NumberKeyword;
break;
Expand Down
1 change: 1 addition & 0 deletions packages/openapi-ts/src/ir/ir.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export interface IRSchemaObject
| 'array'
| 'boolean'
| 'enum'
| 'never'
| 'null'
| 'number'
| 'object'
Expand Down
13 changes: 3 additions & 10 deletions packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,9 @@ const parseObject = ({

if (schema.additionalProperties !== undefined) {
if (typeof schema.additionalProperties === 'boolean') {
if (schema.additionalProperties) {
// no need to add "any" additional properties if there are no defined properties
if (irSchema.properties) {
irSchema.additionalProperties = {
type: 'unknown',
};
}
} else {
// TODO: parser - handle additional properties: false
}
irSchema.additionalProperties = {
type: schema.additionalProperties ? 'unknown' : 'never',
};

Check warning on line 165 in packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts#L163-L165

Added lines #L163 - L165 were not covered by tests
} else {
const irAdditionalPropertiesSchema = schemaToIrSchema({
context,
Expand Down
13 changes: 3 additions & 10 deletions packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,9 @@ const parseObject = ({

if (schema.additionalProperties !== undefined) {
if (typeof schema.additionalProperties === 'boolean') {
if (schema.additionalProperties) {
// no need to add "any" additional properties if there are no defined properties
if (irSchema.properties) {
irSchema.additionalProperties = {
type: 'unknown',
};
}
} else {
// TODO: parser - handle additional properties: false
}
irSchema.additionalProperties = {
type: schema.additionalProperties ? 'unknown' : 'never',
};
} else {
const irAdditionalPropertiesSchema = schemaToIrSchema({
context,
Expand Down
17 changes: 14 additions & 3 deletions packages/openapi-ts/src/plugins/@hey-api/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ const objectTypeToIdentifier = ({
}) => {
let indexProperty: Property | undefined;
const schemaProperties: Array<Property> = [];
const indexPropertyItems: Array<IRSchemaObject> = [];
let indexPropertyItems: Array<IRSchemaObject> = [];
const required = schema.required ?? [];
let hasOptionalProperties = false;

Expand All @@ -424,8 +424,15 @@ const objectTypeToIdentifier = ({
}
}

if (schema.additionalProperties) {
indexPropertyItems.unshift(schema.additionalProperties);
if (
schema.additionalProperties &&
(schema.additionalProperties.type !== 'never' || !indexPropertyItems.length)
) {
if (schema.additionalProperties.type === 'never') {
indexPropertyItems = [schema.additionalProperties];
} else {
indexPropertyItems.unshift(schema.additionalProperties);
}

if (hasOptionalProperties) {
indexPropertyItems.push({
Expand Down Expand Up @@ -555,6 +562,10 @@ const schemaTypeToIdentifier = ({
namespace,
schema: schema as SchemaWithType<'enum'>,
});
case 'never':
return compiler.keywordTypeNode({
keyword: 'never',
});
case 'null':
return compiler.literalTypeNode({
literal: compiler.null(),
Expand Down
71 changes: 71 additions & 0 deletions packages/openapi-ts/test/3.0.x.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { describe, expect, it } from 'vitest';

import { createClient } from '../';
import type { UserConfig } from '../src/types/config';
import { getFilePaths } from './utils';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const VERSION = '3.0.x';

const outputDir = path.join(__dirname, 'generated', VERSION);

describe(`OpenAPI ${VERSION}`, () => {
const createConfig = (userConfig: UserConfig): UserConfig => ({
client: '@hey-api/client-fetch',
experimentalParser: true,
plugins: ['@hey-api/types'],
...userConfig,
input: path.join(
__dirname,
'spec',
VERSION,
typeof userConfig.input === 'string' ? userConfig.input : '',
),
output: path.join(
outputDir,
typeof userConfig.output === 'string' ? userConfig.output : '',
),
});

const scenarios = [
{
config: createConfig({
input: 'additional-properties-false.json',
output: 'additional-properties-false',
}),
description: 'forbids arbitrary properties on objects',
},
{
config: createConfig({
input: 'additional-properties-true.json',
output: 'additional-properties-true',
}),
description: 'allows arbitrary properties on objects',
},
];

it.each(scenarios)('$description', async ({ config }) => {
await createClient(config);

const outputPath = typeof config.output === 'string' ? config.output : '';
const filePaths = getFilePaths(outputPath);

filePaths.forEach((filePath) => {
const fileContent = readFileSync(filePath, 'utf-8');
expect(fileContent).toMatchFileSnapshot(
path.join(
__dirname,
'__snapshots__',
VERSION,
filePath.slice(outputDir.length + 1),
),
);
});
});
});
14 changes: 14 additions & 0 deletions packages/openapi-ts/test/3.1.x.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ describe(`OpenAPI ${VERSION}`, () => {
});

const scenarios = [
{
config: createConfig({
input: 'additional-properties-false.json',
output: 'additional-properties-false',
}),
description: 'forbids arbitrary properties on objects',
},
{
config: createConfig({
input: 'additional-properties-true.json',
output: 'additional-properties-true',
}),
description: 'allows arbitrary properties on objects',
},
{
config: createConfig({
input: 'duplicate-null.json',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This file is auto-generated by @hey-api/openapi-ts
export * from './types.gen';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file is auto-generated by @hey-api/openapi-ts

export type Foo = {
foo: string;
};

export type Bar = Foo & {
[key: string]: never;
};

export type Baz = Foo & {
bar: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This file is auto-generated by @hey-api/openapi-ts
export * from './types.gen';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This file is auto-generated by @hey-api/openapi-ts

export type Foo = {
foo: string;
[key: string]: unknown | string;
};

export type Bar = Foo & {
[key: string]: unknown;
};

export type Baz = Foo & {
bar: string;
[key: string]: unknown | string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,9 @@ export type FreeFormObjectWithoutAdditionalProperties = {};
/**
* This is a free-form object with additionalProperties: true.
*/
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {};
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {
[key: string]: unknown;
};

/**
* This is a free-form object with additionalProperties: {}.
Expand Down Expand Up @@ -920,7 +922,9 @@ export type Generic_Schema_Duplicate_Issue_1_System_Boolean_ = {
item?: boolean;
error?: string | null;
readonly hasError?: boolean;
data?: {};
data?: {
[key: string]: never;
};
};

export type Generic_Schema_Duplicate_Issue_1_System_String_ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,9 @@ export type FreeFormObjectWithoutAdditionalProperties = {};
/**
* This is a free-form object with additionalProperties: true.
*/
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {};
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {
[key: string]: unknown;
};

/**
* This is a free-form object with additionalProperties: {}.
Expand Down Expand Up @@ -920,7 +922,9 @@ export type Generic_Schema_Duplicate_Issue_1_System_Boolean_ = {
item?: boolean;
error?: string | null;
readonly hasError?: boolean;
data?: {};
data?: {
[key: string]: never;
};
};

export type Generic_Schema_Duplicate_Issue_1_System_String_ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,9 @@ export type FreeFormObjectWithoutAdditionalProperties = {};
/**
* This is a free-form object with additionalProperties: true.
*/
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {};
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {
[key: string]: unknown;
};

/**
* This is a free-form object with additionalProperties: {}.
Expand Down Expand Up @@ -920,7 +922,9 @@ export type Generic_Schema_Duplicate_Issue_1_System_Boolean_ = {
item?: boolean;
error?: string | null;
readonly hasError?: boolean;
data?: {};
data?: {
[key: string]: never;
};
};

export type Generic_Schema_Duplicate_Issue_1_System_String_ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,9 @@ export type FreeFormObjectWithoutAdditionalProperties = {};
/**
* This is a free-form object with additionalProperties: true.
*/
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {};
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {
[key: string]: unknown;
};

/**
* This is a free-form object with additionalProperties: {}.
Expand Down Expand Up @@ -920,7 +922,9 @@ export type Generic_Schema_Duplicate_Issue_1_System_Boolean_ = {
item?: boolean;
error?: string | null;
readonly hasError?: boolean;
data?: {};
data?: {
[key: string]: never;
};
};

export type Generic_Schema_Duplicate_Issue_1_System_String_ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,9 @@ export type FreeFormObjectWithoutAdditionalProperties = {};
/**
* This is a free-form object with additionalProperties: true.
*/
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {};
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {
[key: string]: unknown;
};

/**
* This is a free-form object with additionalProperties: {}.
Expand Down Expand Up @@ -920,7 +922,9 @@ export type Generic_Schema_Duplicate_Issue_1_System_Boolean_ = {
item?: boolean;
error?: string | null;
readonly hasError?: boolean;
data?: {};
data?: {
[key: string]: never;
};
};

export type Generic_Schema_Duplicate_Issue_1_System_String_ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,9 @@ export type FreeFormObjectWithoutAdditionalProperties = {};
/**
* This is a free-form object with additionalProperties: true.
*/
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {};
export type FreeFormObjectWithAdditionalPropertiesEqTrue = {
[key: string]: unknown;
};

/**
* This is a free-form object with additionalProperties: {}.
Expand Down Expand Up @@ -920,7 +922,9 @@ export type Generic_Schema_Duplicate_Issue_1_System_Boolean_ = {
item?: boolean;
error?: string | null;
readonly hasError?: boolean;
data?: {};
data?: {
[key: string]: never;
};
};

export type Generic_Schema_Duplicate_Issue_1_System_String_ = {
Expand Down
Loading

0 comments on commit 7a1a419

Please sign in to comment.