Skip to content

Commit fd1db03

Browse files
authored
feat(openapi): input/output detailed structure (#66)
* typed * input structure * improve * output structure * docs * openapi builder * openapi generator testing for input and output structure * optional on query will have no effect
1 parent 1cee930 commit fd1db03

14 files changed

+1011
-176
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
title: Input/Output Structure
3+
description: Control how input and output data is structured in your API.
4+
---
5+
6+
## Introduction
7+
8+
Input and output structures define how data is organized when procedures are executed,
9+
affecting how input data is processed and how output data is formatted in the HTTP response.
10+
11+
```ts twoslash
12+
import { oc } from '@orpc/contract'
13+
import { os } from '@orpc/server'
14+
import { z } from 'zod'
15+
16+
const contract = oc.route({
17+
inputStructure: 'detailed',
18+
outputStructure: 'detailed',
19+
})
20+
21+
// or
22+
23+
const procedure = os.route({
24+
path: '/ping/{name}',
25+
method: 'POST',
26+
inputStructure: 'detailed',
27+
outputStructure: 'detailed',
28+
})
29+
.input(z.object({
30+
params: z.object({ name: z.string() }),
31+
query: z.object({ search: z.string() }),
32+
body: z.object({ description: z.string() }).optional(),
33+
headers: z.object({ 'content-type': z.string() }), // please use lowercase
34+
}))
35+
.handler((input, context, meta) => {
36+
return {
37+
body: 'the body',
38+
headers: {
39+
'x-custom-header': 'custom-value',
40+
},
41+
}
42+
})
43+
```
44+
45+
> **Note**: You only need to define the input and output schemas according to your requirements. The example provided above is solely for demonstration purposes.
46+
47+
## Input Structure Options
48+
49+
The `inputStructure` option determines how the input data is structured based on `params`, `query`, `headers`, and `body`.
50+
51+
- **'compact'**: Combines `params` and either `query` or `body` (depending on the HTTP method) into a single object.
52+
53+
- For example, in a GET request, `params` and `query` are combined.
54+
- In a POST request, `params` and `body` are combined.
55+
56+
- **'detailed'**: Keeps each part of the request (`params`, `query`, `headers`, and `body`) as separate fields in the input object.
57+
58+
```ts
59+
const input = {
60+
params: { id: 1 },
61+
query: { search: 'hello' },
62+
headers: { 'Content-Type': 'application/json' },
63+
body: { name: 'John' },
64+
};
65+
```
66+
67+
## Output Structure Options
68+
69+
The `outputStructure` option determines how the response is structured based on the output.
70+
71+
- **'compact'**: Includes only the body data, encoded directly in the response.
72+
73+
- **'detailed'**: Separates the output into `headers` and `body` fields.
74+
75+
```ts
76+
const output = {
77+
headers: { 'x-custom-header': 'value' },
78+
body: { message: 'Hello, world!' },
79+
};
80+
```
81+
82+
## Default Behavior
83+
84+
- `inputStructure` defaults to `'compact'`
85+
- `outputStructure` defaults to `'compact'`

packages/contract/src/procedure.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,59 @@ export interface RouteOptions {
1212
description?: string
1313
deprecated?: boolean
1414
tags?: readonly string[]
15+
16+
/**
17+
* The status code of the response when the procedure is successful.
18+
*
19+
* @default 200
20+
*/
1521
successStatus?: number
22+
23+
/**
24+
* Determines how the input should be structured based on `params`, `query`, `headers`, and `body`.
25+
*
26+
* @option 'compact'
27+
* Combines `params` and either `query` or `body` (depending on the HTTP method) into a single object.
28+
*
29+
* @option 'detailed'
30+
* Keeps each part of the request (`params`, `query`, `headers`, and `body`) as separate fields in the input object.
31+
*
32+
* Example:
33+
* ```ts
34+
* const input = {
35+
* params: { id: 1 },
36+
* query: { search: 'hello' },
37+
* headers: { 'Content-Type': 'application/json' },
38+
* body: { name: 'John' },
39+
* }
40+
* ```
41+
*
42+
* @default 'compact'
43+
*/
44+
inputStructure?: 'compact' | 'detailed'
45+
46+
/**
47+
* Determines how the response should be structured based on the output.
48+
*
49+
* @option 'compact'
50+
* Includes only the body data, encoded directly in the response.
51+
*
52+
* @option 'detailed'
53+
* Separates the output into `headers` and `body` fields.
54+
* - `headers`: Custom headers to merge with the response headers.
55+
* - `body`: The response data.
56+
*
57+
* Example:
58+
* ```ts
59+
* const output = {
60+
* headers: { 'x-custom-header': 'value' },
61+
* body: { message: 'Hello, world!' },
62+
* };
63+
* ```
64+
*
65+
* @default 'compact'
66+
*/
67+
outputStructure?: 'compact' | 'detailed'
1668
}
1769

1870
export interface ContractProcedureDef<TInputSchema extends Schema, TOutputSchema extends Schema> {

packages/openapi/src/fetch/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export * from './bracket-notation'
2-
export * from './input-builder-full'
3-
export * from './input-builder-simple'
2+
export * from './input-structure-compact'
3+
export * from './input-structure-detailed'
44
export * from './openapi-handler'
55
export * from './openapi-handler-server'
66
export * from './openapi-handler-serverless'

packages/openapi/src/fetch/input-builder-simple.ts renamed to packages/openapi/src/fetch/input-structure-compact.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Params } from 'hono/router'
22
import { isPlainObject } from '@orpc/shared'
33

4-
export class InputBuilderSimple {
4+
export class InputStructureCompact {
55
build(params: Params, payload: unknown): unknown {
66
if (Object.keys(params).length === 0) {
77
return payload
@@ -18,4 +18,4 @@ export class InputBuilderSimple {
1818
}
1919
}
2020

21-
export type PublicInputBuilderSimple = Pick<InputBuilderSimple, keyof InputBuilderSimple>
21+
export type PublicInputStructureCompact = Pick<InputStructureCompact, keyof InputStructureCompact>

packages/openapi/src/fetch/input-builder-full.ts renamed to packages/openapi/src/fetch/input-structure-detailed.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Params } from 'hono/router'
22

3-
export class InputBuilderFull {
3+
export class InputStructureDetailed {
44
build(params: Params, query: unknown, headers: unknown, body: unknown): { params: Params, query: unknown, headers: unknown, body: unknown } {
55
return {
66
params,
@@ -11,4 +11,4 @@ export class InputBuilderFull {
1111
}
1212
}
1313

14-
export type PublicInputBuilderFull = Pick<InputBuilderFull, keyof InputBuilderFull>
14+
export type PublicInputStructureDetailed = Pick<InputStructureDetailed, keyof InputStructureDetailed>

0 commit comments

Comments
 (0)