Skip to content

Commit

Permalink
feature: Better types and handling for different FormData implementat…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
olexandr-mazepa committed Feb 14, 2024
1 parent 4137ca5 commit 906723a
Show file tree
Hide file tree
Showing 26 changed files with 25,508 additions and 233 deletions.
2 changes: 2 additions & 0 deletions dist/Classes/Validations/multipleValidation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ export declare class MultipleValidationJob implements MultipleValidationJobResul
}
export default class MultipleValidationClient extends NavigationThruPages<MultipleValidationJobsListResult> implements IMultipleValidationClient {
request: Request;
private attachmentsHandler;
constructor(request: Request);
private handleResponse;
protected parseList(response: MultipleValidationJobsListResponse): MultipleValidationJobsListResult;
list(query?: MultipleValidationJobsListQuery): Promise<MultipleValidationJobsListResult>;
get(listId: string): Promise<MultipleValidationJob>;
private convertToExpectedShape;
create(listId: string, data: MultipleValidationCreationData): Promise<CreatedMultipleValidationJob>;
destroy(listId: string): Promise<CanceledMultipleValidationJob>;
}
27 changes: 27 additions & 0 deletions dist/Classes/common/AttachmentsHandler.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { Readable } from 'stream';
import { CustomFile, CustomFileData } from '../../Types';
import { AttachmentInfo, StreamValue } from '../../Types/Common/Attachments';
declare class BlobFromStream {
private _stream;
size: number;
constructor(stream: Readable, size: number);
stream(): Readable;
get [Symbol.toStringTag](): string;
}
declare class AttachmentsHandler {
private getAttachmentOptions;
private getFileInfo;
private getCustomFileInfo;
private getBufferInfo;
isStream(data: unknown): data is StreamValue;
isCustomFile(obj: unknown): obj is CustomFile;
isBrowserFile(obj: unknown): obj is File;
isBuffer(data: unknown): data is Buffer;
getAttachmentInfo(attachment: CustomFile | File | string | CustomFileData): AttachmentInfo;
convertToFDexpectedShape(userProvidedValue: CustomFile | File | string | CustomFileData): string | Blob | Buffer | NodeJS.ReadableStream | (CustomFile & StreamValue);
getBlobFromStream(stream: Readable, size: number): BlobFromStream;
}
export default AttachmentsHandler;
1 change: 1 addition & 0 deletions dist/Classes/common/Error.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export default class APIError extends Error implements APIErrorType {
stack: string;
details: string;
type: string;
static getUserDataError(statusText: string, message: string): APIError;
constructor({ status, statusText, message, body }: APIErrorOptions);
}
13 changes: 8 additions & 5 deletions dist/Classes/common/FormDataBuilder.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import * as NodeFormData from 'form-data';
import { InputFormData } from '../../Types/Common';
import { FormDataInput, InputFormData } from '../../Types/Common';
import { MimeMessage } from '../../Types';
declare class FormDataBuilder {
private FormDataConstructor;
private fileKeys;
private attachmentsHandler;
constructor(FormDataConstructor: InputFormData);
createFormData(data: any): NodeFormData | FormData;
private isFormDataPackage;
private getAttachmentOptions;
createFormData(data: FormDataInput): NodeFormData | FormData;
private addMimeDataToFD;
isMIME(data: unknown): data is MimeMessage;
private isFormDataPackage;
private isMessageAttachment;
private addFilesToFD;
private isStream;
private addCommonPropertyToFD;
}
export default FormDataBuilder;
10 changes: 5 additions & 5 deletions dist/Classes/common/Request.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as NodeFormData from 'form-data';
import { RequestOptions, InputFormData, APIResponse, IpPoolDeleteData } from '../../Types';
import { RequestOptions, InputFormData, APIResponse, IpPoolDeleteData, FormDataInput } from '../../Types';
declare class Request {
private username;
private key;
Expand All @@ -20,10 +20,10 @@ declare class Request {
command(method: string, url: string, data?: Record<string, unknown> | Record<string, unknown>[] | string | NodeFormData | FormData, options?: Record<string, unknown>, addDefaultHeaders?: boolean): Promise<APIResponse>;
get(url: string, query?: Record<string, unknown> | Array<Array<string>>, options?: Record<string, unknown>): Promise<APIResponse>;
post(url: string, data?: Record<string, unknown> | string, options?: Record<string, unknown>): Promise<APIResponse>;
postWithFD(url: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<APIResponse>;
putWithFD(url: string, data: Record<string, unknown>): Promise<APIResponse>;
patchWithFD(url: string, data: Record<string, unknown>): Promise<APIResponse>;
put(url: string, data?: Record<string, unknown> | string, options?: Record<string, unknown>): Promise<APIResponse>;
postWithFD(url: string, data: FormDataInput): Promise<APIResponse>;
putWithFD(url: string, data: FormDataInput): Promise<APIResponse>;
patchWithFD(url: string, data: FormDataInput): Promise<APIResponse>;
put(url: string, data?: FormDataInput | string, options?: Record<string, unknown>): Promise<APIResponse>;
delete(url: string, data?: IpPoolDeleteData): Promise<APIResponse>;
}
export default Request;
12 changes: 12 additions & 0 deletions dist/Types/Common/Attachments.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type NodePipeFunction = (destination: WritableStream, options?: {
end?: boolean;
}) => void;
export type BrowserPipeFunction = (destination: WritableStream) => void;
export type StreamValue = {
pipe: NodePipeFunction | BrowserPipeFunction;
};
export type AttachmentInfo = {
filename?: string;
contentType?: string;
knownLength?: number;
};
10 changes: 8 additions & 2 deletions dist/Types/Common/FormData.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import * as NodeFormData from 'form-data';
import { FormDataInputValue } from '../Messages';
export type FormDataOptions = {
[key: string]: any;
[key: string]: NodeFormData;
};
export type InputFormData = {
new (options?: HTMLFormElement | FormDataOptions): NodeFormData | FormData;
new (form?: HTMLFormElement | undefined, submitter?: HTMLElement | null | undefined): FormData;
} | {
new (options?: FormDataOptions): NodeFormData;
};
export type FormDataInput = {
[key: string]: FormDataInputValue;
};
18 changes: 15 additions & 3 deletions dist/Types/Messages/Messages.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// <reference types="node" />
/// <reference types="node" />
/**
* Ensures the object has least one key present and not undefined
*
Expand All @@ -7,6 +8,17 @@
export type AtLeastOneKeyPresent<Object_, Keys extends keyof Object_ = keyof Object_> = Pick<Object_, Exclude<keyof Object_, Keys>> & {
[K in Keys]-?: Required<Pick<Object_, K>> & Partial<Pick<Object_, Exclude<Keys, K>>>;
}[Keys];
export type MimeMessage = string | Blob | Buffer | NodeJS.ReadableStream;
export type CustomFileData = string | Blob | File | Buffer | NodeJS.ReadableStream;
export type CustomFile = {
data: CustomFileData;
filename?: string;
contentType?: string;
knownLength?: number;
[key: string]: unknown;
};
export type MessageAttachment = CustomFile | CustomFile[] | File | File[] | string | CustomFileData | CustomFileData[];
export type FormDataInputValue = MimeMessage | CustomFileData | string | string[] | boolean | MessageAttachment | undefined | number;
export type MailgunMessageContent = AtLeastOneKeyPresent<{
/**
* Body of the message. (text version)
Expand All @@ -19,7 +31,7 @@ export type MailgunMessageContent = AtLeastOneKeyPresent<{
/**
* Body of the message. (MIME version)
*/
message?: string | Buffer | Blob;
message?: MimeMessage;
/**
* Name of a template stored via [template API](https://documentation.mailgun.com/en/latest/api-templates.html#api-templates). See [Templates](https://documentation.mailgun.com/en/latest/user_manual.html#templating) for more information
*/
Expand Down Expand Up @@ -57,7 +69,7 @@ export type MailgunMessageData = MailgunMessageContent & {
*
* **Important:** You must use `multipart/form-data` encoding when sending attachments.
*/
attachment?: any;
attachment?: MessageAttachment;
/**
* Attachment with `inline` disposition. Can be used to send inline images (see example).
*
Expand Down Expand Up @@ -163,7 +175,7 @@ export type MailgunMessageData = MailgunMessageContent & {
* `v:` prefix followed by an arbitrary name allows to attach a custom JSON data to the message. See [Attaching Data to Messages](https://documentation.mailgun.com/en/latest/user_manual.html#manual-customdata) for more information.
*/
'v:my-var'?: string;
[key: string]: unknown;
[key: string]: FormDataInputValue;
};
export type MessagesSendAPIResponse = {
status: number;
Expand Down
7 changes: 3 additions & 4 deletions dist/Types/Validations/MultipleValidation.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PagesList, ParsedPagesList } from '../Common';
import { CustomFile, CustomFileData } from '../Messages';
export type MultipleValidationJobData = {
created_at: number;
id: string;
Expand Down Expand Up @@ -57,12 +58,10 @@ export type CreatedMultipleValidationJob = {
message: string;
};
export type MultipleValidationCreationData = {
file: Record<string, unknown>;
[key: string]: unknown | undefined;
file: CustomFileData | CustomFile;
};
export type MultipleValidationCreationDataUpdated = {
multipleValidationFile: Record<string, unknown>;
[key: string]: unknown | undefined;
multipleValidationFile: CustomFileData | CustomFile;
};
export type MultipleValidationJobsListResult = {
jobs: MultipleValidationJobResult[];
Expand Down
14,692 changes: 14,689 additions & 3 deletions dist/mailgun.node.js

Large diffs are not rendered by default.

9,935 changes: 9,932 additions & 3 deletions dist/mailgun.web.js

Large diffs are not rendered by default.

12 changes: 2 additions & 10 deletions lib/Classes/Domains/domainsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import {

import { APIResponse } from '../../Types/Common/ApiResponse';
import APIError from '../common/Error';
import { APIErrorOptions } from '../../Types/Common';

import Request from '../common/Request';

import DomainCredentialsClient from './domainsCredentials';
Expand Down Expand Up @@ -168,7 +166,7 @@ export default class DomainsClient implements IDomainsClient {
data: OpenTrackingInfo | ClickTrackingInfo | UnsubscribeTrackingInfo
): Promise<UpdatedOpenTracking> {
if (typeof data?.active === 'boolean') {
throw new APIError({ status: 400, statusText: 'Received boolean value for active property', body: { message: 'Property "active" must contain string value.' } } as APIErrorOptions);
throw APIError.getUserDataError('Received boolean value for active property', 'Property "active" must contain string value.');
}
return this.request.putWithFD(urljoin('/v3/domains', domain, 'tracking', type), data)
.then((res : APIResponse) => this._parseTrackingUpdate(res as UpdateDomainTrackingResponse));
Expand Down Expand Up @@ -196,13 +194,7 @@ export default class DomainsClient implements IDomainsClient {
unlinkIpPoll(domain: string, replacement: ReplacementForPool): Promise<APIResponse> {
let searchParams = '';
if (replacement.pool_id && replacement.ip) {
throw new APIError(
{
status: 400,
statusText: 'Too much data for replacement',
body: { message: 'Please specify either pool_id or ip (not both)' }
} as APIErrorOptions
);
throw APIError.getUserDataError('Too much data for replacement', 'Please specify either pool_id or ip (not both)');
} else if (replacement.pool_id) {
searchParams = `?pool_id=${replacement.pool_id}`;
} else if (replacement.ip) {
Expand Down
6 changes: 1 addition & 5 deletions lib/Classes/Messages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import APIError from './common/Error';
import {
APIErrorOptions,
MailgunMessageData,
MessagesSendAPIResponse,
MessagesSendResult
Expand Down Expand Up @@ -28,10 +27,7 @@ export default class MessagesClient implements IMessagesClient {
]);

if (!data || Object.keys(data).length === 0) {
throw new APIError({
status: 400,
message: 'Message data object can not be empty'
} as APIErrorOptions);
throw APIError.getUserDataError('Message data object can not be empty', 'Message data object can not be empty');
}
return Object.keys(data).reduce((acc, key) => {
if (yesNoProperties.has(key) && typeof data[key] === 'boolean') {
Expand Down
56 changes: 21 additions & 35 deletions lib/Classes/Suppressions/SuppressionsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
SuppressionDestroyResult,
SuppressionDestroyResponse
} from '../../Types/Suppressions';
import { APIErrorOptions } from '../../Types/Common';

const createOptions = {
headers: { 'Content-Type': 'application/json' }
Expand Down Expand Up @@ -83,16 +82,13 @@ export default class SuppressionClient
isDataArray: boolean
): Promise<SuppressionCreationResult> {
if (isDataArray) {
throw new APIError({
status: 400,
statusText: 'Data property should be an object',
body: {
message: 'Whitelist\'s creation process does not support multiple creations. Data property should be an object'
}
} as APIErrorOptions);
throw APIError.getUserDataError(
'Data property should be an object',
'Whitelist\'s creation process does not support multiple creations. Data property should be an object'
);
}
return this.request
.postWithFD(urljoin('v3', domain, 'whitelists'), data)
.postWithFD(urljoin('v3', domain, 'whitelists'), data as SuppressionCreationData)
.then(this.prepareResponse);
}

Expand All @@ -103,36 +99,27 @@ export default class SuppressionClient
if (Array.isArray(data)) { // User provided an array
const isContainsTag = data.some((unsubscribe: SuppressionCreationData) => unsubscribe.tag);
if (isContainsTag) {
throw new APIError({
status: 400,
statusText: 'Tag property should not be used for creating multiple unsubscribes.',
body: {
message: 'Tag property can be used only if one unsubscribe provided as second argument of create method. Please use tags instead.'
}
} as APIErrorOptions);
throw APIError.getUserDataError(
'Tag property should not be used for creating multiple unsubscribes.',
'Tag property can be used only if one unsubscribe provided as second argument of create method. Please use tags instead.'
);
}
return this.request
.post(urljoin('v3', domain, 'unsubscribes'), JSON.stringify(data), createOptions)
.then(this.prepareResponse);
}

if (data?.tags) {
throw new APIError({
status: 400,
statusText: 'Tags property should not be used for creating one unsubscribe.',
body: {
message: 'Tags property can be used if you provides an array of unsubscribes as second argument of create method. Please use tag instead'
}
} as APIErrorOptions);
throw APIError.getUserDataError(
'Tags property should not be used for creating one unsubscribe.',
'Tags property can be used if you provides an array of unsubscribes as second argument of create method. Please use tag instead'
);
}
if (Array.isArray(data.tag)) {
throw new APIError({
status: 400,
statusText: 'Tag property can not be an array',
body: {
message: 'Please use array of unsubscribes as second argument of create method to be able to provide few tags'
}
} as APIErrorOptions);
throw APIError.getUserDataError(
'Tag property can not be an array',
'Please use array of unsubscribes as second argument of create method to be able to provide few tags'
);
}
/* We need Form Data for unsubscribes if we want to support the "tag" property */
return this.request
Expand All @@ -144,11 +131,10 @@ export default class SuppressionClient
if (type in this.models) {
return this.models[type as keyof typeof this.models];
}
throw new APIError({
status: 400,
statusText: 'Unknown type value',
body: { message: 'Type may be only one of [bounces, complaints, unsubscribes, whitelists]' }
} as APIErrorOptions);
throw APIError.getUserDataError(
'Unknown type value',
'Type may be only one of [bounces, complaints, unsubscribes, whitelists]'
);
}

private prepareResponse(response: SuppressionCreationResponse): SuppressionCreationResult {
Expand Down
Loading

0 comments on commit 906723a

Please sign in to comment.