Skip to content
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
7 changes: 7 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as Errors from './core/error';
import * as Pagination from './core/pagination';
import { AbstractPage, type SkipLimitParams, SkipLimitResponse } from './core/pagination';
import * as Uploads from './core/uploads';
import { maybeMultipartFormRequestOptions } from './internal/uploads';
import * as API from './resources/index';
import { APIPromise } from './core/api-promise';
import {
Expand Down Expand Up @@ -393,6 +394,12 @@ export class PostGrid {

await this.prepareOptions(options);

// if body contains file uploads, convert to multipart/form-data (fastest way and making compiler happy)
const withMultipart = await maybeMultipartFormRequestOptions(options, this);
if (withMultipart.body !== options.body) {
options.body = withMultipart.body;
}

const { req, url, timeout } = await this.buildRequest(options, {
retryCount: maxRetries - retriesRemaining,
});
Expand Down
9 changes: 5 additions & 4 deletions src/resources/print-mail/letters.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../core/resource';
import type { Uploadable } from '../../core/uploads';
import * as ContactsAPI from './contacts';
import * as PrintMailAPI from './print-mail';
import { APIPromise } from '../../core/api-promise';
Expand Down Expand Up @@ -108,7 +109,7 @@ export interface AttachedPdf {
/**
* The file (multipart form upload) or URL pointing to a PDF for the attached PDF.
*/
file: string;
file: string | Uploadable;

/**
* Enum representing the placement of the attached PDF.
Expand Down Expand Up @@ -405,7 +406,7 @@ export namespace PlasticCard {
* A URL pointing to a PDF file for the double-sided plastic card or the file
* itself.
*/
pdf?: string;
pdf?: string | Uploadable;
}

/**
Expand All @@ -422,7 +423,7 @@ export namespace PlasticCard {
* A URL pointing to a PDF file for the single-sided plastic card or the PDF file
* itself.
*/
pdf?: string;
pdf?: string | Uploadable;

/**
* The template ID for the single-sided plastic card.
Expand Down Expand Up @@ -596,7 +597,7 @@ export declare namespace LetterCreateParams {
/**
* A URL pointing to a PDF file for the letter or the PDF file itself.
*/
pdf: string;
pdf: string | Uploadable;

/**
* The recipient of this order. You can either supply the contact information
Expand Down
5 changes: 3 additions & 2 deletions src/resources/print-mail/order-profiles/cheques.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../../core/resource';
import type { Uploadable } from '../../../core/uploads';
import * as ChequesAPI from '../cheques';
import { APIPromise } from '../../../core/api-promise';
import { PagePromise, SkipLimit, type SkipLimitParams } from '../../../core/pagination';
Expand Down Expand Up @@ -387,7 +388,7 @@ export interface ChequeCreateParams {
* Body param: PDF file for an optional attached letter. Cannot be used with
* `letterHTML` or `letterTemplate`. Input only.
*/
letterPDF?: string;
letterPDF?: string | Uploadable;

/**
* Body param: ID of a template for an optional attached letter. Cannot be used
Expand Down Expand Up @@ -494,7 +495,7 @@ export interface ChequeUpdateParams {
* Body param: PDF file for an optional attached letter. Cannot be used with
* `letterHTML` or `letterTemplate`. Input only.
*/
letterPDF?: string;
letterPDF?: string | Uploadable;

/**
* Body param: ID of a template for an optional attached letter. Cannot be used
Expand Down
5 changes: 3 additions & 2 deletions src/resources/print-mail/order-profiles/letters.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../../core/resource';
import type { Uploadable } from '../../../core/uploads';
import * as LettersAPI from '../letters';
import { APIPromise } from '../../../core/api-promise';
import { PagePromise, SkipLimit, type SkipLimitParams } from '../../../core/pagination';
Expand Down Expand Up @@ -337,7 +338,7 @@ export interface LetterCreateParams {
* Body param: A PDF file containing the letter content. Cannot be used with
* `template`.
*/
pdf?: string;
pdf?: string | Uploadable;

/**
* Body param: Specifies which page number should be perforated (if any).
Expand Down Expand Up @@ -446,7 +447,7 @@ export interface LetterUpdateParams {
* Body param: A PDF file containing the letter content. Cannot be used with
* `template`.
*/
pdf?: string;
pdf?: string | Uploadable;

/**
* Body param: Specifies which page number should be perforated (if any).
Expand Down
5 changes: 3 additions & 2 deletions src/resources/print-mail/order-profiles/postcards.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../../core/resource';
import type { Uploadable } from '../../../core/uploads';
import { APIPromise } from '../../../core/api-promise';
import { PagePromise, SkipLimit, type SkipLimitParams } from '../../../core/pagination';
import { RequestOptions } from '../../../internal/request-options';
Expand Down Expand Up @@ -296,7 +297,7 @@ export interface PostcardCreateParams {
* Body param: A 2-page PDF file containing the postcard content (front and back).
* Cannot be used with `frontTemplate`/`backTemplate`.
*/
pdf?: string;
pdf?: string | Uploadable;
}

export interface PostcardRetrieveParams {
Expand Down Expand Up @@ -376,7 +377,7 @@ export interface PostcardUpdateParams {
* Body param: A 2-page PDF file containing the postcard content (front and back).
* Cannot be used with `frontTemplate`/`backTemplate`.
*/
pdf?: string;
pdf?: string | Uploadable;
}

export interface PostcardListParams extends SkipLimitParams {
Expand Down
5 changes: 3 additions & 2 deletions src/resources/print-mail/order-profiles/self-mailers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../../core/resource';
import type { Uploadable } from '../../../core/uploads';
import { APIPromise } from '../../../core/api-promise';
import { PagePromise, SkipLimit, type SkipLimitParams } from '../../../core/pagination';
import { RequestOptions } from '../../../internal/request-options';
Expand Down Expand Up @@ -303,7 +304,7 @@ export interface SelfMailerCreateParams {
* Body param: A 2-page PDF file containing the self-mailer content (inside and
* outside). Cannot be used with `insideTemplate`/`outsideTemplate`.
*/
pdf?: string;
pdf?: string | Uploadable;
}

export interface SelfMailerRetrieveParams {
Expand Down Expand Up @@ -387,7 +388,7 @@ export interface SelfMailerUpdateParams {
* Body param: A 2-page PDF file containing the self-mailer content (inside and
* outside). Cannot be used with `insideTemplate`/`outsideTemplate`.
*/
pdf?: string;
pdf?: string | Uploadable;
}

export interface SelfMailerListParams extends SkipLimitParams {
Expand Down
5 changes: 3 additions & 2 deletions src/resources/print-mail/postcards.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../core/resource';
import type { Uploadable } from '../../core/uploads';
import * as ContactsAPI from './contacts';
import * as PrintMailAPI from './print-mail';
import * as OrderProfilesPostcardsAPI from './order-profiles/postcards';
Expand Down Expand Up @@ -404,7 +405,7 @@ export declare namespace PostcardCreateParams {
* A URL pointing to a 2 page PDF file. The first page is the front of the postcard
* and the second page is the back (where the address will be stamped on).
*/
pdf: string;
pdf: string | Uploadable;

/**
* Enum representing the supported postcard sizes.
Expand Down Expand Up @@ -489,7 +490,7 @@ export declare namespace PostcardCreateParams {
* A 2 page PDF file. The first page is the front of the postcard and the second
* page is the back (where the address will be stamped on).
*/
pdf: string;
pdf: string | Uploadable;

/**
* Enum representing the supported postcard sizes.
Expand Down
5 changes: 3 additions & 2 deletions src/resources/print-mail/self-mailers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../core/resource';
import type { Uploadable } from '../../core/uploads';
import * as ContactsAPI from './contacts';
import * as PrintMailAPI from './print-mail';
import * as OrderProfilesSelfMailersAPI from './order-profiles/self-mailers';
Expand Down Expand Up @@ -412,7 +413,7 @@ export declare namespace SelfMailerCreateParams {
* self-mailer and the second page is the outside (where the address will be
* stamped on).
*/
pdf: string;
pdf: string | Uploadable;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anyway for us to annotate this in our open API spec to have this auto generated? Otherwise for future or other file uploads, we will need to remember to come here and upload the types.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CalvinPostgrid In TypeSpec, looks like we already do the right thing pdf: url | bytes. Stainless’s Node generator today looks like collapses that to string in the SDK, rather than recognizing byte or file. I feel what we can do is to add a post-process script and run it after every stainless regeneration, but here will be a lot of works here I guess.


/**
* Enum representing the supported self-mailer sizes.
Expand Down Expand Up @@ -496,7 +497,7 @@ export declare namespace SelfMailerCreateParams {
* A 2 page PDF file. The first page is the inside of the self-mailer and the
* second page is the outside (where the address will be stamped on).
*/
pdf: string;
pdf: string | Uploadable;

/**
* Enum representing the supported self-mailer sizes.
Expand Down
54 changes: 54 additions & 0 deletions tests/uploads.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'fs';
import type { ResponseLike } from 'postgrid-node/internal/to-file';
import { toFile } from 'postgrid-node/core/uploads';
import { maybeMultipartFormRequestOptions } from 'postgrid-node/internal/uploads';
import { File } from 'node:buffer';

class MyClass {
Expand Down Expand Up @@ -105,3 +106,56 @@ describe('missing File error message', () => {
);
});
});

describe('maybeMultipartFormRequestOptions', () => {
it('returns opts unchanged when body has no uploadable value', async () => {
const body = {
from: 'contact_1',
to: 'contact_2',
pdf: 'https://example.com/letter.pdf',
};
const opts = { body };
const result = await maybeMultipartFormRequestOptions(opts, fetch);
expect(result).toBe(opts);
expect(result.body).toBe(body);
});

it('returns new opts with FormData body when body contains a File', async () => {
const pdfFile = new File(['fake content'], 'letter.pdf', { type: 'application/pdf' });
const body = {
from: 'contact_1',
to: 'contact_2',
pdf: pdfFile,
};
const opts = { body };
const result = await maybeMultipartFormRequestOptions(opts, fetch);
expect(result).not.toBe(opts);
expect(result.body).toBeInstanceOf(FormData);
const form = result.body as FormData;
expect(form.get('from')).toBe('contact_1');
expect(form.get('to')).toBe('contact_2');
const pdfPart = form.get('pdf');
expect(pdfPart).toBeInstanceOf(Blob);
expect((pdfPart as File).name).toBe('letter.pdf');
});

it('works with toFile(Buffer) for PDF upload path', async () => {
const buffer = Buffer.from('fake content');
const pdfFile = await toFile(buffer, 'letter.pdf');
const body = {
from: 'contact_1',
to: 'contact_2',
pdf: pdfFile,
};
const opts = { body };
const result = await maybeMultipartFormRequestOptions(opts, fetch);
expect(result).not.toBe(opts);
expect(result.body).toBeInstanceOf(FormData);
const form = result.body as FormData;
expect(form.get('from')).toBe('contact_1');
expect(form.get('to')).toBe('contact_2');
const pdfPart = form.get('pdf');
expect(pdfPart).toBeInstanceOf(Blob);
expect((pdfPart as File).name).toBe('letter.pdf');
});
});