Skip to content

Commit

Permalink
Feat/add property include return type http status (#1562)
Browse files Browse the repository at this point in the history
* feat(fetch): add `output.override.fetch.includeHttpStatusReturnType`

* feat(fetch): switch response type by `includeHttpStatusReturnType`

* chore: add test for using `override.fetch.includeHttpStatusReturnType`

* chore: update `swr` sample app to use `includeHttpStatusReturnType`

* docs: add `includeHttpStatusReturnType` to guide
  • Loading branch information
soartec-lab authored Aug 3, 2024
1 parent 44ac828 commit 02b22cc
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 35 deletions.
48 changes: 48 additions & 0 deletions docs/src/pages/guides/fetch-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,54 @@ export const useListPets = <TError = Promise<Pets | Error>>(
};
```

#### return original defined return type

When using `fetch` as an `httpClient`, by default the `fetch` response type includes http status.
If use `swr` or queries, i will be accessing things like `data.data`, which will be noisy so if you want to return a defined return type instead of an automatically generated return type, set `override.fetch.includeHttpStatusReturnType` value to `false`.

```js
module.exports = {
petstore: {
output: {
...
override: {
fetch: {
includeHttpStatusReturnType: false,
},
},
},
...
},
};
```

```diff
/**
* @summary List all pets
*/
- export type listPetsResponse = {
- data: Pets;
- status: number;
- };

export const listPets = async (
params?: ListPetsParams,
options?: RequestInit,
- ): Promise<listPetsResponse> => {
+ ): Promise<Pet> => {
const res = await fetch(getListPetsUrl(params), {
...options,
method: 'GET',
});
const data = await res.json();

- return { status: res.status, data };
+ return data as Pet;
};
```

#### custom fetch client

Also, if you want to use to the custom fetch client, you can set in the override option.

```js
Expand Down
30 changes: 30 additions & 0 deletions docs/src/pages/reference/configuration/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,36 @@ module.exports = {
};
```

#### fetch

Type: `Object`.

Give options to the generated `fetch` client.

```js
module.exports = {
petstore: {
output: {
...
override: {
fetch: {
includeHttpStatusReturnType: false,
},
},
},
...
},
};
```

##### includeHttpStatusReturnType

Type: `Boolean`.
Default: `true`

When using `fetch` for `client` or `httpClient`, the `fetch` response type includes http status for easier processing by the application.
If you want to return a defined return type instead of an automatically generated return type, set this value to `false`.

#### query

Type: `Object`.
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export type NormalizedOverrideOutput = {
angular: Required<AngularOptions>;
swr: SwrOptions;
zod: NormalizedZodOptions;
fetch: FetchOptions;
operationName?: (
operation: OperationObject,
route: string,
Expand Down Expand Up @@ -141,6 +142,7 @@ export type NormalizedOperationOptions = {
route: string,
verb: Verbs,
) => string;
fetch?: FetchOptions;
formData?: boolean | NormalizedMutator;
formUrlEncoded?: boolean | NormalizedMutator;
paramsSerializer?: NormalizedMutator;
Expand Down Expand Up @@ -356,6 +358,7 @@ export type OverrideOutput = {
route: string,
verb: Verbs,
) => string;
fetch?: FetchOptions;
requestOptions?: Record<string, any> | boolean;
useDates?: boolean;
useTypeOverInterfaces?: boolean;
Expand Down Expand Up @@ -496,6 +499,10 @@ export type SwrOptions = {
swrInfiniteOptions?: any;
};

export type FetchOptions = {
includeHttpStatusReturnType: boolean;
};

export type InputTransformerFn = (spec: OpenAPIObject) => OpenAPIObject;

type InputTransformer = string | InputTransformerFn;
Expand All @@ -520,6 +527,7 @@ export type OperationOptions = {
route: string,
verb: Verbs,
) => string;
fetch?: FetchOptions;
formData?: boolean | Mutator;
formUrlEncoded?: boolean | Mutator;
paramsSerializer?: Mutator;
Expand Down
27 changes: 20 additions & 7 deletions packages/fetch/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,17 @@ ${
}
}\n`;

const responseTypeName = fetchResponseTypeName(operationName);
const responseTypeImplementation = `export type ${responseTypeName} = {
const responseTypeName = fetchResponseTypeName(
override.fetch.includeHttpStatusReturnType,
response.definition.success,
operationName,
);
const responseTypeImplementation = override.fetch.includeHttpStatusReturnType
? `export type ${responseTypeName} = {
data: ${response.definition.success || 'unknown'};
status: number;
}`;
}\n\n`
: '';

const getUrlFnProperties = props
.filter(
Expand Down Expand Up @@ -122,7 +128,7 @@ ${
)
const data = await res.json()
return { status: res.status, data }
${override.fetch.includeHttpStatusReturnType ? 'return { status: res.status, data }' : `return data as ${responseTypeName}`}
`;
const customFetchResponseImplementation = `return ${mutator?.name}<${retrunType}>(${fetchFnOptions});`;

Expand All @@ -144,15 +150,22 @@ ${
`;

const implementation =
`${responseTypeImplementation}\n\n` +
`${responseTypeImplementation}` +
`${getUrlFnImplementation}\n` +
`${fetchImplementation}\n`;

return implementation;
};

export const fetchResponseTypeName = (operationName: string) =>
`${operationName}Response`;
export const fetchResponseTypeName = (
includeHttpStatusReturnType: boolean,
definitionSuccessResponse: string,
operationName: string,
) => {
return includeHttpStatusReturnType
? `${operationName}Response`
: definitionSuccessResponse;
};

export const generateClient: ClientBuilder = (verbOptions, options) => {
const imports = generateVerbImports(verbOptions);
Expand Down
5 changes: 5 additions & 0 deletions packages/orval/src/utils/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,11 @@ export const normalizeOptions = async (
angular: {
provideIn: outputOptions.override?.angular?.provideIn ?? 'root',
},
fetch: {
includeHttpStatusReturnType:
outputOptions.override?.fetch?.includeHttpStatusReturnType ?? true,
...(outputOptions.override?.fetch ?? {}),
},
useDates: outputOptions.override?.useDates || false,
useDeprecatedOperations:
outputOptions.override?.useDeprecatedOperations ?? true,
Expand Down
7 changes: 6 additions & 1 deletion packages/swr/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,16 @@ export const getSwrMutationFetcherOptionType = (
export const getSwrMutationFetcherType = (
response: GetterResponse,
httpClient: OutputHttpClient,
includeHttpStatusReturnType: boolean,
operationName: string,
mutator?: GeneratorMutator,
) => {
if (httpClient === OutputHttpClient.FETCH) {
const responseType = fetchResponseTypeName(operationName);
const responseType = fetchResponseTypeName(
includeHttpStatusReturnType,
response.definition.success,
operationName,
);

return `Promise<${responseType}>`;
} else if (mutator) {
Expand Down
1 change: 1 addition & 0 deletions packages/swr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ export const ${swrKeyFnName} = (${queryKeyProps}) => [\`${route}\`${
const swrMutationFetcherType = getSwrMutationFetcherType(
response,
httpClient,
override.fetch.includeHttpStatusReturnType,
operationName,
mutator,
);
Expand Down
5 changes: 5 additions & 0 deletions samples/react-app-with-swr/fetch-client/orval.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export default defineConfig({
client: 'swr',
httpClient: 'fetch',
mock: true,
override: {
fetch: {
includeHttpStatusReturnType: false,
},
},
},
input: {
target: './petstore.yaml',
Expand Down
4 changes: 2 additions & 2 deletions samples/react-app-with-swr/fetch-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ function App() {
<>
<h1>SWR with fetch client</h1>
<div>
{data?.data &&
data.data.map((pet: Pet, index: number) => (
{data &&
data.map((pet: Pet, index: number) => (
<div key={index}>
<p>id: {pet.id}</p>
<p>name: {pet.name}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ import type {
/**
* @summary List all pets
*/
export type listPetsResponse = {
data: Pets;
status: number;
};

export const getListPetsUrl = (params?: ListPetsParams) => {
const normalizedParams = new URLSearchParams();

Expand All @@ -43,14 +38,14 @@ export const getListPetsUrl = (params?: ListPetsParams) => {
export const listPets = async (
params?: ListPetsParams,
options?: RequestInit,
): Promise<listPetsResponse> => {
): Promise<Pets> => {
const res = await fetch(getListPetsUrl(params), {
...options,
method: 'GET',
});
const data = await res.json();

return { status: res.status, data };
return data as Pets;
};

export const getListPetsKey = (params?: ListPetsParams) =>
Expand Down Expand Up @@ -96,19 +91,14 @@ export const useListPets = <TError = Promise<Error>>(
/**
* @summary Create a pet
*/
export type createPetsResponse = {
data: Pet;
status: number;
};

export const getCreatePetsUrl = () => {
return `http://localhost:8000/pets`;
};

export const createPets = async (
createPetsBody: CreatePetsBody,
options?: RequestInit,
): Promise<createPetsResponse> => {
): Promise<Pet> => {
const res = await fetch(getCreatePetsUrl(), {
...options,
method: 'POST',
Expand All @@ -117,14 +107,11 @@ export const createPets = async (
});
const data = await res.json();

return { status: res.status, data };
return data as Pet;
};

export const getCreatePetsMutationFetcher = (options?: RequestInit) => {
return (
_: Key,
{ arg }: { arg: CreatePetsBody },
): Promise<createPetsResponse> => {
return (_: Key, { arg }: { arg: CreatePetsBody }): Promise<Pet> => {
return createPets(arg, options);
};
};
Expand Down Expand Up @@ -165,26 +152,21 @@ export const useCreatePets = <TError = Promise<Error>>(options?: {
/**
* @summary Info for a specific pet
*/
export type showPetByIdResponse = {
data: Pet;
status: number;
};

export const getShowPetByIdUrl = (petId: string) => {
return `http://localhost:8000/pets/${petId}`;
};

export const showPetById = async (
petId: string,
options?: RequestInit,
): Promise<showPetByIdResponse> => {
): Promise<Pet> => {
const res = await fetch(getShowPetByIdUrl(petId), {
...options,
method: 'GET',
});
const data = await res.json();

return { status: res.status, data };
return data as Pet;
};

export const getShowPetByIdKey = (petId: string) =>
Expand Down
16 changes: 16 additions & 0 deletions tests/configs/fetch.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ export default defineConfig({
target: '../specifications/petstore.yaml',
},
},
includeHttpStatusReturnType: {
output: {
target: '../generated/fetch/include-http-status-return-type/endpoints.ts',
schemas: '../generated/fetch/include-http-status-return-type/model',
mock: true,
client: 'fetch',
override: {
fetch: {
includeHttpStatusReturnType: false,
},
},
},
input: {
target: '../specifications/petstore.yaml',
},
},
namedParameters: {
output: {
target: '../generated/fetch/named-parameters/endpoints.ts',
Expand Down
19 changes: 19 additions & 0 deletions tests/configs/react-query.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ export default defineConfig({
target: '../specifications/petstore.yaml',
},
},
httpClientFetchWithIncludeHttpStatusReturnType: {
output: {
target:
'../generated/react-query/http-client-fetch-with-include-http-status-return-type/endpoints.ts',
schemas:
'../generated/react-query/http-client-fetch-with-include-http-status-return-type/model',
mode: 'tags-split',
client: 'react-query',
httpClient: 'fetch',
override: {
fetch: {
includeHttpStatusReturnType: false,
},
},
},
input: {
target: '../specifications/petstore.yaml',
},
},
mutator: {
output: {
target: '../generated/react-query/mutator/endpoints.ts',
Expand Down
Loading

0 comments on commit 02b22cc

Please sign in to comment.