Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Zod instead of our own validation system #69

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions itest/test/attachments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ beforeAll(async () => {
});

test('Get single attachment', async () => {
await expect(api.getAttachment(1)).resolves.toMatchInlineSnapshot();

await expect(api.getAttachment(1)).resolves.toEqual({
bug_id: expect.anything(),
content_type: 'image/png',
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
},
"dependencies": {
"axios": "1.6.8",
"luxon": "3.4.4"
"js-base64": "3.7.5",
"luxon": "3.4.4",
"zod": "3.21.4"
},
"packageManager": "[email protected]"
}
62 changes: 31 additions & 31 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { URL, URLSearchParams } from 'url';

import type { DateTime } from 'luxon';
import { URL, URLSearchParams } from 'url';
import { z } from 'zod';

import type { BugzillaLink, SearchParams } from './link';
import { PublicLink, params, PasswordLink, ApiKeyLink } from './link';
Expand All @@ -20,19 +20,18 @@ import type {
UpdatedAttachment,
} from './types';
import {
HistoryLookupSpec,
BugSpec,
UserSpec,
VersionSpec,
CommentsSpec,
CreatedCommentSpec,
CreatedBugSpec,
UpdatedBugTemplateSpec,
AttachmentsSpec,
CreatedAttachmentSpec,
UpdatedAttachmentTemplateSpec,
historyLookupSchema,
bugSchema,
userSchema,
versionSchema,
commentsSchema,
createdCommentSchema,
createdBugSchema,
updatedBugTemplateSchema,
attachmentsSchema,
createdAttachmentSchema,
updatedAttachmentTemplateSchema,
} from './types';
import { array, object } from './validators';

export type {
Bug,
Expand Down Expand Up @@ -73,13 +72,13 @@ export default class BugzillaAPI {
}

public async version(): Promise<string> {
let version = await this.link.get('version', object(VersionSpec));
let version = await this.link.get('version', versionSchema);

return version.version;
}

public whoami(): Promise<User> {
return this.link.get('whoami', object(UserSpec));
return this.link.get('whoami', userSchema);
}

public async bugHistory(
Expand All @@ -95,7 +94,7 @@ export default class BugzillaAPI {

let bugs = await this.link.get(
`bug/${bugId}/history`,
object(HistoryLookupSpec),
historyLookupSchema,
searchParams,
);

Expand Down Expand Up @@ -123,9 +122,13 @@ export default class BugzillaAPI {

let result = await this.link.get(
'bug',
object({
bugs: array(object(BugSpec, includes, excludes)),
z.object({
// TODO: pick includes and omit excludes | transform???
bugs: z.array(bugSchema),
}),
// object({
// bugs: array(object(BugSpec, includes, excludes)),
// }),
search,
);

Expand Down Expand Up @@ -174,7 +177,7 @@ export default class BugzillaAPI {
public async getComment(commentId: number): Promise<Comment | undefined> {
let comment = await this.link.get(
`bug/comment/${commentId}`,
object(CommentsSpec),
commentsSchema,
);

if (!comment) {
Expand All @@ -185,10 +188,7 @@ export default class BugzillaAPI {
}

public async getBugComments(bugId: number): Promise<Comment[] | undefined> {
let comments = await this.link.get(
`bug/${bugId}/comment`,
object(CommentsSpec),
);
let comments = await this.link.get(`bug/${bugId}/comment`, commentsSchema);

if (!comments) {
throw new Error(`Failed to get comments of bug #${bugId}.`);
Expand All @@ -209,7 +209,7 @@ export default class BugzillaAPI {

let commentStatus = await this.link.post(
`bug/${bugId}/comment`,
object(CreatedCommentSpec),
createdCommentSchema,
content,
);

Expand All @@ -221,7 +221,7 @@ export default class BugzillaAPI {
}

public async createBug(bug: CreateBugContent): Promise<number> {
let bugStatus = await this.link.post('bug', object(CreatedBugSpec), bug);
let bugStatus = await this.link.post('bug', createdBugSchema, bug);

if (!bugStatus) {
throw new Error('Failed to create bug.');
Expand All @@ -236,7 +236,7 @@ export default class BugzillaAPI {
): Promise<UpdatedBug[]> {
let response = await this.link.put(
`bug/${bugIdOrAlias}`,
object(UpdatedBugTemplateSpec),
updatedBugTemplateSchema,
data,
);

Expand All @@ -252,7 +252,7 @@ export default class BugzillaAPI {
): Promise<Attachment | undefined> {
let attachment = await this.link.get(
`bug/attachment/${attachmentId}`,
object(AttachmentsSpec),
attachmentsSchema,
);

if (!attachment) {
Expand All @@ -267,7 +267,7 @@ export default class BugzillaAPI {
): Promise<Attachment[] | undefined> {
let attachments = await this.link.get(
`bug/${bugId}/attachment`,
object(AttachmentsSpec),
attachmentsSchema,
);

if (!attachments) {
Expand All @@ -287,7 +287,7 @@ export default class BugzillaAPI {

let attachmentStatus = await this.link.post(
`bug/${bugId}/attachment`,
object(CreatedAttachmentSpec),
createdAttachmentSchema,
{ ...attachment, ...dataBase64 },
);

Expand All @@ -304,7 +304,7 @@ export default class BugzillaAPI {
): Promise<UpdatedAttachment[]> {
let response = await this.link.put(
`bug/attachment/${attachmentId}`,
object(UpdatedAttachmentTemplateSpec),
updatedAttachmentTemplateSchema,
data,
);

Expand Down
86 changes: 47 additions & 39 deletions src/link.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { URLSearchParams, URL } from 'url';
import { z, ZodSchema } from 'zod';

import axios, { AxiosRequestConfig } from 'axios';
import { object, Validator } from './validators';
import { LoginResponseSpec } from './types';
import { loginResponseSchema } from './types';

export type SearchParams =
| Record<string, string>
Expand All @@ -28,10 +28,10 @@ function isError(payload: unknown): payload is ApiError {
return payload && typeof payload == 'object' && payload.error;
}

async function performRequest<T>(
config: AxiosRequestConfig,
validator: Validator<T>,
): Promise<T> {
async function performRequest<
TSchema extends ZodSchema,
KValues extends z.infer<TSchema>,
>(config: AxiosRequestConfig, schema: TSchema): Promise<KValues> {
try {
let response = await axios.request({
...config,
Expand All @@ -45,7 +45,7 @@ async function performRequest<T>(
throw new Error(response.data.message);
}

return validator(response.data);
return schema.parse(response.data);
} catch (e: unknown) {
if (axios.isAxiosError(e)) {
throw new Error(e.message);
Expand All @@ -67,10 +67,10 @@ export abstract class BugzillaLink {
this.instance = new URL('rest/', instance);
}

protected abstract request<T>(
config: AxiosRequestConfig,
validator: Validator<T>,
): Promise<T>;
protected abstract request<
TSchema extends ZodSchema,
KValues extends z.infer<TSchema>,
>(config: AxiosRequestConfig, schema: TSchema): Promise<KValues>;

protected buildURL(path: string, query?: SearchParams): URL {
let url = new URL(path, this.instance);
Expand All @@ -80,25 +80,29 @@ export abstract class BugzillaLink {
return url;
}

public async get<T>(
public async get<TSchema extends ZodSchema, KValues extends z.infer<TSchema>>(
path: string,
validator: Validator<T>,
schema: TSchema,
searchParams?: SearchParams,
): Promise<T> {
): Promise<KValues> {
return this.request(
{
url: this.buildURL(path, searchParams).toString(),
},
validator,
schema,
);
}

public async post<R, T>(
public async post<
R,
TSchema extends ZodSchema,
KValues extends z.infer<TSchema>,
>(
path: string,
validator: Validator<T>,
schema: TSchema,
content: R,
searchParams?: SearchParams,
): Promise<T> {
): Promise<KValues> {
return this.request(
{
url: this.buildURL(path, searchParams).toString(),
Expand All @@ -108,16 +112,20 @@ export abstract class BugzillaLink {
'Content-Type': 'application/json',
},
},
validator,
schema,
);
}

public async put<R, T>(
public async put<
R,
TSchema extends ZodSchema,
KValues extends z.infer<TSchema>,
>(
path: string,
validator: Validator<T>,
schema: TSchema,
content: R,
searchParams?: SearchParams,
): Promise<T> {
): Promise<KValues> {
return this.request(
{
url: this.buildURL(path, searchParams).toString(),
Expand All @@ -127,17 +135,17 @@ export abstract class BugzillaLink {
'Content-Type': 'application/json',
},
},
validator,
schema,
);
}
}

export class PublicLink extends BugzillaLink {
protected async request<T>(
config: AxiosRequestConfig,
validator: Validator<T>,
): Promise<T> {
return performRequest(config, validator);
protected async request<
TSchema extends ZodSchema,
KValues extends z.infer<TSchema>,
>(config: AxiosRequestConfig, schema: TSchema): Promise<KValues> {
return performRequest(config, schema);
}
}

Expand All @@ -152,10 +160,10 @@ export class ApiKeyLink extends BugzillaLink {
super(instance);
}

protected async request<T>(
config: AxiosRequestConfig,
validator: Validator<T>,
): Promise<T> {
protected async request<
TSchema extends ZodSchema,
KValues extends z.infer<TSchema>,
>(config: AxiosRequestConfig, schema: TSchema): Promise<KValues> {
return performRequest(
{
...config,
Expand All @@ -166,7 +174,7 @@ export class ApiKeyLink extends BugzillaLink {
Authorization: `Bearer ${this.apiKey}`,
},
},
validator,
schema,
);
}
}
Expand Down Expand Up @@ -195,16 +203,16 @@ export class PasswordLink extends BugzillaLink {
restrict_login: String(this.restrictLogin),
}).toString(),
},
object(LoginResponseSpec),
loginResponseSchema,
);

return loginInfo.token;
}

protected async request<T>(
config: AxiosRequestConfig,
validator: Validator<T>,
): Promise<T> {
protected async request<
TSchema extends ZodSchema,
KValues extends z.infer<TSchema>,
>(config: AxiosRequestConfig, schema: TSchema): Promise<KValues> {
if (!this.token) {
this.token = await this.login();
}
Expand All @@ -217,7 +225,7 @@ export class PasswordLink extends BugzillaLink {
'X-BUGZILLA-TOKEN': this.token,
},
},
validator,
schema,
);
}
}
Loading
Loading