Skip to content

Commit

Permalink
add request.formData support to adapter plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
thescientist13 committed Aug 26, 2023
1 parent c580243 commit 7a68bcc
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 22 deletions.
33 changes: 27 additions & 6 deletions packages/plugin-adapter-netlify/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,40 @@ import { zip } from 'zip-a-folder';

// https://docs.netlify.com/functions/create/?fn-language=js
function generateOutputFormat(id) {
const variableNameSafeId = id.replace(/-/g, '');

return `
import { handler as ${id} } from './__${id}.js';
import { handler as ${variableNameSafeId} } from './__${id}.js';
export async function handler (event, context = {}) {
const { rawUrl, body, headers, httpMethod } = event;
const { rawUrl, body, headers = {}, httpMethod } = event;
const contentType = headers['content-type'] || '';
let format = body;
if (['GET', 'HEAD'].includes(httpMethod.toUpperCase())) {
format = null
} else if (contentType.includes('application/x-www-form-urlencoded')) {
const formData = new FormData();
for (const key of Object.keys(body)) {
formData.append(key, body[key]);
}
// when using FormData, let Request set the correct headers
// or else it will come out as multipart/form-data
// https://stackoverflow.com/a/43521052/417806
format = formData;
delete headers['content-type'];
} else if(contentType.includes('application/json')) {
format = JSON.stringify(body);
}
const request = new Request(rawUrl, {
body: ['GET', 'HEAD'].includes(httpMethod.toUpperCase())
? null
: JSON.stringify(body),
body: format,
method: httpMethod,
headers: new Headers(headers)
});
const response = await ${id}(request, context);
const response = await ${variableNameSafeId}(request, context);
return {
statusCode: response.status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ describe('Build Greenwood With: ', function() {
});

it('should output the expected number of serverless function zip files', function() {
expect(zipFiles.length).to.be.equal(5);
expect(zipFiles.length).to.be.equal(6);
});

it('should output the expected number of serverless function API zip files', function() {
expect(zipFiles.filter(file => path.basename(file).startsWith('api-')).length).to.be.equal(3);
expect(zipFiles.filter(file => path.basename(file).startsWith('api-')).length).to.be.equal(4);
});

it('should output the expected number of serverless function SSR page zip files', function() {
Expand Down Expand Up @@ -156,11 +156,11 @@ describe('Build Greenwood With: ', function() {
});
});

describe('Submit API Route adapter', function() {
describe('Submit JSON API Route adapter', function() {
let apiFunctions;

before(async function() {
apiFunctions = await glob.promise(path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), 'api-submit.zip'));
apiFunctions = await glob.promise(path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), 'api-submit-json.zip'));
});

it('should output one API route as a serverless function zip file', function() {
Expand All @@ -176,7 +176,7 @@ describe('Build Greenwood With: ', function() {
});
const { handler } = await import(new URL(`./${name}/${name}.js`, netlifyFunctionsOutputUrl));
const response = await handler({
rawUrl: 'http://localhost:8080/api/submit',
rawUrl: 'http://localhost:8080/api/submit-json',
body: { name: param },
httpMethod: 'POST',
headers: {
Expand All @@ -192,6 +192,41 @@ describe('Build Greenwood With: ', function() {
});
});

describe('Submit FormData API Route adapter', function() {
let apiFunctions;

before(async function() {
apiFunctions = await glob.promise(path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), 'api-submit-form-data.zip'));
});

it('should output one API route as a serverless function zip file', function() {
expect(apiFunctions.length).to.be.equal(1);
});

it('should return the expected response when the serverless adapter entry point handler is invoked', async function() {
const param = 'Greenwood';
const name = path.basename(apiFunctions[0]).replace('.zip', '');

await extract(apiFunctions[0], {
dir: path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), name)
});
const { handler } = await import(new URL(`./${name}/${name}.js`, netlifyFunctionsOutputUrl));
const response = await handler({
rawUrl: 'http://localhost:8080/api/submit-form-data',
body: { name: param },
httpMethod: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded'
}
}, {});
const { statusCode, body, headers } = response;

expect(statusCode).to.be.equal(200);
expect(body).to.be.equal(`Thank you ${param} for your submission!`);
expect(headers.get('Content-Type')).to.be.equal('text/html');
});
});

describe('Artists SSR Page adapter', function() {
const count = 2;
let pageFunctions;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export async function handler(request) {
const formData = await request.formData();
const name = formData.get('name');
const body = `Thank you ${name} for your submission!`;

return new Response(body, {
headers: new Headers({
'Content-Type': 'text/html'
})
});
}
32 changes: 26 additions & 6 deletions packages/plugin-adapter-vercel/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,43 @@ import { checkResourceExists } from '@greenwood/cli/src/lib/resource-utils.js';

// https://vercel.com/docs/functions/serverless-functions/runtimes/node-js#node.js-helpers
function generateOutputFormat(id, type) {
const variableNameSafeId = id.replace(/-/g, '');
const path = type === 'page'
? `__${id}`
: id;

return `
import { handler as ${id} } from './${path}.js';
import { handler as ${variableNameSafeId} } from './${path}.js';
export default async function handler (request, response) {
const { body, url, headers, method } = request;
const { body, url, headers = {}, method } = request;
const contentType = headers['content-type'] || '';
let format = body;
if (['GET', 'HEAD'].includes(method.toUpperCase())) {
format = null
} else if (contentType.includes('application/x-www-form-urlencoded')) {
const formData = new FormData();
for (const key of Object.keys(body)) {
formData.append(key, body[key]);
}
// when using FormData, let Request set the correct headers
// or else it will come out as multipart/form-data
// https://stackoverflow.com/a/43521052/417806
format = formData;
delete headers['content-type'];
} else if(contentType.includes('application/json')) {
format = JSON.stringify(body);
}
const req = new Request(new URL(url, \`http://\${headers.host}\`), {
body: ['GET', 'HEAD'].includes(method.toUpperCase())
? null
: JSON.stringify(body),
body: format,
headers: new Headers(headers),
method
});
const res = await ${id}(req);
const res = await ${variableNameSafeId}(req);
res.headers.forEach((value, key) => {
response.setHeader(key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('Build Greenwood With: ', function() {
});

it('should output the expected number of serverless function output folders', function() {
expect(functionFolders.length).to.be.equal(5);
expect(functionFolders.length).to.be.equal(6);
});

it('should output the expected configuration file for the build output', function() {
Expand Down Expand Up @@ -181,19 +181,20 @@ describe('Build Greenwood With: ', function() {
});
});

describe('Submit API Route adapter', function() {
describe('Submit JSON API Route adapter', function() {
const name = 'Greenwood';

it('should return the expected response when the serverless adapter entry point handler is invoked', async function() {
const handler = (await import(new URL('./api/submit.func/index.js', vercelFunctionsOutputUrl))).default;
const handler = (await import(new URL('./api/submit-json.func/index.js', vercelFunctionsOutputUrl))).default;
const response = {
headers: new Headers()
};

await handler({
url: 'http://localhost:8080/api/submit',
url: 'http://localhost:8080/api/submit-json',
headers: {
host: 'http://localhost:8080'
'host': 'http://localhost:8080',
'content-type': 'application/json'
},
body: { name },
method: 'POST'
Expand All @@ -217,6 +218,42 @@ describe('Build Greenwood With: ', function() {
});
});

describe('Submit FormData JSON API Route adapter', function() {
const name = 'Greenwood';

it('should return the expected response when the serverless adapter entry point handler is invoked', async function() {
const handler = (await import(new URL('./api/submit-form-data.func/index.js', vercelFunctionsOutputUrl))).default;
const response = {
headers: new Headers()
};

await handler({
url: 'http://localhost:8080/api/submit-form-data',
headers: {
'host': 'http://localhost:8080',
'content-type': 'application/x-www-form-urlencoded'
},
body: { name },
method: 'POST'
}, {
status: function(code) {
response.status = code;
},
send: function(body) {
response.body = body;
},
setHeader: function(key, value) {
response.headers.set(key, value);
}
});
const { status, body, headers } = response;

expect(status).to.be.equal(200);
expect(body).to.be.equal(`Thank you ${name} for your submission!`);
expect(headers.get('Content-Type')).to.be.equal('text/html');
});
});

describe('Artists SSR Page adapter', function() {
it('should return the expected response when the serverless adapter entry point handler is invoked', async function() {
const handler = (await import(new URL('./artists.func/index.js', vercelFunctionsOutputUrl))).default;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export async function handler(request) {
const formData = await request.formData();
const name = formData.get('name');
const body = `Thank you ${name} for your submission!`;

return new Response(body, {
headers: new Headers({
'Content-Type': 'text/html'
})
});
}

0 comments on commit 7a68bcc

Please sign in to comment.