-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from superindustries/feature/map-ast-visitor
Feature/map ast visitor
- Loading branch information
Showing
15 changed files
with
3,663 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,11 @@ jobs: | |
# Setup environment and checkout the project master | ||
- name: Setup Node.js environment | ||
uses: actions/[email protected] | ||
with: | ||
registry-url: "https://npm.pkg.github.com" | ||
scope: "@superindustries" | ||
always-auth: true | ||
|
||
- name: Checkout | ||
uses: actions/checkout@v2 | ||
|
||
|
@@ -27,6 +32,8 @@ jobs: | |
# Install and run tests | ||
- name: Install dependencies | ||
run: yarn install | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.SUPERFACE_BOT_PAT }} | ||
- name: Test | ||
run: yarn test | ||
|
||
|
@@ -36,6 +43,11 @@ jobs: | |
# Setup environment and checkout the project master | ||
- name: Setup Node.js environment | ||
uses: actions/[email protected] | ||
with: | ||
registry-url: "https://npm.pkg.github.com" | ||
scope: "@superindustries" | ||
always-auth: true | ||
|
||
- name: Checkout | ||
uses: actions/checkout@v2 | ||
|
||
|
@@ -54,6 +66,8 @@ jobs: | |
# Install and run lint | ||
- name: Install dependencies | ||
run: yarn install | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.SUPERFACE_BOT_PAT }} | ||
- name: Lint | ||
run: yarn lint | ||
|
||
|
@@ -63,6 +77,11 @@ jobs: | |
# Setup environment and checkout the project master | ||
- name: Setup Node.js environment | ||
uses: actions/[email protected] | ||
with: | ||
registry-url: "https://npm.pkg.github.com" | ||
scope: "@superindustries" | ||
always-auth: true | ||
|
||
- name: Checkout | ||
uses: actions/checkout@v2 | ||
|
||
|
@@ -81,6 +100,8 @@ jobs: | |
# Install and run license checker | ||
- name: Install dependencies | ||
run: yarn install | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.SUPERFACE_BOT_PAT }} | ||
- name: Install License checker | ||
run: | | ||
yarn global add license-checker | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
@superindustries:registry=https://npm.pkg.github.com |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { getLocal } from 'mockttp'; | ||
|
||
import { HttpClient } from './http'; | ||
|
||
const mockServer = getLocal(); | ||
|
||
describe('HttpClient', () => { | ||
beforeEach(async () => { | ||
await mockServer.start(); | ||
}); | ||
|
||
afterEach(async () => { | ||
await mockServer.stop(); | ||
}); | ||
|
||
it('gets basic response', async () => { | ||
await mockServer.get('/valid').thenJson(200, { response: 'valid' }); | ||
const url = mockServer.urlFor('/valid'); | ||
const response = await HttpClient.request(url, { | ||
method: 'get', | ||
accept: 'application/json', | ||
}); | ||
expect(response.statusCode).toEqual(200); | ||
expect(response.body).toEqual({ response: 'valid' }); | ||
}); | ||
|
||
it('gets error response', async () => { | ||
await mockServer | ||
.get('/invalid') | ||
.thenJson(404, { error: { message: 'Not found' } }); | ||
const url = mockServer.urlFor('/invalid'); | ||
const response = await HttpClient.request(url, { | ||
method: 'get', | ||
accept: 'application/json', | ||
}); | ||
expect(response.statusCode).toEqual(404); | ||
expect(response.body).toEqual({ error: { message: 'Not found' } }); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import 'isomorphic-form-data'; | ||
|
||
import fetch, { Headers } from 'cross-fetch'; | ||
|
||
import { evalScript } from '../client/interpreter/Sandbox'; | ||
import { Variables } from './interpreter/interfaces'; | ||
|
||
export interface HttpResponse { | ||
statusCode: number; | ||
body: unknown; | ||
headers: Record<string, string>; | ||
} | ||
|
||
const AUTH_HEADER_NAME = 'Authorization'; | ||
const JSON_CONTENT = 'application/json'; | ||
const URLENCODED_CONTENT = 'application/x-www-form-urlencoded'; | ||
const FORMDATA_CONTENT = 'multipart/form-data'; | ||
|
||
const variablesToStrings = (variables?: Variables): Record<string, string> => { | ||
const result: Record<string, string> = {}; | ||
|
||
if (variables) { | ||
for (const [key, value] of Object.entries(variables)) { | ||
result[key] = typeof value === 'string' ? value : JSON.stringify(value); | ||
} | ||
} | ||
|
||
return result; | ||
}; | ||
|
||
const queryParameters = (parameters?: Record<string, string>): string => { | ||
if (parameters && Object.keys(parameters).length) { | ||
return '?' + new URLSearchParams(parameters).toString(); | ||
} | ||
|
||
return ''; | ||
}; | ||
|
||
const basicAuth = (auth?: { username: string; password: string }): string => { | ||
if (!auth || !auth.username || !auth.password) { | ||
throw new Error('Missing credentials for Basic Auth!'); | ||
} | ||
|
||
return ( | ||
'Basic ' + | ||
Buffer.from(`${auth.username}:${auth.password}`).toString('base64') | ||
); | ||
}; | ||
|
||
const bearerAuth = (auth?: { token: string }): string => { | ||
if (!auth || !auth.token) { | ||
throw new Error('Missing token for Bearer Auth!'); | ||
} | ||
|
||
return `Bearer ${auth.token}`; | ||
}; | ||
|
||
const formData = (data?: Record<string, string>): FormData => { | ||
const formData = new FormData(); | ||
|
||
if (data) { | ||
Object.entries(data).forEach(([key, value]) => formData.append(key, value)); | ||
} | ||
|
||
return formData; | ||
}; | ||
|
||
const createUrl = ( | ||
inputUrl: string, | ||
parameters: { | ||
baseUrl?: string; | ||
pathParameters?: Variables; | ||
queryParameters?: Record<string, string>; | ||
} | ||
): string => { | ||
const query = queryParameters(parameters.queryParameters); | ||
const isRelative = /^\/[^/]/.test(inputUrl); | ||
|
||
if (isRelative && !parameters.baseUrl) { | ||
throw new Error('Relative URL specified, but base URL not provided!'); | ||
} | ||
|
||
let url = isRelative ? `${parameters.baseUrl}${inputUrl}` : inputUrl; | ||
|
||
if (parameters.pathParameters) { | ||
const pathParameters = Object.keys(parameters.pathParameters); | ||
const replacements: string[] = []; | ||
|
||
const regex = RegExp('{(.*?)}', 'g'); | ||
let replacement: RegExpExecArray | null; | ||
while ((replacement = regex.exec(url)) !== null) { | ||
replacements.push(replacement[1]); | ||
} | ||
|
||
const missingKeys = replacements.filter( | ||
key => !pathParameters.includes(key) | ||
); | ||
|
||
if (missingKeys.length) { | ||
throw new Error( | ||
`Values for URL replacement keys not found: ${missingKeys.join(', ')}` | ||
); | ||
} | ||
|
||
for (const param of pathParameters) { | ||
url = url.replace( | ||
`{${param}}`, | ||
evalScript(param, parameters.pathParameters) | ||
); | ||
} | ||
} | ||
|
||
return `${url}${query}`; | ||
}; | ||
|
||
export const HttpClient = { | ||
request: async ( | ||
url: string, | ||
parameters: { | ||
method: string; | ||
headers?: Variables; | ||
queryParameters?: Variables; | ||
body?: Variables; | ||
contentType?: string; | ||
accept?: string; | ||
security?: 'basic' | 'bearer' | 'other'; | ||
basic?: { username: string; password: string }; | ||
bearer?: { token: string }; | ||
baseUrl?: string; | ||
pathParameters?: Variables; | ||
} | ||
): Promise<HttpResponse> => { | ||
const headers = new Headers(variablesToStrings(parameters?.headers)); | ||
headers.append('Accept', parameters.accept ?? '*/*'); | ||
|
||
const params: RequestInit = { | ||
headers, | ||
method: parameters.method, | ||
}; | ||
|
||
if ( | ||
parameters.body && | ||
['post', 'put', 'patch'].includes(parameters.method.toLowerCase()) | ||
) { | ||
if (parameters.contentType === JSON_CONTENT) { | ||
headers.append('Content-Type', JSON_CONTENT); | ||
params.body = JSON.stringify(parameters.body); | ||
} else if (parameters.contentType === URLENCODED_CONTENT) { | ||
headers.append('Content-Type', URLENCODED_CONTENT); | ||
params.body = new URLSearchParams(variablesToStrings(parameters.body)); | ||
} else if (parameters.contentType === FORMDATA_CONTENT) { | ||
headers.append('Content-Type', FORMDATA_CONTENT); | ||
params.body = formData(variablesToStrings(parameters.body)); | ||
} else { | ||
throw new Error(`Unknown content type: ${parameters.contentType}`); | ||
} | ||
} | ||
|
||
if (parameters.security === 'basic') { | ||
headers.append(AUTH_HEADER_NAME, basicAuth(parameters.basic)); | ||
} else if (parameters.security === 'bearer') { | ||
headers.append(AUTH_HEADER_NAME, bearerAuth(parameters.bearer)); | ||
} | ||
|
||
const response = await fetch( | ||
encodeURI( | ||
createUrl(url, { | ||
baseUrl: parameters.baseUrl, | ||
pathParameters: parameters.pathParameters, | ||
queryParameters: variablesToStrings(parameters.queryParameters), | ||
}) | ||
), | ||
params | ||
); | ||
|
||
let body: unknown; | ||
|
||
if (parameters.accept === JSON_CONTENT) { | ||
body = await response.json(); | ||
} else { | ||
body = await response.text(); | ||
} | ||
|
||
const responseHeaders: Record<string, string> = {}; | ||
response.headers.forEach((key, value) => { | ||
responseHeaders[key] = value; | ||
}); | ||
|
||
return { | ||
statusCode: response.status, | ||
body, | ||
headers: responseHeaders, | ||
}; | ||
}, | ||
}; |
Oops, something went wrong.