Skip to content

Commit

Permalink
chore: generate typed baseUrl option
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos committed Feb 2, 2025
1 parent ccc7ae4 commit 766ad64
Show file tree
Hide file tree
Showing 347 changed files with 3,062 additions and 424 deletions.
1 change: 1 addition & 0 deletions packages/openapi-ts/src/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const compiler = {
safeAccessExpression: transform.createSafeAccessExpression,
stringLiteral: types.createStringLiteral,
stringToTsNodes: utils.stringToTsNodes,
templateLiteralType: types.createTemplateLiteralType,
transformArrayMap: transform.createArrayMapTransform,
transformArrayMutation: transform.createArrayTransformMutation,
transformDateMutation: transform.createDateTransformMutation,
Expand Down
28 changes: 28 additions & 0 deletions packages/openapi-ts/src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,3 +1012,31 @@ export const createAsExpression = ({
expression: ts.Expression;
type: ts.TypeNode;
}) => ts.factory.createAsExpression(expression, type);

export const createTemplateLiteralType = ({
value,
}: {
value: ReadonlyArray<string | ts.TypeNode>;
}) => {
const spans: Array<ts.TemplateLiteralTypeSpan> = [];
let spanText = '';

for (const item of value.slice(0).reverse()) {
if (typeof item === 'string') {
spanText = `${item}${spanText}`;
} else {
const literal = spans.length
? ts.factory.createTemplateMiddle(spanText)
: ts.factory.createTemplateTail(spanText);
const span = ts.factory.createTemplateLiteralTypeSpan(item, literal);
spans.push(span);
spanText = '';
}
}

const templateLiteralType = ts.factory.createTemplateLiteralType(
ts.factory.createTemplateHead(spanText),
spans.reverse(),
);
return templateLiteralType;
};

Check warning on line 1042 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L1017-L1042

Added lines #L1017 - L1042 were not covered by tests
1 change: 1 addition & 0 deletions packages/openapi-ts/src/ir/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ interface Events {
name: string;
schema: IR.SchemaObject;
}) => void;
server: (args: { server: IR.ServerObject }) => void;
}

type Listeners = {
Expand Down
4 changes: 4 additions & 0 deletions packages/openapi-ts/src/ir/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import type { IR } from './types';
export const parseIR = async ({ context }: { context: IR.Context }) => {
await context.broadcast('before');

for (const server of context.ir.servers ?? []) {
await context.broadcast('server', { server });
}

if (context.ir.components) {
for (const name in context.ir.components.schemas) {
const schema = context.ir.components.schemas[name]!;
Expand Down
119 changes: 119 additions & 0 deletions packages/openapi-ts/src/openApi/2.0.x/parser/__tests__/server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { describe, expect, it } from 'vitest';

import type { IR } from '../../../../ir/types';
import type { OpenApi } from '../../../types';
import { parseServers } from '../server';

describe('parseServers', () => {
it('host + basePath + schemes', () => {
const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = {
// @ts-expect-error
config: {
input: {
path: '',
},
},
ir: {},
spec: {
basePath: '/v1',
host: 'foo.com',
schemes: ['http', 'https'],
},
};
parseServers({ context: context as IR.Context });
expect(context.ir!.servers).toEqual([
{
url: 'http://foo.com/v1',
},
{
url: 'https://foo.com/v1',
},
]);
});

it('schemes + host', () => {
const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = {
// @ts-expect-error
config: {
input: {
path: '',
},
},
ir: {},
spec: {
host: 'foo.com',
schemes: ['ws'],
},
};
parseServers({ context: context as IR.Context });
expect(context.ir!.servers).toEqual([
{
url: 'ws://foo.com',
},
]);
});

it('host + basePath', () => {
const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = {
// @ts-expect-error
config: {
input: {
path: '',
},
},
ir: {},
spec: {
basePath: '/v1',
host: 'foo.com',
},
};
parseServers({ context: context as IR.Context });
expect(context.ir!.servers).toEqual([
{
url: 'foo.com/v1',
},
]);
});

it('host', () => {
const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = {
// @ts-expect-error
config: {
input: {
path: '',
},
},
ir: {},
spec: {
host: 'foo.com',
},
};
parseServers({ context: context as IR.Context });
expect(context.ir!.servers).toEqual([
{
url: 'foo.com',
},
]);
});

it('basePath', () => {
const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = {
// @ts-expect-error
config: {
input: {
path: '',
},
},
ir: {},
spec: {
basePath: '/v1',
},
};
parseServers({ context: context as IR.Context });
expect(context.ir!.servers).toEqual([
{
url: '/v1',
},
]);
});
});
3 changes: 3 additions & 0 deletions packages/openapi-ts/src/openApi/2.0.x/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
import { parseOperation } from './operation';
import { parametersArrayToObject } from './parameter';
import { parseSchema } from './schema';
import { parseServers } from './server';

type PathKeys<T extends keyof PathsObject = keyof PathsObject> =
keyof T extends infer K ? (K extends `/${string}` ? K : never) : never;
Expand Down Expand Up @@ -55,6 +56,8 @@ export const parseV2_0_X = (context: IR.Context<OpenApiV2_0_X>) => {
}
}

parseServers({ context });

for (const path in context.spec.paths) {
if (path.startsWith('x-')) {
continue;
Expand Down
36 changes: 36 additions & 0 deletions packages/openapi-ts/src/openApi/2.0.x/parser/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { IR } from '../../../ir/types';
import { parseUrl } from '../../../utils/url';

export const parseServers = ({ context }: { context: IR.Context }) => {
let schemes: ReadonlyArray<string> = context.spec.schemes ?? [];
let host = context.spec.host ?? '';
const path = context.spec.basePath ?? '';

if (typeof context.config.input.path === 'string') {
const url = parseUrl(context.config.input.path);

if (!schemes.length) {
if (url.protocol) {
schemes = [url.protocol] as typeof schemes;
}

Check warning on line 15 in packages/openapi-ts/src/openApi/2.0.x/parser/server.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/2.0.x/parser/server.ts#L14-L15

Added lines #L14 - L15 were not covered by tests
}

if (!host) {
host = `${url.host}${url.port ? `:${url.port}` : ''}`;
}
}

if (!schemes.length) {
schemes = [''];
}

const servers = schemes
.map((scheme) => `${scheme ? `${scheme}://` : ''}${host}${path}`)
.filter(Boolean);

if (servers.length) {
context.ir.servers = servers.map((url) => ({
url,
}));
}
};
4 changes: 4 additions & 0 deletions packages/openapi-ts/src/openApi/3.0.x/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export const parseV3_0_X = (context: IR.Context<OpenApiV3_0_X>) => {
}
}

if (context.spec.servers) {
context.ir.servers = context.spec.servers;
}

for (const path in context.spec.paths) {
const pathItem = context.spec.paths[path as keyof PathsObject]!;

Expand Down
4 changes: 4 additions & 0 deletions packages/openapi-ts/src/openApi/3.0.x/parser/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ export const parseOperation = ({
context.ir.paths[path] = {};
}

if (operation.servers) {
context.ir.servers = [...(context.ir.servers ?? []), ...operation.servers];
}

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

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/3.0.x/parser/operation.ts#L240-L241

Added lines #L240 - L241 were not covered by tests

operation.id = operationToId({
context,
id: operation.operationId,
Expand Down
4 changes: 4 additions & 0 deletions packages/openapi-ts/src/openApi/3.1.x/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export const parseV3_1_X = (context: IR.Context<OpenApiV3_1_X>) => {
}
}

if (context.spec.servers) {
context.ir.servers = context.spec.servers;
}

for (const path in context.spec.paths) {
const pathItem = context.spec.paths[path as keyof PathsObject]!;

Expand Down
4 changes: 4 additions & 0 deletions packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ export const parseOperation = ({
context.ir.paths[path] = {};
}

if (operation.servers) {
context.ir.servers = [...(context.ir.servers ?? []), ...operation.servers];
}

Check warning on line 226 in packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts#L225-L226

Added lines #L225 - L226 were not covered by tests

operation.id = operationToId({
context,
id: operation.operationId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import ts from 'typescript';

import { compiler } from '../../../compiler';
import type { Identifier } from '../../../generate/files';
import type { IR } from '../../../ir/types';
import { parseUrl } from '../../../utils/url';
import type { Plugin } from '../../types';
import { getClientBaseUrlKey } from '../client-core/utils';
import { typesId } from './ref';
import type { Config } from './types';

const stringType = compiler.keywordTypeNode({ keyword: 'string' });

const serverToBaseUrlType = ({ server }: { server: IR.ServerObject }) => {
const url = parseUrl(server.url);

if (url.protocol && url.host) {
return compiler.literalTypeNode({
literal: compiler.stringLiteral({ text: server.url }),
});
}

return compiler.templateLiteralType({
value: [
url.protocol || stringType,

Check warning on line 25 in packages/openapi-ts/src/plugins/@hey-api/typescript/clientOptions.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/plugins/@hey-api/typescript/clientOptions.ts#L22-L25

Added lines #L22 - L25 were not covered by tests
'://',
url.host || stringType,
url.port ? `:${url.port}` : '',
url.path || '',
],
});
};

export const createClientOptions = ({
context,
identifier,
servers,
}: {
context: IR.Context;
identifier: Identifier;
plugin: Plugin.Instance<Config>;
servers: ReadonlyArray<IR.ServerObject>;
}) => {
const file = context.file({ id: typesId })!;

if (!identifier.name) {
return;
}

Check warning on line 48 in packages/openapi-ts/src/plugins/@hey-api/typescript/clientOptions.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/plugins/@hey-api/typescript/clientOptions.ts#L47-L48

Added lines #L47 - L48 were not covered by tests

const typeClientOptions = compiler.typeAliasDeclaration({
exportType: true,
name: identifier.name,
type: compiler.typeInterfaceNode({
properties: [
{
name: getClientBaseUrlKey(context.config),
type: compiler.typeUnionNode({
types: [
...servers.map((server) =>
serverToBaseUrlType({
server,
}),
),
servers.length
? compiler.typeIntersectionNode({
types: [stringType, ts.factory.createTypeLiteralNode([])],
})
: stringType,

Check warning on line 68 in packages/openapi-ts/src/plugins/@hey-api/typescript/clientOptions.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/plugins/@hey-api/typescript/clientOptions.ts#L68

Added line #L68 was not covered by tests
],
}),
},
],
useLegacyResolution: false,
}),
});

file.add(typeClientOptions);
};
Loading

0 comments on commit 766ad64

Please sign in to comment.