diff --git a/tests/__snapshots__/vue-query/issue-1026/endpoints.ts b/tests/__snapshots__/vue-query/issue-1026/endpoints.ts new file mode 100644 index 0000000000..d0cec97771 --- /dev/null +++ b/tests/__snapshots__/vue-query/issue-1026/endpoints.ts @@ -0,0 +1,117 @@ +/** + * Generated by orval v8.11.0 🍺 + * Do not edit manually. + * Issue 1026 - Vue Query header parameters + * OpenAPI spec version: 1.0.0 + */ +import { useQuery } from '@tanstack/vue-query'; +import type { + DataTag, + QueryClient, + QueryFunction, + QueryKey, + UseQueryOptions, + UseQueryReturnType, +} from '@tanstack/vue-query'; + +import axios from 'axios'; +import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; + +import { unref } from 'vue'; +import type { MaybeRef } from 'vue'; + +import type { GetSomeEndpointHeaders, SomeEndpointResult } from './model'; + +/** + * @summary Get something with header parameters + */ +export const getSomeEndpoint = ( + headers?: MaybeRef, + options?: AxiosRequestConfig, +): Promise> => { + headers = unref(headers); + + return axios.get(`/api/v1/someEndPoint`, { + ...options, + headers: { ...headers, ...options?.headers }, + }); +}; + +export const getGetSomeEndpointQueryKey = () => { + return ['api', 'v1', 'someEndPoint'] as const; +}; + +export const getGetSomeEndpointQueryOptions = < + TData = Awaited>, + TError = AxiosError, +>( + headers?: MaybeRef, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + axios?: AxiosRequestConfig; + }, +) => { + const { query: queryOptions, axios: axiosOptions } = options ?? {}; + + const queryKey = getGetSomeEndpointQueryKey(); + + const queryFn: QueryFunction>> = ({ + signal, + }) => getSomeEndpoint(headers, { signal, ...axiosOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + >; +}; + +export type GetSomeEndpointQueryResult = NonNullable< + Awaited> +>; +export type GetSomeEndpointQueryError = AxiosError; + +/** + * @summary Get something with header parameters + */ + +export function useGetSomeEndpoint< + TData = Awaited>, + TError = AxiosError, +>( + headers?: MaybeRef, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + axios?: AxiosRequestConfig; + }, + queryClient?: QueryClient, +): UseQueryReturnType & { + queryKey: DataTag; +} { + const queryOptions = getGetSomeEndpointQueryOptions(headers, options); + + const query = useQuery(queryOptions, queryClient) as UseQueryReturnType< + TData, + TError + > & { queryKey: DataTag }; + + query.queryKey = unref(queryOptions).queryKey as DataTag< + QueryKey, + TData, + TError + >; + + return query; +} diff --git a/tests/__snapshots__/vue-query/issue-1026/model/getSomeEndpointHeaders.ts b/tests/__snapshots__/vue-query/issue-1026/model/getSomeEndpointHeaders.ts new file mode 100644 index 0000000000..563e2ad7b8 --- /dev/null +++ b/tests/__snapshots__/vue-query/issue-1026/model/getSomeEndpointHeaders.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.11.0 🍺 + * Do not edit manually. + * Issue 1026 - Vue Query header parameters + * OpenAPI spec version: 1.0.0 + */ + +export type GetSomeEndpointHeaders = { + 'Language-Id'?: number; + 'Country-Id'?: number; + TimeZone?: string; +}; diff --git a/tests/__snapshots__/vue-query/issue-1026/model/index.ts b/tests/__snapshots__/vue-query/issue-1026/model/index.ts new file mode 100644 index 0000000000..c55e4768fa --- /dev/null +++ b/tests/__snapshots__/vue-query/issue-1026/model/index.ts @@ -0,0 +1,9 @@ +/** + * Generated by orval v8.11.0 🍺 + * Do not edit manually. + * Issue 1026 - Vue Query header parameters + * OpenAPI spec version: 1.0.0 + */ + +export * from './getSomeEndpointHeaders'; +export * from './someEndpointResult'; diff --git a/tests/__snapshots__/vue-query/issue-1026/model/someEndpointResult.ts b/tests/__snapshots__/vue-query/issue-1026/model/someEndpointResult.ts new file mode 100644 index 0000000000..eac3e30d49 --- /dev/null +++ b/tests/__snapshots__/vue-query/issue-1026/model/someEndpointResult.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v8.11.0 🍺 + * Do not edit manually. + * Issue 1026 - Vue Query header parameters + * OpenAPI spec version: 1.0.0 + */ + +export interface SomeEndpointResult { + value?: string; +} diff --git a/tests/api-generation.spec.ts b/tests/api-generation.spec.ts index 661013585c..4c63c2a592 100644 --- a/tests/api-generation.spec.ts +++ b/tests/api-generation.spec.ts @@ -176,3 +176,36 @@ test('default issue-873 does not duplicate multi-tag operations across tag files ).not.toContain(marker); } }); + +test('vue-query issue-1026 keeps header params out of the query key getter', async () => { + // Regression for #1026: with `headers: true` the Vue Query key getter used to + // emit `headers = unref(headers);` even though `headers` is not one of its + // parameters, throwing `ReferenceError: headers is not defined` at runtime. + // The getter must never unref params (that would also break key reactivity); + // `headers` is only unref'd inside the HTTP function where it is a parameter. + // Keep this focused assertion alongside the snapshot so #1026 fails with a + // targeted message instead of a full-file snapshot diff. + const content = await readFile( + generated('vue-query', 'issue-1026', 'endpoints.ts'), + 'utf8', + ); + + // Slice out the `getGetSomeEndpointQueryKey` declaration body. + const marker = 'export const getGetSomeEndpointQueryKey = ('; + const start = content.indexOf(marker); + expect(start, `${marker} should be generated`).toBeGreaterThan(-1); + const end = content.indexOf('as const', start); + expect(end, `${marker} body should be terminated`).toBeGreaterThan(start); + const queryKeyFn = content.slice(start, end); + + // The getter must not reference `headers` as an identifier: that was the + // #1026 bug (`headers = unref(headers);`) and unref-ing a param would also + // break query-key reactivity. A word-boundary regex keeps the intent precise + // rather than matching `headers` as a loose substring. + expect(queryKeyFn).not.toMatch(/\bheaders\b/); + + // Sanity check: the HTTP function still receives and unrefs `headers`, so the + // assertion above is not passing simply because headers support is missing. + expect(content).toContain('headers?: MaybeRef'); + expect(content).toContain('headers = unref(headers);'); +}); diff --git a/tests/configs/vue-query.config.ts b/tests/configs/vue-query.config.ts index 8c4a69b299..b581382f5f 100644 --- a/tests/configs/vue-query.config.ts +++ b/tests/configs/vue-query.config.ts @@ -250,6 +250,21 @@ export default defineConfig({ }, input: { target: '../specifications/petstore.yaml' }, }, + issue1026: { + output: { + target: '../generated/vue-query/issue-1026/endpoints.ts', + schemas: '../generated/vue-query/issue-1026/model', + client: 'vue-query', + httpClient: 'axios', + mode: 'split', + headers: true, + clean: true, + formatter: 'prettier', + }, + input: { + target: '../specifications/issue-1026.yaml', + }, + }, // Unsupported for now, see for context: https://github.com/orval-labs/orval/pull/931#issuecomment-1752355686 // namedParameters: { // output: { diff --git a/tests/specifications/issue-1026.yaml b/tests/specifications/issue-1026.yaml new file mode 100644 index 0000000000..a6ea99d085 --- /dev/null +++ b/tests/specifications/issue-1026.yaml @@ -0,0 +1,41 @@ +openapi: 3.0.0 +info: + title: Issue 1026 - Vue Query header parameters + version: 1.0.0 +paths: + /api/v1/someEndPoint: + get: + operationId: getSomeEndpoint + tags: + - things + summary: Get something with header parameters + parameters: + - name: Language-Id + in: header + style: simple + schema: + type: integer + - name: Country-Id + in: header + style: simple + schema: + type: integer + - name: TimeZone + in: header + style: simple + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SomeEndpointResult' +components: + schemas: + SomeEndpointResult: + type: object + properties: + value: + type: string