Skip to content

feat: separated http client file and services support different hosts #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
12 changes: 12 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ const program = cli({
default: codeGenBaseConfig.modular,
internal: { formatter: Boolean },
},
{
flags: '--separatedHttpClient',
description: 'separated http client file in default template',
default: codeGenBaseConfig.separatedHttpClient,
internal: { formatter: Boolean },
},
{
flags: '--specificApiBaseUrl',
description: 'api has baseUrl',
default: codeGenBaseConfig.specificApiBaseUrl,
internal: { formatter: Boolean },
},
{
flags: '--js',
description: 'generate js api module with declaration file',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"test:--another-array-type": "node tests/spec/another-array-type/test.js",
"test:--object-types": "node tests/spec/object-types/test.js",
"test:--axios--single-http-client": "node tests/spec/axiosSingleHttpClient/test.js",
"test:--axios--single-separated-http-client-and-different-hosts": "node tests/spec/axiosSingleSeparatedHttpClientAndDifferentHosts/test.js",
"test:--type-suffix--type-prefix": "node tests/spec/typeSuffixPrefix/test.js",
"test:--dot-path-params": "node tests/spec/dot-path-params/test.js",
"test:--primitive-type-constructs": "node tests/spec/primitive-type-constructs/test.js",
Expand Down
56 changes: 34 additions & 22 deletions src/code-gen-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,33 +462,45 @@ class CodeGenProcess {
* @returns {Promise<TranslatorIO[]>}
*/
createSingleFileInfo = async (templatesToRender, configuration) => {
const { generateRouteTypes, generateClient } = configuration.config;
const { fileNames, generateRouteTypes, generateClient, separatedHttpClient } = configuration.config;

return await this.createOutputFileInfo(
configuration,
configuration.fileName,
_.compact([
this.templatesWorker.renderTemplate(
templatesToRender.dataContracts,
configuration,
),
generateRouteTypes &&
this.templatesWorker.renderTemplate(
templatesToRender.routeTypes,
configuration,
),
generateClient &&
return [
...await this.createOutputFileInfo(
configuration,
configuration.fileName,
_.compact([
this.templatesWorker.renderTemplate(
templatesToRender.httpClient,
templatesToRender.dataContracts,
configuration,
),
generateClient &&
this.templatesWorker.renderTemplate(
templatesToRender.api,
generateRouteTypes &&
this.templatesWorker.renderTemplate(
templatesToRender.routeTypes,
configuration,
),
generateClient && !separatedHttpClient &&
this.templatesWorker.renderTemplate(
templatesToRender.httpClient,
configuration,
),
generateClient &&
this.templatesWorker.renderTemplate(
templatesToRender.api,
configuration,
),
]).join('\n'),
),
...(generateClient && separatedHttpClient
? await this.createOutputFileInfo(
configuration,
),
]).join('\n'),
);
fileNames.httpClient,
this.templatesWorker.renderTemplate(
templatesToRender.httpClient,
configuration,
),
)
: []),
]
};

/**
Expand Down
2 changes: 2 additions & 0 deletions src/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ class CodeGenConfig {
extraTemplates = [];
input = '';
modular = false;
separatedHttpClient = false;
specificApiBaseUrl = false;
output = '';
url = '';
cleanOutput = false;
Expand Down
11 changes: 10 additions & 1 deletion templates/default/api.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,22 @@ const descriptionLines = _.compact([
<% }) %>
*/
<% } %>
<% if (config.separatedHttpClient) { %>
import {ContentType, HttpClient, RequestParams} from './http-client';
<% } %>
export class <%~ config.apiClassName %><SecurityDataType extends unknown><% if (!config.singleHttpClient) { %> extends HttpClient<SecurityDataType> <% } %> {

<% if(config.singleHttpClient) { %>
http: HttpClient<SecurityDataType>;
<% if(config.specificApiBaseUrl) { %>
baseUrl: string;
<% } %>

constructor (http: HttpClient<SecurityDataType>) {
constructor (http: HttpClient<SecurityDataType><% if(config.specificApiBaseUrl) { %>, baseUrl = ''<% } %>) {
this.http = http;
<% if(config.specificApiBaseUrl) { %>
this.baseUrl = baseUrl;
<% } %>
}
<% } %>

Expand Down
2 changes: 1 addition & 1 deletion templates/default/procedure-call.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const describeReturnType = () => {
*/
<%~ route.routeName.usage %><%~ route.namespace ? ': ' : ' = ' %>(<%~ wrapperArgs %>)<%~ config.toJS ? `: ${describeReturnType()}` : "" %> =>
<%~ config.singleHttpClient ? 'this.http.request' : 'this.request' %><<%~ type %>, <%~ errorType %>>({
path: `<%~ path %>`,
path: `<%~ config.specificApiBaseUrl ? '${this.baseUrl}' : '' %><%~ path %>`,
method: '<%~ _.upperCase(method) %>',
<%~ queryTmpl ? `query: ${queryTmpl},` : '' %>
<%~ bodyTmpl ? `body: ${bodyTmpl},` : '' %>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/

import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, HeadersDefaults, ResponseType } from "axios";
import axios from "axios";

export type QueryParamsType = Record<string | number, any>;

export interface FullRequestParams extends Omit<AxiosRequestConfig, "data" | "params" | "url" | "responseType"> {
/** set parameter to `true` for call `securityWorker` for this request */
secure?: boolean;
/** request path */
path: string;
/** content type of request body */
type?: ContentType;
/** query params */
query?: QueryParamsType;
/** format of response (i.e. response.json() -> format: "json") */
format?: ResponseType;
/** request body */
body?: unknown;
}

export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">;

export interface ApiConfig<SecurityDataType = unknown> extends Omit<AxiosRequestConfig, "data" | "cancelToken"> {
securityWorker?: (
securityData: SecurityDataType | null,
) => Promise<AxiosRequestConfig | void> | AxiosRequestConfig | void;
secure?: boolean;
format?: ResponseType;
}

export enum ContentType {
Json = "application/json",
FormData = "multipart/form-data",
UrlEncoded = "application/x-www-form-urlencoded",
Text = "text/plain",
}

export class HttpClient<SecurityDataType = unknown> {
public instance: AxiosInstance;
private securityData: SecurityDataType | null = null;
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
private secure?: boolean;
private format?: ResponseType;

constructor({ securityWorker, secure, format, ...axiosConfig }: ApiConfig<SecurityDataType> = {}) {
this.instance = axios.create({ ...axiosConfig, baseURL: axiosConfig.baseURL || "http://petstore.swagger.io/api" });
this.secure = secure;
this.format = format;
this.securityWorker = securityWorker;
}

public setSecurityData = (data: SecurityDataType | null) => {
this.securityData = data;
};

protected mergeRequestParams(params1: AxiosRequestConfig, params2?: AxiosRequestConfig): AxiosRequestConfig {
const method = params1.method || (params2 && params2.method);

return {
...this.instance.defaults,
...params1,
...(params2 || {}),
headers: {
...((method && this.instance.defaults.headers[method.toLowerCase() as keyof HeadersDefaults]) || {}),
...(params1.headers || {}),
...((params2 && params2.headers) || {}),
},
};
}

protected stringifyFormItem(formItem: unknown) {
if (typeof formItem === "object" && formItem !== null) {
return JSON.stringify(formItem);
} else {
return `${formItem}`;
}
}

protected createFormData(input: Record<string, unknown>): FormData {
return Object.keys(input || {}).reduce((formData, key) => {
const property = input[key];
const propertyContent: any[] = property instanceof Array ? property : [property];

for (const formItem of propertyContent) {
const isFileType = formItem instanceof Blob || formItem instanceof File;
formData.append(key, isFileType ? formItem : this.stringifyFormItem(formItem));
}

return formData;
}, new FormData());
}

public request = async <T = any, _E = any>({
secure,
path,
type,
query,
format,
body,
...params
}: FullRequestParams): Promise<AxiosResponse<T>> => {
const secureParams =
((typeof secure === "boolean" ? secure : this.secure) &&
this.securityWorker &&
(await this.securityWorker(this.securityData))) ||
{};
const requestParams = this.mergeRequestParams(params, secureParams);
const responseFormat = format || this.format || undefined;

if (type === ContentType.FormData && body && body !== null && typeof body === "object") {
body = this.createFormData(body as Record<string, unknown>);
}

if (type === ContentType.Text && body && body !== null && typeof body !== "string") {
body = JSON.stringify(body);
}

return this.instance.request({
...requestParams,
headers: {
...(requestParams.headers || {}),
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
},
params: query,
responseType: responseFormat,
data: body,
url: path,
});
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/

export interface Pet {
/** @format int64 */
id: number;
name: string;
tag?: string;
multiple?: string | number;
}

/**
* @title Swagger Petstore
* @version 1.0.0
* @license MIT
* @termsOfService http://swagger.io/terms/
* @baseUrl http://petstore.swagger.io/api
* @contact Swagger API Team
*
* A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification
*/
import { HttpClient, RequestParams } from "./http-client";
export class Api<SecurityDataType extends unknown> {
http: HttpClient<SecurityDataType>;
baseUrl: string;

constructor(http: HttpClient<SecurityDataType>, baseUrl = "") {
this.http = http;
this.baseUrl = baseUrl;
}

pets = {
/**
* @description Returns all pets from the system that the user has access to
*
* @name PetsList
* @request GET:/pets
*/
petsList: (params: RequestParams = {}) =>
this.http.request<Pet[], any>({
path: `${this.baseUrl}/pets`,
method: "GET",
format: "json",
...params,
}),
};
}
Loading