diff --git a/.gitignore b/.gitignore index 1eedd2e..7a3cddd 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ yarn-error.log* dist .eslintcache /.idea/ + +lib/tests/test-generation/*-output.ts +lib/tests/test-generation/*-output diff --git a/lib/src/template-context.ts b/lib/src/template-context.ts index 48a61ea..59a6399 100644 --- a/lib/src/template-context.ts +++ b/lib/src/template-context.ts @@ -53,7 +53,8 @@ export const getZodClientTemplateContext = ( data.schemas[normalizeString(name)] = wrapWithLazyIfNeeded(name); } - for (const ref in depsGraphs.deepDependencyGraph) { + for (const [name] of Object.entries(docSchemas)) { + const ref = asComponentSchema(name); const isCircular = ref && depsGraphs.deepDependencyGraph[ref]?.has(ref); const ctx: TsConversionContext = { nodeByRef: {}, resolver: result.resolver, visitedsRefs: {} }; diff --git a/lib/tests/allOf-infer-required-only-item.test.ts b/lib/tests/allOf-infer-required-only-item.test.ts index af56626..5aec6ae 100644 --- a/lib/tests/allOf-infer-required-only-item.test.ts +++ b/lib/tests/allOf-infer-required-only-item.test.ts @@ -73,15 +73,15 @@ test("allOf-infer-required-only-item", async () => { "import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core"; import { z } from "zod"; + type user = Partial<{ + name: string; + email: string; + }>; type userResponse = Partial<{ user: user & { name: string; }; }>; - type user = Partial<{ - name: string; - email: string; - }>; const user: z.ZodType = z .object({ name: z.string(), email: z.string() }) diff --git a/lib/tests/api-doc.test.ts b/lib/tests/api-doc.test.ts new file mode 100644 index 0000000..1f2404a --- /dev/null +++ b/lib/tests/api-doc.test.ts @@ -0,0 +1,69 @@ +import SwaggerParser from "@apidevtools/swagger-parser"; +import type { OpenAPIObject } from "openapi3-ts"; +import { generateZodClientFromOpenAPI } from "../src"; +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve } from "path"; +import { expect, test } from "vitest"; + +test("api-doc.yaml generates expected TypeScript client", async () => { + const openApiDoc = (await SwaggerParser.parse(resolve(__dirname, "schemas/api-doc.yaml"))) as OpenAPIObject; + + const result = await generateZodClientFromOpenAPI({ + openApiDoc, + disableWriteToFile: true, + options: { + apiClientName: "api", + shouldExportAllTypes: true, + }, + }); + + const expectedContent = readFileSync(resolve(__dirname, "test-generation/api-doc-expected.ts"), "utf8"); + + writeFileSync(resolve(__dirname, "test-generation/api-doc-output.ts"), result, "utf8"); + + expect(result).toBe(expectedContent); +}); + +test("api-doc.yaml with groupStrategy tag-file and shouldExportAllTypes", async () => { + const openApiDoc = (await SwaggerParser.parse(resolve(__dirname, "schemas/api-doc.yaml"))) as OpenAPIObject; + + const result = await generateZodClientFromOpenAPI({ + openApiDoc, + disableWriteToFile: true, + options: { + groupStrategy: "tag-file", + shouldExportAllTypes: true, + }, + }); + + // Write actual output files to folder + const outputDir = resolve(__dirname, "test-generation/api-doc-grouped-output"); + const expectedDir = resolve(__dirname, "test-generation/api-doc-grouped-expected"); + + const fs = await import("@liuli-util/fs-extra"); + await fs.ensureDir(outputDir); + await fs.ensureDir(expectedDir); + + // Write all generated files to output directory + for (const [fileName, content] of Object.entries(result)) { + const filePath = fileName.startsWith("__") ? `${fileName.slice(2)}.ts` : `${fileName}.ts`; + writeFileSync(resolve(outputDir, filePath), content, "utf8"); + } + + // Compare each file with expected + for (const [fileName, content] of Object.entries(result)) { + const filePath = fileName.startsWith("__") ? `${fileName.slice(2)}.ts` : `${fileName}.ts`; + const expectedPath = resolve(expectedDir, filePath); + + let expectedContent: string; + try { + expectedContent = readFileSync(expectedPath, "utf8"); + } catch (error) { + // If expected file doesn't exist, create it from actual output + expectedContent = content; + writeFileSync(expectedPath, expectedContent, "utf8"); + } + + expect(content).toBe(expectedContent); + } +}); diff --git a/lib/tests/enum-null.test.ts b/lib/tests/enum-null.test.ts index ba37750..a0dbe45 100644 --- a/lib/tests/enum-null.test.ts +++ b/lib/tests/enum-null.test.ts @@ -111,18 +111,18 @@ test("enum-null", async () => { "import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core"; import { z } from "zod"; - type Compound = Partial<{ - field: Null1 | Null2 | Null3 | Null4 | string; - }>; type Null1 = null; type Null2 = "a" | null; type Null3 = "a" | null; type Null4 = null; + type Compound = Partial<{ + field: Null1 | Null2 | Null3 | Null4 | string; + }>; - const Null1 = z.literal(null); - const Null2 = z.enum(["a", null]); - const Null3 = z.enum(["a", null]); - const Null4 = z.literal(null); + const Null1: z.ZodType = z.literal(null); + const Null2: z.ZodType = z.enum(["a", null]); + const Null3: z.ZodType = z.enum(["a", null]); + const Null4: z.ZodType = z.literal(null); const Compound: z.ZodType = z .object({ field: z.union([Null1, Null2, Null3, Null4, z.string()]) }) .partial() diff --git a/lib/tests/jsdoc.test.ts b/lib/tests/jsdoc.test.ts index ba91812..301efa0 100644 --- a/lib/tests/jsdoc.test.ts +++ b/lib/tests/jsdoc.test.ts @@ -98,6 +98,9 @@ test("jsdoc", async () => { expect(output).toMatchInlineSnapshot(`"import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core"; import { z } from "zod"; +type SimpleObject = Partial<{ + str: string; +}>; type ComplexObject = Partial<{ /** * A string with example tag @@ -156,9 +159,6 @@ type ComplexObject = Partial<{ */ refArray: Array; }>; -type SimpleObject = Partial<{ - str: string; -}>; const SimpleObject: z.ZodType = z .object({ str: z.string() }) diff --git a/lib/tests/schemas/api-doc.yaml b/lib/tests/schemas/api-doc.yaml new file mode 100644 index 0000000..279d8f4 --- /dev/null +++ b/lib/tests/schemas/api-doc.yaml @@ -0,0 +1,886 @@ +openapi: 3.0.3 +info: + title: API Documentation + version: 1.0.0 + description: OpenAPI specification +paths: + /api/auth/login/: + post: + operationId: authLoginCreate + description: Authenticate with username/email and password to obtain access + tokens. Returns user details along with JWT access and refresh tokens with + expiration times. Authentication cookies are set automatically for secure + token storage. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LoginRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LoginRequest' + required: true + security: + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Login' + description: '' + /api/auth/logout/: + post: + operationId: authLogoutCreate + description: Logout user and invalidate authentication tokens. Blacklists JWT + refresh tokens to prevent further use. Clears authentication cookies from + the browser. Requires authentication to ensure only valid sessions can be + logged out. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JWTLogoutRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/JWTLogoutRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/JWTLogoutRequest' + security: + - JWTAuthentication: [] + JWTCookieAuthentication: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/JWTLogout' + description: '' + /api/auth/password/change/: + post: + operationId: authPasswordChangeCreate + description: 'Change the current user''s password. Requires authentication. ' + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordChangeRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PasswordChangeRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PasswordChangeRequest' + required: true + security: + - JWTAuthentication: [] + JWTCookieAuthentication: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordChange' + description: '' + /api/auth/password/reset/: + post: + operationId: authPasswordResetCreate + description: Send password reset instructions to the provided email address. + If the email is registered, a secure reset link will be sent. The link expires + after a limited time for security. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordResetRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PasswordResetRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PasswordResetRequest' + required: true + security: + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordReset' + description: '' + /api/auth/password/reset/confirm/: + post: + operationId: authPasswordResetConfirmCreate + description: Complete the password reset process using the token from the reset + email. Requires the UID and token from the email along with the new password. + The token is single-use and expires for security. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordResetConfirmRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PasswordResetConfirmRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PasswordResetConfirmRequest' + required: true + security: + - JWTAuthentication: [] + JWTCookieAuthentication: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordResetConfirm' + description: '' + /api/auth/registration/: + post: + operationId: authRegistrationCreate + description: Register a new user account. Users must verify their email address + before the account is fully activated. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RegisterRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RegisterRequest' + required: true + security: + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Register' + description: '' + /api/auth/registration/resend-email/: + post: + operationId: authRegistrationResendEmailCreate + description: Send a new email verification message to unverified email addresses. + Only works for email addresses that are registered but not yet verified. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ResendEmailVerificationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ResendEmailVerificationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ResendEmailVerificationRequest' + required: true + security: + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/ResendEmailVerification' + description: '' + /api/auth/registration/verify-email/: + get: + operationId: authRegistrationVerifyEmailRetrieve + description: GET method not allowed for email verification. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + security: + - {} + responses: + '405': + description: Method not allowed + post: + operationId: authRegistrationVerifyEmailCreate + description: Confirm email address using the verification key sent via email. + This activates the user account and allows login access. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VerifyEmailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VerifyEmailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VerifyEmailRequest' + required: true + security: + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VerifyEmail' + description: '' + /api/auth/social/accounts/: + get: + operationId: authSocialAccountsList + description: List all social accounts connected to the current user. Shows account + details including provider, UID, and connection dates. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - auth + security: + - JWTAuthentication: [] + JWTCookieAuthentication: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedSocialAccountList' + description: '' + /api/auth/social/accounts/{id}/: + delete: + operationId: authSocialAccountsDestroy + description: Disconnect a social account from the current user. Removes the + social account connection and prevents future logins via that provider. Requires + authentication and the account must belong to the current user. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this social account. + required: true + tags: + - auth + security: + - JWTAuthentication: [] + JWTCookieAuthentication: [] + responses: + '204': + description: No response body + /api/auth/token/refresh/: + post: + operationId: authTokenRefreshCreate + description: Generate new JWT access tokens using refresh tokens. Refresh tokens + can be provided in request data or extracted automatically from HTTP cookies. + Returns new access tokens with updated expiration times. New tokens are automatically + set in HTTP cookies for secure storage. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CookieTokenRefreshRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CookieTokenRefreshRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CookieTokenRefreshRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CookieTokenRefresh' + description: '' + /api/auth/token/verify/: + post: + operationId: authTokenVerifyCreate + description: |- + Takes a token and indicates if it is valid. This view provides no + information about a token's fitness for a particular use. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TokenVerifyRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TokenVerifyRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TokenVerifyRequest' + required: true + responses: + '200': + description: No response body + /api/auth/user/: + get: + operationId: authUserRetrieve + description: Retrieve the authenticated user's profile information including + username, email, first name, and last name. Password fields are excluded. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + security: + - JWTAuthentication: [] + JWTCookieAuthentication: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetails' + description: '' + put: + operationId: authUserUpdate + description: Update the authenticated user's profile information. Allows modification + of username, first name, and last name. Email field is read-only for security. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetailsRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserDetailsRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserDetailsRequest' + security: + - JWTAuthentication: [] + JWTCookieAuthentication: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetails' + description: '' + patch: + operationId: authUserPartialUpdate + description: Partially update the authenticated user's profile information. + Only provided fields will be updated. Email field is read-only. + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedUserDetailsRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedUserDetailsRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedUserDetailsRequest' + security: + - JWTAuthentication: [] + JWTCookieAuthentication: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetails' + description: '' + /api/status/: + get: + operationId: statusRetrieve + parameters: + - in: header + name: accept-language + schema: + type: string + description: Language code, such as 'vi-VN'. The default value is en + tags: + - status + security: + - {} + responses: + '200': + description: No response body +components: + schemas: + CookieTokenRefresh: + type: object + description: JWT token refresh with cookie and request data support. + properties: + access: + type: string + readOnly: true + accessExpiration: + type: string + format: date-time + readOnly: true + required: + - access + - accessExpiration + CookieTokenRefreshRequest: + type: object + description: JWT token refresh with cookie and request data support. + properties: + refresh: + type: string + writeOnly: true + description: Will override cookie. + JWTLogout: + type: object + description: JWT logout with refresh token blacklisting. + properties: + detail: + type: string + readOnly: true + required: + - detail + JWTLogoutRequest: + type: object + description: JWT logout with refresh token blacklisting. + properties: + refresh: + type: string + writeOnly: true + minLength: 1 + Login: + type: object + description: User authentication with credentials response. + properties: + access: + type: string + readOnly: true + refresh: + type: string + readOnly: true + accessExpiration: + type: string + format: date-time + readOnly: true + refreshExpiration: + type: string + format: date-time + readOnly: true + user: + allOf: + - $ref: '#/components/schemas/UserDetails' + readOnly: true + required: + - access + - accessExpiration + - refresh + - refreshExpiration + - user + LoginRequest: + type: object + description: User authentication with credentials response. + properties: + email: + type: string + format: email + writeOnly: true + minLength: 1 + password: + type: string + writeOnly: true + minLength: 1 + required: + - email + - password + PaginatedSocialAccountList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/SocialAccount' + PasswordChange: + type: object + description: Password change for authenticated users. + properties: + detail: + type: string + readOnly: true + required: + - detail + PasswordChangeRequest: + type: object + description: Password change for authenticated users. + properties: + newPassword1: + type: string + writeOnly: true + minLength: 1 + maxLength: 128 + newPassword2: + type: string + writeOnly: true + minLength: 1 + maxLength: 128 + required: + - newPassword1 + - newPassword2 + PasswordReset: + type: object + description: Password reset request with email verification. + properties: + detail: + type: string + readOnly: true + required: + - detail + PasswordResetConfirm: + type: object + description: Password reset confirmation with new password. + properties: + detail: + type: string + readOnly: true + required: + - detail + PasswordResetConfirmRequest: + type: object + description: Password reset confirmation with new password. + properties: + newPassword1: + type: string + writeOnly: true + minLength: 1 + maxLength: 128 + newPassword2: + type: string + writeOnly: true + minLength: 1 + maxLength: 128 + uid: + type: string + writeOnly: true + minLength: 1 + token: + type: string + writeOnly: true + minLength: 1 + required: + - newPassword1 + - newPassword2 + - token + - uid + PasswordResetRequest: + type: object + description: Password reset request with email verification. + properties: + email: + type: string + format: email + writeOnly: true + minLength: 1 + required: + - email + PatchedUserDetailsRequest: + type: object + description: User profile information and updates. + properties: + firstName: + type: string + maxLength: 150 + lastName: + type: string + maxLength: 150 + Register: + type: object + description: User registration with email verification. + properties: + detail: + type: string + readOnly: true + required: + - detail + RegisterRequest: + type: object + description: User registration with email verification. + properties: + email: + type: string + format: email + writeOnly: true + minLength: 1 + password1: + type: string + writeOnly: true + minLength: 1 + password2: + type: string + writeOnly: true + minLength: 1 + firstName: + type: string + writeOnly: true + lastName: + type: string + writeOnly: true + required: + - email + - password1 + - password2 + ResendEmailVerification: + type: object + description: Request new email verification message. + properties: + detail: + type: string + readOnly: true + required: + - detail + ResendEmailVerificationRequest: + type: object + description: Request new email verification message. + properties: + email: + type: string + format: email + writeOnly: true + minLength: 1 + required: + - email + SocialAccount: + type: object + description: |- + Serializer for SocialAccount instances. + + Provides a REST API representation of django-allauth SocialAccount + objects, including provider information and connection metadata. + properties: + id: + type: integer + readOnly: true + provider: + type: string + maxLength: 200 + uid: + type: string + maxLength: 191 + lastLogin: + type: string + format: date-time + readOnly: true + dateJoined: + type: string + format: date-time + readOnly: true + required: + - dateJoined + - id + - lastLogin + - provider + - uid + TokenVerifyRequest: + type: object + properties: + token: + type: string + writeOnly: true + minLength: 1 + required: + - token + UserDetails: + type: object + description: User profile information and updates. + properties: + pk: + type: integer + readOnly: true + title: ID + email: + type: string + format: email + readOnly: true + title: Email address + firstName: + type: string + maxLength: 150 + lastName: + type: string + maxLength: 150 + required: + - email + - pk + UserDetailsRequest: + type: object + description: User profile information and updates. + properties: + firstName: + type: string + maxLength: 150 + lastName: + type: string + maxLength: 150 + VerifyEmail: + type: object + description: Email address verification with confirmation key. + properties: + detail: + type: string + readOnly: true + required: + - detail + VerifyEmailRequest: + type: object + description: Email address verification with confirmation key. + properties: + key: + type: string + writeOnly: true + minLength: 1 + required: + - key + securitySchemes: + JWTAuthentication: + type: http + scheme: bearer + bearerFormat: JWT + JWTCookieAuthentication: + type: apiKey + in: cookie + name: auth-jwt diff --git a/lib/tests/test-generation/api-doc-expected.ts b/lib/tests/test-generation/api-doc-expected.ts new file mode 100644 index 0000000..019786f --- /dev/null +++ b/lib/tests/test-generation/api-doc-expected.ts @@ -0,0 +1,575 @@ +import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core"; +import { z } from "zod"; + +type CookieTokenRefresh = { + access: string; + accessExpiration: string; +}; +type CookieTokenRefreshRequest = Partial<{ + refresh: string; +}>; +type JWTLogout = { + detail: string; +}; +type JWTLogoutRequest = Partial<{ + refresh: string; +}>; +type Login = { + access: string; + refresh: string; + accessExpiration: string; + refreshExpiration: string; + user: UserDetails; +}; +type UserDetails = { + pk: number; + email: string; + firstName?: string | undefined; + lastName?: string | undefined; +}; +type LoginRequest = { + email: string; + password: string; +}; +type PaginatedSocialAccountList = { + count: number; + next?: (string | null) | undefined; + previous?: (string | null) | undefined; + results: Array; +}; +type SocialAccount = { + id: number; + provider: string; + uid: string; + lastLogin: string; + dateJoined: string; +}; +type PasswordChange = { + detail: string; +}; +type PasswordChangeRequest = { + newPassword1: string; + newPassword2: string; +}; +type PasswordReset = { + detail: string; +}; +type PasswordResetConfirm = { + detail: string; +}; +type PasswordResetConfirmRequest = { + newPassword1: string; + newPassword2: string; + uid: string; + token: string; +}; +type PasswordResetRequest = { + email: string; +}; +type PatchedUserDetailsRequest = Partial<{ + firstName: string; + lastName: string; +}>; +type Register = { + detail: string; +}; +type RegisterRequest = { + email: string; + password1: string; + password2: string; + firstName?: string | undefined; + lastName?: string | undefined; +}; +type ResendEmailVerification = { + detail: string; +}; +type ResendEmailVerificationRequest = { + email: string; +}; +type TokenVerifyRequest = { + token: string; +}; +type UserDetailsRequest = Partial<{ + firstName: string; + lastName: string; +}>; +type VerifyEmail = { + detail: string; +}; +type VerifyEmailRequest = { + key: string; +}; + +const LoginRequest: z.ZodType = z + .object({ email: z.string().min(1).email(), password: z.string().min(1) }) + .passthrough(); +const UserDetails: z.ZodType = z + .object({ + pk: z.number().int(), + email: z.string().email(), + firstName: z.string().max(150).optional(), + lastName: z.string().max(150).optional(), + }) + .passthrough(); +const Login: z.ZodType = z + .object({ + access: z.string(), + refresh: z.string(), + accessExpiration: z.string().datetime({ offset: true }), + refreshExpiration: z.string().datetime({ offset: true }), + user: UserDetails, + }) + .passthrough(); +const JWTLogoutRequest: z.ZodType = z + .object({ refresh: z.string().min(1) }) + .partial() + .passthrough(); +const JWTLogout: z.ZodType = z + .object({ detail: z.string() }) + .passthrough(); +const PasswordChangeRequest: z.ZodType = z + .object({ + newPassword1: z.string().min(1).max(128), + newPassword2: z.string().min(1).max(128), + }) + .passthrough(); +const PasswordChange: z.ZodType = z + .object({ detail: z.string() }) + .passthrough(); +const PasswordResetRequest: z.ZodType = z + .object({ email: z.string().min(1).email() }) + .passthrough(); +const PasswordReset: z.ZodType = z + .object({ detail: z.string() }) + .passthrough(); +const PasswordResetConfirmRequest: z.ZodType = z + .object({ + newPassword1: z.string().min(1).max(128), + newPassword2: z.string().min(1).max(128), + uid: z.string().min(1), + token: z.string().min(1), + }) + .passthrough(); +const PasswordResetConfirm: z.ZodType = z + .object({ detail: z.string() }) + .passthrough(); +const RegisterRequest: z.ZodType = z + .object({ + email: z.string().min(1).email(), + password1: z.string().min(1), + password2: z.string().min(1), + firstName: z.string().optional(), + lastName: z.string().optional(), + }) + .passthrough(); +const Register: z.ZodType = z + .object({ detail: z.string() }) + .passthrough(); +const ResendEmailVerificationRequest: z.ZodType = + z.object({ email: z.string().min(1).email() }).passthrough(); +const ResendEmailVerification: z.ZodType = z + .object({ detail: z.string() }) + .passthrough(); +const VerifyEmailRequest: z.ZodType = z + .object({ key: z.string().min(1) }) + .passthrough(); +const VerifyEmail: z.ZodType = z + .object({ detail: z.string() }) + .passthrough(); +const SocialAccount: z.ZodType = z + .object({ + id: z.number().int(), + provider: z.string().max(200), + uid: z.string().max(191), + lastLogin: z.string().datetime({ offset: true }), + dateJoined: z.string().datetime({ offset: true }), + }) + .passthrough(); +const PaginatedSocialAccountList: z.ZodType = z + .object({ + count: z.number().int(), + next: z.string().url().nullish(), + previous: z.string().url().nullish(), + results: z.array(SocialAccount), + }) + .passthrough(); +const CookieTokenRefreshRequest: z.ZodType = z + .object({ refresh: z.string() }) + .partial() + .passthrough(); +const CookieTokenRefresh: z.ZodType = z + .object({ + access: z.string(), + accessExpiration: z.string().datetime({ offset: true }), + }) + .passthrough(); +const TokenVerifyRequest: z.ZodType = z + .object({ token: z.string().min(1) }) + .passthrough(); +const UserDetailsRequest: z.ZodType = z + .object({ firstName: z.string().max(150), lastName: z.string().max(150) }) + .partial() + .passthrough(); +const PatchedUserDetailsRequest: z.ZodType = z + .object({ firstName: z.string().max(150), lastName: z.string().max(150) }) + .partial() + .passthrough(); + +export const schemas = { + LoginRequest, + UserDetails, + Login, + JWTLogoutRequest, + JWTLogout, + PasswordChangeRequest, + PasswordChange, + PasswordResetRequest, + PasswordReset, + PasswordResetConfirmRequest, + PasswordResetConfirm, + RegisterRequest, + Register, + ResendEmailVerificationRequest, + ResendEmailVerification, + VerifyEmailRequest, + VerifyEmail, + SocialAccount, + PaginatedSocialAccountList, + CookieTokenRefreshRequest, + CookieTokenRefresh, + TokenVerifyRequest, + UserDetailsRequest, + PatchedUserDetailsRequest, +}; + +const endpoints = makeApi([ + { + method: "post", + path: "/api/auth/login/", + description: `Authenticate with username/email and password to obtain access tokens. Returns user details along with JWT access and refresh tokens with expiration times. Authentication cookies are set automatically for secure token storage.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: LoginRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: Login, + }, + { + method: "post", + path: "/api/auth/logout/", + description: `Logout user and invalidate authentication tokens. Blacklists JWT refresh tokens to prevent further use. Clears authentication cookies from the browser. Requires authentication to ensure only valid sessions can be logged out.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z + .object({ refresh: z.string().min(1) }) + .partial() + .passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/password/change/", + description: `Change the current user's password. Requires authentication. `, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: PasswordChangeRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/password/reset/", + description: `Send password reset instructions to the provided email address. If the email is registered, a secure reset link will be sent. The link expires after a limited time for security.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ email: z.string().min(1).email() }).passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/password/reset/confirm/", + description: `Complete the password reset process using the token from the reset email. Requires the UID and token from the email along with the new password. The token is single-use and expires for security.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: PasswordResetConfirmRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/registration/", + description: `Register a new user account. Users must verify their email address before the account is fully activated.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: RegisterRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/registration/resend-email/", + description: `Send a new email verification message to unverified email addresses. Only works for email addresses that are registered but not yet verified.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ email: z.string().min(1).email() }).passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "get", + path: "/api/auth/registration/verify-email/", + description: `GET method not allowed for email verification.`, + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.void(), + errors: [ + { + status: 405, + description: `Method not allowed`, + schema: z.void(), + }, + ], + }, + { + method: "post", + path: "/api/auth/registration/verify-email/", + description: `Confirm email address using the verification key sent via email. This activates the user account and allows login access.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ key: z.string().min(1) }).passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "get", + path: "/api/auth/social/accounts/", + description: `List all social accounts connected to the current user. Shows account details including provider, UID, and connection dates.`, + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + { + name: "limit", + type: "Query", + schema: z.number().int().optional(), + }, + { + name: "offset", + type: "Query", + schema: z.number().int().optional(), + }, + ], + response: PaginatedSocialAccountList, + }, + { + method: "delete", + path: "/api/auth/social/accounts/:id/", + description: `Disconnect a social account from the current user. Removes the social account connection and prevents future logins via that provider. Requires authentication and the account must belong to the current user.`, + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + { + name: "id", + type: "Path", + schema: z.number().int(), + }, + ], + response: z.void(), + }, + { + method: "post", + path: "/api/auth/token/refresh/", + description: `Generate new JWT access tokens using refresh tokens. Refresh tokens can be provided in request data or extracted automatically from HTTP cookies. Returns new access tokens with updated expiration times. New tokens are automatically set in HTTP cookies for secure storage.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ refresh: z.string() }).partial().passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: CookieTokenRefresh, + }, + { + method: "post", + path: "/api/auth/token/verify/", + description: `Takes a token and indicates if it is valid. This view provides no +information about a token's fitness for a particular use.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ token: z.string().min(1) }).passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.void(), + }, + { + method: "get", + path: "/api/auth/user/", + description: `Retrieve the authenticated user's profile information including username, email, first name, and last name. Password fields are excluded.`, + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: UserDetails, + }, + { + method: "put", + path: "/api/auth/user/", + description: `Update the authenticated user's profile information. Allows modification of username, first name, and last name. Email field is read-only for security.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: UserDetailsRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: UserDetails, + }, + { + method: "patch", + path: "/api/auth/user/", + description: `Partially update the authenticated user's profile information. Only provided fields will be updated. Email field is read-only.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: PatchedUserDetailsRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: UserDetails, + }, + { + method: "get", + path: "/api/status/", + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.void(), + }, +]); + +export const api = new Zodios(endpoints); + +export function createApiClient(baseUrl: string, options?: ZodiosOptions) { + return new Zodios(baseUrl, endpoints, options); +} diff --git a/lib/tests/test-generation/api-doc-grouped-expected/auth.ts b/lib/tests/test-generation/api-doc-grouped-expected/auth.ts new file mode 100644 index 0000000..4494157 --- /dev/null +++ b/lib/tests/test-generation/api-doc-grouped-expected/auth.ts @@ -0,0 +1,470 @@ +import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core"; +import { z } from "zod"; + +type Login = { + access: string; + refresh: string; + accessExpiration: string; + refreshExpiration: string; + user: UserDetails; +}; +type LoginRequest = { + email: string; + password: string; +}; +type UserDetails = { + pk: number; + email: string; + firstName?: string | undefined; + lastName?: string | undefined; +}; +type PasswordChangeRequest = { + newPassword1: string; + newPassword2: string; +}; +type PasswordResetConfirmRequest = { + newPassword1: string; + newPassword2: string; + uid: string; + token: string; +}; +type RegisterRequest = { + email: string; + password1: string; + password2: string; + firstName?: string | undefined; + lastName?: string | undefined; +}; +type PaginatedSocialAccountList = { + count: number; + next?: (string | null) | undefined; + previous?: (string | null) | undefined; + results: Array; +}; +type SocialAccount = { + id: number; + provider: string; + uid: string; + lastLogin: string; + dateJoined: string; +}; +type CookieTokenRefresh = { + access: string; + accessExpiration: string; +}; +type UserDetailsRequest = Partial<{ + firstName: string; + lastName: string; +}>; +type PatchedUserDetailsRequest = Partial<{ + firstName: string; + lastName: string; +}>; + +const UserDetails: z.ZodType = z + .object({ + pk: z.number().int(), + email: z.string().email(), + firstName: z.string().max(150).optional(), + lastName: z.string().max(150).optional(), + }) + .passthrough(); +const Login: z.ZodType = z + .object({ + access: z.string(), + refresh: z.string(), + accessExpiration: z.string().datetime({ offset: true }), + refreshExpiration: z.string().datetime({ offset: true }), + user: UserDetails, + }) + .passthrough(); +const SocialAccount: z.ZodType = z + .object({ + id: z.number().int(), + provider: z.string().max(200), + uid: z.string().max(191), + lastLogin: z.string().datetime({ offset: true }), + dateJoined: z.string().datetime({ offset: true }), + }) + .passthrough(); +const PaginatedSocialAccountList: z.ZodType = z + .object({ + count: z.number().int(), + next: z.string().url().nullish(), + previous: z.string().url().nullish(), + results: z.array(SocialAccount), + }) + .passthrough(); +const LoginRequest: z.ZodType = z + .object({ email: z.string().min(1).email(), password: z.string().min(1) }) + .passthrough(); +const PasswordChangeRequest: z.ZodType = z + .object({ + newPassword1: z.string().min(1).max(128), + newPassword2: z.string().min(1).max(128), + }) + .passthrough(); +const PasswordResetConfirmRequest: z.ZodType = z + .object({ + newPassword1: z.string().min(1).max(128), + newPassword2: z.string().min(1).max(128), + uid: z.string().min(1), + token: z.string().min(1), + }) + .passthrough(); +const RegisterRequest: z.ZodType = z + .object({ + email: z.string().min(1).email(), + password1: z.string().min(1), + password2: z.string().min(1), + firstName: z.string().optional(), + lastName: z.string().optional(), + }) + .passthrough(); +const CookieTokenRefresh: z.ZodType = z + .object({ + access: z.string(), + accessExpiration: z.string().datetime({ offset: true }), + }) + .passthrough(); +const UserDetailsRequest: z.ZodType = z + .object({ firstName: z.string().max(150), lastName: z.string().max(150) }) + .partial() + .passthrough(); +const PatchedUserDetailsRequest: z.ZodType = z + .object({ firstName: z.string().max(150), lastName: z.string().max(150) }) + .partial() + .passthrough(); + +export const schemas = { + UserDetails, + Login, + SocialAccount, + PaginatedSocialAccountList, + LoginRequest, + PasswordChangeRequest, + PasswordResetConfirmRequest, + RegisterRequest, + CookieTokenRefresh, + UserDetailsRequest, + PatchedUserDetailsRequest, +}; + +const endpoints = makeApi([ + { + method: "post", + path: "/api/auth/login/", + description: `Authenticate with username/email and password to obtain access tokens. Returns user details along with JWT access and refresh tokens with expiration times. Authentication cookies are set automatically for secure token storage.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: LoginRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: Login, + }, + { + method: "post", + path: "/api/auth/logout/", + description: `Logout user and invalidate authentication tokens. Blacklists JWT refresh tokens to prevent further use. Clears authentication cookies from the browser. Requires authentication to ensure only valid sessions can be logged out.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z + .object({ refresh: z.string().min(1) }) + .partial() + .passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/password/change/", + description: `Change the current user's password. Requires authentication. `, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: PasswordChangeRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/password/reset/", + description: `Send password reset instructions to the provided email address. If the email is registered, a secure reset link will be sent. The link expires after a limited time for security.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ email: z.string().min(1).email() }).passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/password/reset/confirm/", + description: `Complete the password reset process using the token from the reset email. Requires the UID and token from the email along with the new password. The token is single-use and expires for security.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: PasswordResetConfirmRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/registration/", + description: `Register a new user account. Users must verify their email address before the account is fully activated.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: RegisterRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "post", + path: "/api/auth/registration/resend-email/", + description: `Send a new email verification message to unverified email addresses. Only works for email addresses that are registered but not yet verified.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ email: z.string().min(1).email() }).passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "get", + path: "/api/auth/registration/verify-email/", + description: `GET method not allowed for email verification.`, + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.void(), + errors: [ + { + status: 405, + description: `Method not allowed`, + schema: z.void(), + }, + ], + }, + { + method: "post", + path: "/api/auth/registration/verify-email/", + description: `Confirm email address using the verification key sent via email. This activates the user account and allows login access.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ key: z.string().min(1) }).passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.object({ detail: z.string() }).passthrough(), + }, + { + method: "get", + path: "/api/auth/social/accounts/", + description: `List all social accounts connected to the current user. Shows account details including provider, UID, and connection dates.`, + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + { + name: "limit", + type: "Query", + schema: z.number().int().optional(), + }, + { + name: "offset", + type: "Query", + schema: z.number().int().optional(), + }, + ], + response: PaginatedSocialAccountList, + }, + { + method: "delete", + path: "/api/auth/social/accounts/:id/", + description: `Disconnect a social account from the current user. Removes the social account connection and prevents future logins via that provider. Requires authentication and the account must belong to the current user.`, + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + { + name: "id", + type: "Path", + schema: z.number().int(), + }, + ], + response: z.void(), + }, + { + method: "post", + path: "/api/auth/token/refresh/", + description: `Generate new JWT access tokens using refresh tokens. Refresh tokens can be provided in request data or extracted automatically from HTTP cookies. Returns new access tokens with updated expiration times. New tokens are automatically set in HTTP cookies for secure storage.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ refresh: z.string() }).partial().passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: CookieTokenRefresh, + }, + { + method: "post", + path: "/api/auth/token/verify/", + description: `Takes a token and indicates if it is valid. This view provides no +information about a token's fitness for a particular use.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: z.object({ token: z.string().min(1) }).passthrough(), + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.void(), + }, + { + method: "get", + path: "/api/auth/user/", + description: `Retrieve the authenticated user's profile information including username, email, first name, and last name. Password fields are excluded.`, + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: UserDetails, + }, + { + method: "put", + path: "/api/auth/user/", + description: `Update the authenticated user's profile information. Allows modification of username, first name, and last name. Email field is read-only for security.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: UserDetailsRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: UserDetails, + }, + { + method: "patch", + path: "/api/auth/user/", + description: `Partially update the authenticated user's profile information. Only provided fields will be updated. Email field is read-only.`, + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: PatchedUserDetailsRequest, + }, + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: UserDetails, + }, +]); + +export const AuthApi = new Zodios(endpoints); + +export function createApiClient(baseUrl: string, options?: ZodiosOptions) { + return new Zodios(baseUrl, endpoints, options); +} diff --git a/lib/tests/test-generation/api-doc-grouped-expected/index.ts b/lib/tests/test-generation/api-doc-grouped-expected/index.ts new file mode 100644 index 0000000..a88f051 --- /dev/null +++ b/lib/tests/test-generation/api-doc-grouped-expected/index.ts @@ -0,0 +1,2 @@ +export { AuthApi } from "./auth"; +export { StatusApi } from "./status"; diff --git a/lib/tests/test-generation/api-doc-grouped-expected/status.ts b/lib/tests/test-generation/api-doc-grouped-expected/status.ts new file mode 100644 index 0000000..7bb5ee7 --- /dev/null +++ b/lib/tests/test-generation/api-doc-grouped-expected/status.ts @@ -0,0 +1,24 @@ +import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core"; +import { z } from "zod"; + +const endpoints = makeApi([ + { + method: "get", + path: "/api/status/", + requestFormat: "json", + parameters: [ + { + name: "accept-language", + type: "Header", + schema: z.string().optional(), + }, + ], + response: z.void(), + }, +]); + +export const StatusApi = new Zodios(endpoints); + +export function createApiClient(baseUrl: string, options?: ZodiosOptions) { + return new Zodios(baseUrl, endpoints, options); +}