Skip to content

Commit

Permalink
refactor: improve OpenAPI internal type names
Browse files Browse the repository at this point in the history
  • Loading branch information
johannschopplich committed Nov 3, 2023
1 parent ab95a90 commit c784aea
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 61 deletions.
10 changes: 5 additions & 5 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,18 +221,18 @@ export const ${getDataComposableName(i)} = (...args) => _useApiData('${i}', ...a
getContents() {
return `
// Generated by ${moduleName}
import type { $Api, $OpenApi, ApiFetchOptions } from '${relativeTo('runtime/composables/$api')}'
import type { UseApiData, UseOpenApiData, UseApiDataOptions } from '${relativeTo('runtime/composables/useApiData')}'
import type { $Api, $OpenAPI, ApiFetchOptions } from '${relativeTo('runtime/composables/$api')}'
import type { UseApiData, UseOpenAPIData, UseApiDataOptions } from '${relativeTo('runtime/composables/useApiData')}'
${schemaEndpointIds.map(i => `
import type { paths as ${pascalCase(i)}Paths } from '#${moduleName}/${i}'
`.trimStart()).join('').trimEnd()}
export type { $Api, $OpenApi, ApiFetchOptions, UseApiData, UseOpenApiData, UseApiDataOptions }
export type { $Api, $OpenAPI, ApiFetchOptions, UseApiData, UseOpenAPIData, UseApiDataOptions }
${endpointKeys.map(i => `
export declare const ${getRawComposableName(i)}: ${schemaEndpointIds.includes(i) ? `$OpenApi<${pascalCase(i)}Paths>` : '$Api'}
export declare const ${getDataComposableName(i)}: ${schemaEndpointIds.includes(i) ? `UseOpenApiData<${pascalCase(i)}Paths>` : 'UseApiData'}
export declare const ${getRawComposableName(i)}: ${schemaEndpointIds.includes(i) ? `$OpenAPI<${pascalCase(i)}Paths>` : '$Api'}
export declare const ${getDataComposableName(i)}: ${schemaEndpointIds.includes(i) ? `UseOpenAPIData<${pascalCase(i)}Paths>` : 'UseApiData'}
`.trimStart()).join('').trimEnd()}
`.trimStart()
},
Expand Down
22 changes: 11 additions & 11 deletions src/runtime/composables/$api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { isFormData } from '../formData'
import type { ModuleOptions } from '../../module'
import { CACHE_KEY_PREFIX } from '../constants'
import type { EndpointFetchOptions } from '../types'
import type { AllPaths, GETPaths, GETPlainPaths, HttpMethod, IgnoreCase, OpenApiRequestOptions, OpenApiResponse, PathItemObject } from '../openapi'
import type { AllPaths, ApiResponse, CaseVariants, GetPaths, GetPlainPaths, HttpMethod, RequestOptions, SchemaPath } from '../openapi'
import { useNuxtApp, useRequestHeaders, useRuntimeConfig } from '#imports'

export interface BaseApiFetchOptions {
Expand Down Expand Up @@ -41,19 +41,19 @@ export type $Api = <T = any>(
opts?: ApiFetchOptions & BaseApiFetchOptions,
) => Promise<T>

export interface $OpenApi<Paths extends Record<string, PathItemObject>> {
<P extends GETPlainPaths<Paths>>(
export interface $OpenAPI<Paths extends Record<string, SchemaPath>> {
<P extends GetPlainPaths<Paths>>(
path: P,
opts?: BaseApiFetchOptions & Omit<OpenApiRequestOptions<Paths[`/${P}`]>, 'method'>
): Promise<OpenApiResponse<Paths[`/${P}`]['get']>>
<P extends GETPaths<Paths>>(
opts?: BaseApiFetchOptions & Omit<RequestOptions<Paths[`/${P}`]>, 'method'>
): Promise<ApiResponse<Paths[`/${P}`]['get']>>
<P extends GetPaths<Paths>>(
path: P,
opts: BaseApiFetchOptions & Omit<OpenApiRequestOptions<Paths[`/${P}`]>, 'method'>
): Promise<OpenApiResponse<Paths[`/${P}`]['get']>>
<P extends AllPaths<Paths>, M extends IgnoreCase<keyof Paths[`/${P}`] & HttpMethod>>(
opts: BaseApiFetchOptions & Omit<RequestOptions<Paths[`/${P}`]>, 'method'>
): Promise<ApiResponse<Paths[`/${P}`]['get']>>
<P extends AllPaths<Paths>, M extends CaseVariants<keyof Paths[`/${P}`] & HttpMethod>>(
path: P,
opts?: BaseApiFetchOptions & OpenApiRequestOptions<Paths[`/${P}`], M> & { method: M }
): Promise<OpenApiResponse<Paths[`/${P}`][Lowercase<M>]>>
opts?: BaseApiFetchOptions & RequestOptions<Paths[`/${P}`], M> & { method: M }
): Promise<ApiResponse<Paths[`/${P}`][Lowercase<M>]>>
}

export function _$api<T = any>(
Expand Down
32 changes: 16 additions & 16 deletions src/runtime/composables/useApiData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { CACHE_KEY_PREFIX } from '../constants'
import { headersToObject, resolvePathParams, serializeMaybeEncodedBody, toValue } from '../utils'
import { isFormData } from '../formData'
import type { EndpointFetchOptions, MaybeRef, MaybeRefOrGetter } from '../types'
import type { AllPaths, GETPaths, GETPlainPaths, HttpMethod, IgnoreCase, OpenApiError, OpenApiRequestOptions, OpenApiResponse, PathItemObject } from '../openapi'
import type { AllPaths, ApiError, ApiResponse, CaseVariants, GetPaths, GetPlainPaths, HttpMethod, RequestOptions, SchemaPath } from '../openapi'
import { useAsyncData, useRequestHeaders, useRuntimeConfig } from '#imports'

type ComputedOptions<T extends Record<string, any>> = {
Expand Down Expand Up @@ -66,31 +66,31 @@ export type UseApiDataOptions<T> = Pick<
body?: MaybeRef<string | Record<string, any> | FormData | null>
} & BaseUseApiDataOptions<T>

export type UseOpenApiDataOptions<
P extends PathItemObject,
M extends IgnoreCase<keyof P & HttpMethod> = IgnoreCase<keyof P & 'get'>,
ResT = OpenApiResponse<P[Lowercase<M>]>,
export type UseOpenAPIDataOptions<
P extends SchemaPath,
M extends CaseVariants<keyof P & HttpMethod> = CaseVariants<keyof P & 'get'>,
ResT = ApiResponse<P[Lowercase<M>]>,
DataT = ResT,
> = BaseUseApiDataOptions<ResT, DataT> & ComputedOptions<OpenApiRequestOptions<P, M>>
> = BaseUseApiDataOptions<ResT, DataT> & ComputedOptions<RequestOptions<P, M>>

export type UseApiData = <T = any>(
path: MaybeRefOrGetter<string>,
opts?: UseApiDataOptions<T>,
) => AsyncData<T | undefined, FetchError>

export interface UseOpenApiData<Paths extends Record<string, PathItemObject>> {
<P extends GETPlainPaths<Paths>, ResT = OpenApiResponse<Paths[`/${P}`]['get']>, DataT = ResT>(
export interface UseOpenAPIData<Paths extends Record<string, SchemaPath>> {
<P extends GetPlainPaths<Paths>, ResT = ApiResponse<Paths[`/${P}`]['get']>, DataT = ResT>(
path: MaybeRefOrGetter<P>,
opts?: Omit<UseOpenApiDataOptions<Paths[`/${P}`], IgnoreCase<keyof Paths[`/${P}`] & HttpMethod>, ResT, DataT>, 'method'>,
): AsyncData<DataT, FetchError<OpenApiError<Paths[`/${P}`]['get']>>>
<P extends GETPaths<Paths>, ResT = OpenApiResponse<Paths[`/${P}`]['get']>, DataT = ResT>(
opts?: Omit<UseOpenAPIDataOptions<Paths[`/${P}`], CaseVariants<keyof Paths[`/${P}`] & HttpMethod>, ResT, DataT>, 'method'>,
): AsyncData<DataT, FetchError<ApiError<Paths[`/${P}`]['get']>>>
<P extends GetPaths<Paths>, ResT = ApiResponse<Paths[`/${P}`]['get']>, DataT = ResT>(
path: MaybeRefOrGetter<P>,
opts: Omit<UseOpenApiDataOptions<Paths[`/${P}`], IgnoreCase<keyof Paths[`/${P}`] & HttpMethod>, ResT, DataT>, 'method'>,
): AsyncData<DataT, FetchError<OpenApiError<Paths[`/${P}`]['get']>>>
<P extends AllPaths<Paths>, M extends IgnoreCase<keyof Paths[`/${P}`] & HttpMethod>, ResT = OpenApiResponse<Paths[`/${P}`][Lowercase<M>]>, DataT = ResT>(
opts: Omit<UseOpenAPIDataOptions<Paths[`/${P}`], CaseVariants<keyof Paths[`/${P}`] & HttpMethod>, ResT, DataT>, 'method'>,
): AsyncData<DataT, FetchError<ApiError<Paths[`/${P}`]['get']>>>
<P extends AllPaths<Paths>, M extends CaseVariants<keyof Paths[`/${P}`] & HttpMethod>, ResT = ApiResponse<Paths[`/${P}`][Lowercase<M>]>, DataT = ResT>(
path: MaybeRefOrGetter<P>,
opts: UseOpenApiDataOptions<Paths[`/${P}`], M, ResT, DataT> & { method: M },
): AsyncData<DataT, FetchError<OpenApiError<Paths[`/${P}`][Lowercase<M>]>>>
opts: UseOpenAPIDataOptions<Paths[`/${P}`], M, ResT, DataT> & { method: M },
): AsyncData<DataT, FetchError<ApiError<Paths[`/${P}`][Lowercase<M>]>>>
}

export function _useApiData<T = any>(
Expand Down
75 changes: 46 additions & 29 deletions src/runtime/openapi.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import type { NitroFetchOptions } from 'nitropack'

export type IgnoreCase<T extends string> = Lowercase<T> | Uppercase<T>
export type RemovePrefix<T extends string, P extends string> = T extends `${P}${infer S}` ? S : never
// --------------------------
// OpenAPI Schema
// --------------------------
export type SchemaPath = { [M in HttpMethod]?: any } & { parameters?: any }

export type PathItemObject = { [M in HttpMethod]?: any } & { parameters?: any }
// --------------------------
// Requests
// --------------------------
type RequestMethod<M extends CaseVariants<HttpMethod>> =
'get' extends Lowercase<M> ? { method?: M } : { method: M }

type ParameterFromSchema<T, N extends string, K extends string = N> = T extends {
parameters: { [_ in N]?: any }
}
? { [_ in keyof Pick<T['parameters'], N> as K]: T['parameters'][N] }
: unknown

export type PathParameters<T> = ParameterFromSchema<T, 'path', 'pathParams'>
export type QueryParameters<T> = ParameterFromSchema<T, 'query'>
export type HeaderParameters<T> = ParameterFromSchema<T, 'header', 'headers'>

// General purpose types
export type RequestBody<T> = T extends { requestBody?: { content: infer Body } }
?
| (Body extends { 'application/octet-stream': any }
Expand All @@ -25,28 +40,18 @@ export type RequestBody<T> = T extends { requestBody?: { content: infer Body } }
: Record<string, any>)
: unknown

export type Method<M extends IgnoreCase<HttpMethod>> =
'get' extends Lowercase<M> ? { method?: M } : { method: M }

export type Param<T, N extends string, K extends string = N> = T extends {
parameters: { [_ in N]?: any }
}
? { [_ in keyof Pick<T['parameters'], N> as K]: T['parameters'][N] }
: unknown

export type PathParameters<T> = Param<T, 'path', 'pathParams'>
export type QueryParameters<T> = Param<T, 'query'>
export type HeaderParameters<T> = Param<T, 'header', 'headers'>

export type OpenApiRequestOptions<
P extends PathItemObject,
M extends IgnoreCase<keyof P & HttpMethod> = IgnoreCase<keyof P & 'get'>,
export type RequestOptions<
P extends SchemaPath,
M extends CaseVariants<keyof P & HttpMethod> = CaseVariants<keyof P & 'get'>,
> = Omit<
NitroFetchOptions<any, Lowercase<M>>,
'params' | 'query' | 'headers' | 'method' | 'body' | 'cache'
> & RequestBody<P[Lowercase<M>]> & PathParameters<P[Lowercase<M>]> & QueryParameters<P[Lowercase<M>]> & Method<M>
> & RequestBody<P[Lowercase<M>]> & PathParameters<P[Lowercase<M>]> & QueryParameters<P[Lowercase<M>]> & RequestMethod<M>

type MediaTypes<T, Status extends keyof any> = {
// --------------------------
// Responses
// --------------------------
type ResponseContentTypes<T, Status extends keyof any> = {
[S in Status]: T extends {
responses: {
[_ in S]: {
Expand All @@ -60,28 +65,40 @@ type MediaTypes<T, Status extends keyof any> = {
: never;
}[Status]

// Fetch types
export type OpenApiResponse<T> = MediaTypes<T, HttpSuccessStatus>
export type OpenApiError<T> = MediaTypes<T, HttpErrorStatus>
export type ApiResponse<T> = ResponseContentTypes<T, HttpSuccessStatus>
export type ApiError<T> = ResponseContentTypes<T, HttpErrorStatus>

// Path types
// --------------------------
// Paths
// --------------------------
export type AllPaths<Paths> = RemovePrefix<keyof Paths & string, '/'>

/** All endpoints that don't require a `method` property */
export type GETPaths<Paths> = {
export type GetPaths<Paths> = {
[P in keyof Paths]: Paths[P] extends { get: any } ? RemovePrefix<P & string, '/'> : never;
}[keyof Paths]

/** All endpoints that don't require additional options */
export type GETPlainPaths<Paths> = {
export type GetPlainPaths<Paths> = {
[P in keyof Paths]: Paths[P] extends { get: infer O }
? O extends { parameters: { query: any } | { header: any } | { path: any } }
? never
: RemovePrefix<P & string, '/'>
: never;
}[keyof Paths]

// HTTP status codes and methods
// --------------------------
// Utilities
// --------------------------
export type CaseVariants<T extends string> = Lowercase<T> | Uppercase<T>
export type RemovePrefix<
T extends string,
P extends string,
> = T extends `${P}${infer S}` ? S : never

// --------------------------
// HTTP status codes
// --------------------------
export type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace'
export type HttpSuccessStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | '2XX' | 'default'
export type HttpErrorStatus = 500 | '5XX' | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 429 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | '4XX'

0 comments on commit c784aea

Please sign in to comment.