Skip to content

Commit 56cbd20

Browse files
authored
feat(openapi): rewrite OpenAPIGenerator (#208)
* wip * wip * wip * wip * fix types * utils tests * custom tests * wip * tests * tests * disable stripInternal * @scalar/openapi-types -> openapi-types * operationId * fix types * sync tests
1 parent 1431467 commit 56cbd20

29 files changed

+2011
-1199
lines changed

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default antfu({
2121
],
2222
'no-restricted-imports': ['error', {
2323
patterns: [{
24-
group: ['json-schema-typed', 'json-schema-typed/*'],
24+
group: ['json-schema-typed', 'json-schema-typed/*', 'openapi-types', 'openapi-types/*'],
2525
message: 'Please import from @orpc/openapi instead',
2626
}],
2727
}],

packages/contract/src/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { HTTPMethod, InputStructure } from './route'
1+
import type { HTTPMethod, InputStructure, OutputStructure } from './route'
22

33
export interface ContractConfig {
44
defaultMethod: HTTPMethod
55
defaultSuccessStatus: number
66
defaultSuccessDescription: string
77
defaultInputStructure: InputStructure
8-
defaultOutputStructure: InputStructure
8+
defaultOutputStructure: OutputStructure
99
}
1010

1111
const DEFAULT_CONFIG: ContractConfig = {

packages/openapi/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"@orpc/standard-server-fetch": "workspace:*",
7474
"@orpc/standard-server-node": "workspace:*",
7575
"json-schema-typed": "^8.0.1",
76-
"openapi3-ts": "^4.4.0",
76+
"openapi-types": "^12.1.3",
7777
"rou3": "^0.5.1"
7878
},
7979
"devDependencies": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './openapi-codec'
22
export * from './openapi-matcher'
3+
export * from './utils'

packages/openapi/src/adapters/standard/openapi-matcher.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { StandardMatcher, StandardMatchResult } from '@orpc/server/standard
33
import { type AnyContractProcedure, fallbackContractConfig, type HTTPPath } from '@orpc/contract'
44
import { convertPathToHttpPath, createContractedProcedure, eachContractProcedure, getLazyRouterPrefix, getRouterChild, isProcedure, unlazy } from '@orpc/server'
55
import { addRoute, createRouter, findRoute } from 'rou3'
6-
import { standardizeHTTPPath } from '../../utils'
6+
import { decodeParams, toRou3Pattern } from './utils'
77

88
export class OpenAPIMatcher implements StandardMatcher {
99
private readonly tree = createRouter<{
@@ -97,14 +97,3 @@ export class OpenAPIMatcher implements StandardMatcher {
9797
}
9898
}
9999
}
100-
101-
/**
102-
* {@link https://github.com/unjs/rou3}
103-
*/
104-
function toRou3Pattern(path: HTTPPath): string {
105-
return standardizeHTTPPath(path).replace(/\{\+([^}]+)\}/g, '**:$1').replace(/\{([^}]+)\}/g, ':$1')
106-
}
107-
108-
function decodeParams(params: Record<string, string>): Record<string, string> {
109-
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, decodeURIComponent(value)]))
110-
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { decodeParams, toRou3Pattern } from './utils'
2+
3+
it('toRou3Pattern', () => {
4+
expect(toRou3Pattern('/api/v1/users/{id}')).toBe('/api/v1/users/:id')
5+
expect(toRou3Pattern('/api/v1/users/{+id}')).toBe('/api/v1/users/**:id')
6+
expect(toRou3Pattern('/api/v1/users/name')).toBe('/api/v1/users/name')
7+
expect(toRou3Pattern('/api/v1/users/name{id}')).toBe('/api/v1/users/name{id}')
8+
})
9+
10+
it('decodeParams', () => {
11+
expect(decodeParams({ id: '1' })).toEqual({ id: '1' })
12+
expect(decodeParams({ id: '1%2B1' })).toEqual({ id: '1+1' })
13+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { HTTPPath } from '@orpc/contract'
2+
import { standardizeHTTPPath } from '../../openapi-utils'
3+
4+
/**
5+
* {@link https://github.com/unjs/rou3}
6+
*
7+
* @internal
8+
*/
9+
export function toRou3Pattern(path: HTTPPath): string {
10+
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, '/**:$1').replace(/\/\{([^}]+)\}/g, '/:$1')
11+
}
12+
13+
/**
14+
* @internal
15+
*/
16+
export function decodeParams(params: Record<string, string>): Record<string, string> {
17+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, decodeURIComponent(value)]))
18+
}

packages/openapi/src/index.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
/** unnoq */
2-
3-
import { setOperationExtender } from './openapi-operation-extender'
1+
import { customOpenAPIOperation } from './openapi-custom'
42

53
export * from './openapi'
6-
export * from './openapi-content-builder'
4+
export * from './openapi-custom'
75
export * from './openapi-generator'
8-
export * from './openapi-operation-extender'
9-
export * from './openapi-parameters-builder'
10-
export * from './openapi-path-parser'
6+
export * from './openapi-utils'
117
export * from './schema'
128
export * from './schema-converter'
139
export * from './schema-utils'
14-
export * from './utils'
1510

1611
export const oo = {
17-
spec: setOperationExtender,
12+
spec: customOpenAPIOperation,
1813
}

packages/openapi/src/openapi-content-builder.ts

Lines changed: 0 additions & 44 deletions
This file was deleted.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import type { OpenAPI } from './openapi'
2+
import { oc } from '@orpc/contract'
3+
import { os } from '@orpc/server'
4+
import { applyCustomOpenAPIOperation, customOpenAPIOperation, getCustomOpenAPIOperation } from './openapi-custom'
5+
6+
it('customOpenAPIOperation & getCustomOpenAPIOperation', () => {
7+
const customed = customOpenAPIOperation({ value: 123 }, { security: [{ bearerAuth: [] }] })
8+
9+
expect(customed).toEqual({ value: 123 })
10+
expect(getCustomOpenAPIOperation(customed)).toEqual({ security: [{ bearerAuth: [] }] })
11+
})
12+
13+
describe('applyCustomOpenAPIOperation', () => {
14+
it('no custom operation', () => {
15+
const procedure = os.handler(() => {})
16+
17+
const operation: OpenAPI.OperationObject = {
18+
parameters: [{
19+
name: 'id',
20+
in: 'path',
21+
required: true,
22+
schema: {
23+
type: 'string',
24+
},
25+
}],
26+
}
27+
28+
expect(applyCustomOpenAPIOperation(operation, procedure)).toBe(operation)
29+
})
30+
31+
it('custom at errors', () => {
32+
const contract = oc.errors({
33+
AUTHENTICATION_FAILED: customOpenAPIOperation({}, {
34+
security: [{ bearerAuth: [] }],
35+
}),
36+
TEST: undefined, // ensure check undefinable error map item
37+
})
38+
39+
const operation: OpenAPI.OperationObject = {
40+
parameters: [{
41+
name: 'id',
42+
in: 'path',
43+
required: true,
44+
schema: {
45+
type: 'string',
46+
},
47+
}],
48+
}
49+
50+
expect(applyCustomOpenAPIOperation(operation, contract)).toEqual({
51+
...operation,
52+
security: [{ bearerAuth: [] }],
53+
})
54+
})
55+
56+
it('custom at middlewares', () => {
57+
const requiredAuth = os.middleware(({ next }) => next())
58+
const procedure = os
59+
.use(customOpenAPIOperation(requiredAuth, {
60+
security: [{ bearerAuth: [] }],
61+
}))
62+
.handler(() => {})
63+
64+
const operation: OpenAPI.OperationObject = {
65+
parameters: [{
66+
name: 'id',
67+
in: 'path',
68+
required: true,
69+
schema: {
70+
type: 'string',
71+
},
72+
}],
73+
}
74+
75+
expect(applyCustomOpenAPIOperation(operation, procedure)).toEqual({
76+
...operation,
77+
security: [{ bearerAuth: [] }],
78+
})
79+
})
80+
81+
it('callback override', () => {
82+
const requiredAuth = os.middleware(({ next }) => next())
83+
const callback = vi.fn()
84+
const procedure = os
85+
.use(customOpenAPIOperation(requiredAuth, callback))
86+
.handler(() => { })
87+
88+
const operation: OpenAPI.OperationObject = {
89+
parameters: [{
90+
name: 'id',
91+
in: 'path',
92+
required: true,
93+
schema: {
94+
type: 'string',
95+
},
96+
}],
97+
}
98+
99+
callback.mockReturnValue('__mocked__')
100+
101+
expect(applyCustomOpenAPIOperation(operation, procedure)).toEqual('__mocked__')
102+
103+
expect(callback).toBeCalledTimes(1)
104+
expect(callback).toBeCalledWith(operation, procedure)
105+
})
106+
})

0 commit comments

Comments
 (0)