diff --git a/bin/index.js b/bin/index.js index 99605b78..8f5ded0d 100755 --- a/bin/index.js +++ b/bin/index.js @@ -12,7 +12,7 @@ program .version(pkg.version) .requiredOption('-i, --input ', 'OpenAPI specification, can be a path, url or string content (required)') .requiredOption('-o, --output ', 'Output directory (required)') - .option('-c, --client ', 'HTTP client to generate [fetch, xhr, node]', 'fetch') + .option('-c, --client ', 'HTTP client to generate [fetch, xhr, node, httpntlm]', 'fetch') .option('--useOptions', 'Use options instead of arguments') .option('--useUnionTypes', 'Use union types instead of enums') .option('--exportCore ', 'Write core files to disk', true) diff --git a/package.json b/package.json index b35bffc9..6ed8254d 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "express": "4.17.1", "form-data": "3.0.0", "glob": "7.1.6", + "httpntlm": "1.7.6", "jest": "26.6.3", "jest-cli": "26.6.3", "node-fetch": "2.6.1", diff --git a/src/index.ts b/src/index.ts index c098715c..59a1a216 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ export enum HttpClient { FETCH = 'fetch', XHR = 'xhr', NODE = 'node', + HTTPNTLM = 'httpntlm', } export type Options = { diff --git a/src/templates/core/OpenAPI.hbs b/src/templates/core/OpenAPI.hbs index 270f0bfc..fc9045b2 100644 --- a/src/templates/core/OpenAPI.hbs +++ b/src/templates/core/OpenAPI.hbs @@ -10,6 +10,7 @@ type Config = { TOKEN?: string | Resolver; USERNAME?: string | Resolver; PASSWORD?: string | Resolver; + DOMAIN?: string | Resolver; HEADERS?: Headers | Resolver; } @@ -20,5 +21,6 @@ export const OpenAPI: Config = { TOKEN: undefined, USERNAME: undefined, PASSWORD: undefined, + DOMAIN: undefined, HEADERS: undefined, }; diff --git a/src/templates/core/httpntlm/getHeaders.hbs b/src/templates/core/httpntlm/getHeaders.hbs new file mode 100644 index 00000000..e4ee40b2 --- /dev/null +++ b/src/templates/core/httpntlm/getHeaders.hbs @@ -0,0 +1,31 @@ +async function getHeaders(options: ApiRequestOptions): Promise { + const headers = new Headers({ + Accept: 'application/json', + ...OpenAPI.HEADERS, + ...options.headers, + }); + + const token = await resolve(OpenAPI.TOKEN); + const username = await resolve(OpenAPI.USERNAME); + const password = await resolve(OpenAPI.PASSWORD); + + if (isStringWithValue(token)) { + headers.append('Authorization', `Bearer ${token}`); + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = Buffer.from(`${username}:${password}`).toString('base64'); + headers.append('Authorization', `Basic ${credentials}`); + } + + if (options.body) { + if (isBinary(options.body)) { + headers.append('Content-Type', 'application/octet-stream'); + } else if (isString(options.body)) { + headers.append('Content-Type', 'text/plain'); + } else { + headers.append('Content-Type', 'application/json'); + } + } + return headers; +} diff --git a/src/templates/core/httpntlm/getRequestBody.hbs b/src/templates/core/httpntlm/getRequestBody.hbs new file mode 100644 index 00000000..0977770c --- /dev/null +++ b/src/templates/core/httpntlm/getRequestBody.hbs @@ -0,0 +1,13 @@ +function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { + if (options.formData) { + return getFormData(options.formData); + } + if (options.body) { + if (isString(options.body) || isBinary(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +} diff --git a/src/templates/core/httpntlm/getResponseBody.hbs b/src/templates/core/httpntlm/getResponseBody.hbs new file mode 100644 index 00000000..502979ef --- /dev/null +++ b/src/templates/core/httpntlm/getResponseBody.hbs @@ -0,0 +1,16 @@ +async function getResponseBody(response: Response): Promise { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const isJSON = contentType.toLowerCase().startsWith('application/json'); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + return null; +} diff --git a/src/templates/core/httpntlm/getResponseHeader.hbs b/src/templates/core/httpntlm/getResponseHeader.hbs new file mode 100644 index 00000000..ce326a71 --- /dev/null +++ b/src/templates/core/httpntlm/getResponseHeader.hbs @@ -0,0 +1,9 @@ +function getResponseHeader(response: Response, responseHeader?: string): string | null { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return null; +} diff --git a/src/templates/core/httpntlm/request.hbs b/src/templates/core/httpntlm/request.hbs new file mode 100644 index 00000000..0edc3d09 --- /dev/null +++ b/src/templates/core/httpntlm/request.hbs @@ -0,0 +1,76 @@ +{{>header}} + +import FormData from 'form-data'; +import fetch, { BodyInit, Headers, RequestInit, Response } from 'node-fetch'; +import { types } from 'util'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { OpenAPI } from './OpenAPI'; + +{{>functions/isDefined}} + + +{{>functions/isString}} + + +{{>functions/isStringWithValue}} + + +{{>functions/isBinary}} + + +{{>functions/getQueryString}} + + +{{>functions/getUrl}} + + +{{>functions/getFormData}} + + +{{>functions/resolve}} + + +{{>httpntlm/getHeaders}} + + +{{>httpntlm/getRequestBody}} + + +{{>httpntlm/sendRequest}} + + +{{>httpntlm/getResponseHeader}} + + +{{>httpntlm/getResponseBody}} + + +{{>functions/catchErrors}} + + +/** +* Request using node-fetch client +* @param options The request options from the the service +* @result ApiResult +* @throws ApiError +*/ +export async function request(options: ApiRequestOptions): Promise { + const url = getUrl(options); + const response = await sendRequest(options, url); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; + + catchErrors(options, result); + return result; +} diff --git a/src/templates/core/httpntlm/sendRequest.hbs b/src/templates/core/httpntlm/sendRequest.hbs new file mode 100644 index 00000000..e0a296e0 --- /dev/null +++ b/src/templates/core/httpntlm/sendRequest.hbs @@ -0,0 +1,8 @@ +async function sendRequest(options: ApiRequestOptions, url: string): Promise { + const request: RequestInit = { + method: options.method, + headers: await getHeaders(options), + body: getRequestBody(options), + }; + return await fetch(url, request); +} diff --git a/src/templates/core/request.hbs b/src/templates/core/request.hbs index 2a32097e..f3960955 100644 --- a/src/templates/core/request.hbs +++ b/src/templates/core/request.hbs @@ -1,3 +1,4 @@ {{#equals @root.httpClient 'fetch'}}{{>fetch/request}}{{/equals}} {{#equals @root.httpClient 'xhr'}}{{>xhr/request}}{{/equals}} {{#equals @root.httpClient 'node'}}{{>node/request}}{{/equals}} +{{#equals @root.httpClient 'httpntml'}}{{>httpntml/request}}{{/equals}} diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index 6772eebd..11d040be 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -26,6 +26,12 @@ import nodeGetResponseBody from '../templates/core/node/getResponseBody.hbs'; import nodeGetResponseHeader from '../templates/core/node/getResponseHeader.hbs'; import nodeRequest from '../templates/core/node/request.hbs'; import nodeSendRequest from '../templates/core/node/sendRequest.hbs'; +import httpntlmGetHeaders from '../templates/core/httpntlm/getHeaders.hbs'; +import httpntlmGetRequestBody from '../templates/core/httpntlm/getRequestBody.hbs'; +import httpntlmGetResponseBody from '../templates/core/httpntlm/getResponseBody.hbs'; +import httpntlmGetResponseHeader from '../templates/core/httpntlm/getResponseHeader.hbs'; +import httpntlmRequest from '../templates/core/httpntlm/request.hbs'; +import httpntlmSendRequest from '../templates/core/httpntlm/sendRequest.hbs'; import templateCoreSettings from '../templates/core/OpenAPI.hbs'; import templateCoreRequest from '../templates/core/request.hbs'; import xhrGetHeaders from '../templates/core/xhr/getHeaders.hbs'; @@ -173,5 +179,13 @@ export function registerHandlebarTemplates(): Templates { Handlebars.registerPartial('node/sendRequest', Handlebars.template(nodeSendRequest)); Handlebars.registerPartial('node/request', Handlebars.template(nodeRequest)); + // Specific files for the node client implementation + Handlebars.registerPartial('httpntlm/getHeaders', Handlebars.template(httpntlmGetHeaders)); + Handlebars.registerPartial('httpntlm/getRequestBody', Handlebars.template(httpntlmGetRequestBody)); + Handlebars.registerPartial('httpntlm/getResponseBody', Handlebars.template(httpntlmGetResponseBody)); + Handlebars.registerPartial('httpntlm/getResponseHeader', Handlebars.template(httpntlmGetResponseHeader)); + Handlebars.registerPartial('httpntlm/sendRequest', Handlebars.template(httpntlmSendRequest)); + Handlebars.registerPartial('httpntlm/request', Handlebars.template(httpntlmRequest)); + return templates; } diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap index 5f230fc2..58fb9a89 100644 --- a/test/__snapshots__/index.spec.js.snap +++ b/test/__snapshots__/index.spec.js.snap @@ -67,6 +67,7 @@ type Config = { TOKEN?: string | Resolver; USERNAME?: string | Resolver; PASSWORD?: string | Resolver; + DOMAIN?: string | Resolver; HEADERS?: Headers | Resolver; } @@ -77,6 +78,7 @@ export const OpenAPI: Config = { TOKEN: undefined, USERNAME: undefined, PASSWORD: undefined, + DOMAIN: undefined, HEADERS: undefined, };" `; @@ -284,6 +286,7 @@ export async function request(options: ApiRequestOptions): Promise { return result; } + " `; @@ -2310,6 +2313,7 @@ type Config = { TOKEN?: string | Resolver; USERNAME?: string | Resolver; PASSWORD?: string | Resolver; + DOMAIN?: string | Resolver; HEADERS?: Headers | Resolver; } @@ -2320,6 +2324,7 @@ export const OpenAPI: Config = { TOKEN: undefined, USERNAME: undefined, PASSWORD: undefined, + DOMAIN: undefined, HEADERS: undefined, };" `; @@ -2527,6 +2532,7 @@ export async function request(options: ApiRequestOptions): Promise { return result; } + " `; diff --git a/test/index.js b/test/index.js index 7c88334b..fa0017ee 100644 --- a/test/index.js +++ b/test/index.js @@ -6,7 +6,7 @@ async function generateV2() { await OpenAPI.generate({ input: './test/spec/v2.json', output: './test/generated/v2/', - httpClient: OpenAPI.HttpClient.FETCH, + httpClient: OpenAPI.HttpClient.HTTPNTLM, useOptions: false, useUnionTypes: false, exportCore: true, @@ -20,7 +20,7 @@ async function generateV3() { await OpenAPI.generate({ input: './test/spec/v3.json', output: './test/generated/v3/', - httpClient: OpenAPI.HttpClient.FETCH, + httpClient: OpenAPI.HttpClient.HTTPNTLM, useOptions: false, useUnionTypes: false, exportCore: true, diff --git a/types/index.d.ts b/types/index.d.ts index e1829b2d..5259b4ca 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -2,6 +2,7 @@ export declare enum HttpClient { FETCH = 'fetch', XHR = 'xhr', NODE = 'node', + HTTPNTLM = 'httpntlm', } export type Options = { diff --git a/yarn.lock b/yarn.lock index 95f60bc1..a93d5730 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3164,6 +3164,19 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +httpntlm@1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/httpntlm/-/httpntlm-1.7.6.tgz#6991e8352836007d67101b83db8ed0f915f906d0" + integrity sha1-aZHoNSg2AH1nEBuD247Q+RX5BtA= + dependencies: + httpreq ">=0.4.22" + underscore "~1.7.0" + +httpreq@>=0.4.22: + version "0.4.24" + resolved "https://registry.yarnpkg.com/httpreq/-/httpreq-0.4.24.tgz#4335ffd82cd969668a39465c929ac61d6393627f" + integrity sha1-QzX/2CzZaWaKOUZckprGHWOTYn8= + https-proxy-agent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" @@ -5679,6 +5692,11 @@ unbzip2-stream@^1.3.3: buffer "^5.2.1" through "^2.3.8" +underscore@~1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" + integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk= + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"