Skip to content
Merged
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
16 changes: 16 additions & 0 deletions Agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Aspose.BarCode Cloud SDK and Codegen repository

This is repository with swagger generated SDK for Aspose.Barcode.Cloud service and code generating scripts. All sdk are submodules of this repo and located in `submodules` directory. Swagger specification of service API is located in `spec/aspose-barcode-cloud.json` file. Custom mustashe templates are located in `codegen/Templates` dir. Original templates for all languages are in <https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/> github. Sripts for generating SDK `codegen` directory. Some post processing are in Makefiles in all submodules repo.

## Common requirements for making changes in SDK code

0. Don't commit or push in repo by youreself. But you can pull and stage in git.
1. To run any scripts, use WSL if you're on Windows.
2. After making changes run tests with `make test` or similar comand in Makefile in SDK submodule repo.
3. Add changes to mustache templates after changes in SDK code are made. If you changed some code and template for it not in codegen/Templates dir. Download this template and made changes in local copy in codegen/Templates dir.
4. Enshure that generated code is the same as you new code. For it:
4.1 Stage your changes in sdk submodule repo.
4.2 Run `make <skd-name>` command in main repo. See Makefile for it.
4.3 After generating script are end it work. Enshure there is no unstaged changes in sdk submodule.
4.4 Fix templates if generated code are not the same as you new code.
5. After templates fixed, you can end your task.
233 changes: 225 additions & 8 deletions codegen/Templates/nodejs/api.mustache
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Configuration } from './Configuration';
import { HttpClient, HttpOptions, HttpResponse, HttpResult } from './httpClient';
import { Multipart, RequestFile, FormParamsType } from './multipart';
import { Multipart, RequestFile, FormParamPairs } from './multipart';

export * from './models';

Expand All @@ -9,6 +8,224 @@ import { {{#models}} {{#model}}{{classname}},{{/model}} {{/models}} } from './mo
import { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} {{operationIdCamelCase}}RequestWrapper, {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} } from './models';


type StringMap = Record<string, string>;

type ApiRequestOptions = {
uri: string;
body?: any;
encoding?: BufferEncoding | null;
form?: StringMap;
headers?: StringMap;
json?: boolean;
method?: string;
qs?: StringMap;
};

type ApiResponse = {
statusCode: number;
statusMessage: string;
headers: NodeJS.Dict<string | string[]>;
body: any;
};

type ApiResult<T = any> = {
response: ApiResponse;
body: T;
};

type ApiRejectType = {
response: ApiResponse | null;
errorResponse: ApiErrorResponse | null;
error: Error;
};

export class ApiClient {
private readonly _fetcher: typeof fetch;

constructor() {
const resolvedFetch = (globalThis as { fetch?: typeof fetch }).fetch;
if (!resolvedFetch) {
throw new Error('Global fetch API is not available. Please use Node.js 18+.');
}

this._fetcher = resolvedFetch;
}

public requestAsync(options: ApiRequestOptions): Promise<ApiResult> {
const url: URL = options.qs
? new URL(`?${new URLSearchParams(options.qs).toString()}`, options.uri)
: new URL(options.uri);

const requestBody = this.buildRequestBody(options);

const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8';

const requestOptions: RequestInit = {
method: options.method || 'GET',
headers: options.headers,
};

if (requestBody) {
requestOptions.body = requestBody;
}

return this.doFetchRequest(url, requestOptions, responseEncoding);
}

private buildRequestBody(options: ApiRequestOptions) {
let requestBody = options.body;
if (options.form) {
// Override requestBody for form with form content
requestBody = new URLSearchParams(options.form).toString();
options.headers = Object.assign(
{
'Content-Type': 'application/x-www-form-urlencoded',
},
options.headers
);
}
if (options.json) {
// Override requestBody with JSON value
requestBody = JSON.stringify(options.body);
options.headers = Object.assign(
{
'Content-Type': 'application/json',
},
options.headers
);
}
return requestBody;
}

private async doFetchRequest(
url: URL,
requestOptions: RequestInit,
responseEncoding: BufferEncoding | null
): Promise<ApiResult> {
let response: Response;
try {
response = await this._fetcher(url.toString(), requestOptions);
} catch (error) {
return Promise.reject({
response: null,
error: this.normalizeFetchError(error),
errorResponse: null,
});
}

const respBody = await this.readResponseBody(response, responseEncoding);
const responseHeaders = this.toHeaderDict(response.headers);

const httpResponse: ApiResponse = {
statusCode: response.status,
statusMessage: response.statusText,
headers: responseHeaders,
body: respBody,
};

if (response.ok) {
return {
response: httpResponse,
body: respBody,
};
}

const rejectObject: ApiRejectType = {
response: httpResponse,
error: new Error(`Error on '${url}': ${response.status} ${response.statusText}`),
errorResponse: null,
};
let errorResponse = null;
try {
errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse;
} catch (parseError) {}

if (errorResponse) {
rejectObject.errorResponse = errorResponse;
} else {
rejectObject.error.message += `. ${respBody}`;
}

return Promise.reject(rejectObject);
}

private async readResponseBody(
response: Response,
responseEncoding: BufferEncoding | null
): Promise<string | Buffer> {
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);

if (responseEncoding === null) {
return buffer;
}

return buffer.toString(responseEncoding);
}

private toHeaderDict(headers: Headers): NodeJS.Dict<string | string[]> {
const normalizedHeaders: NodeJS.Dict<string | string[]> = {};

headers.forEach((value, key) => {
const existing = normalizedHeaders[key];
if (existing === undefined) {
normalizedHeaders[key] = value;
return;
}

if (Array.isArray(existing)) {
existing.push(value);
normalizedHeaders[key] = existing;
return;
}

normalizedHeaders[key] = [existing, value];
});

return normalizedHeaders;
}

private normalizeFetchError(error: unknown): Error {
if (error instanceof Error) {
const mutableError = error as Error & { code?: string; cause?: unknown; name: string };
let normalizedCode = mutableError.code;

if (!normalizedCode) {
const cause = mutableError.cause;
if (cause && typeof cause === 'object' && 'code' in (cause as { code?: string })) {
const code = (cause as { code?: string }).code;
if (code) {
normalizedCode = String(code);
}
}
}

if (!normalizedCode) {
normalizedCode = mutableError.name || 'FETCH_ERROR';
}

try {
if (!mutableError.code) {
mutableError.code = normalizedCode;
}
} catch (assignError) {}

if (mutableError.code) {
return mutableError;
}

const wrapped = new Error(mutableError.message);
wrapped.name = mutableError.name;
(wrapped as { code?: string }).code = normalizedCode;
return wrapped;
}

const wrapped = new Error(String(error));
(wrapped as { code?: string }).code = 'FETCH_ERROR';
return wrapped;
}
}

let primitives = ['string', 'boolean', 'double', 'integer', 'long', 'float', 'number', 'any'];

class ObjectSerializer {
Expand Down Expand Up @@ -184,11 +401,11 @@ export class {{classname}} {
'x-aspose-client-version': '{{npmVersion}}'
};
protected _configuration: Configuration;
private _client: HttpClient;
private _client: ApiClient;

constructor(configuration: Configuration) {
this._configuration = configuration;
this._client = new HttpClient();
this._client = new ApiClient();
}

{{#operation}}
Expand All @@ -200,7 +417,7 @@ export class {{classname}} {
{{/summary}}
* @param request {{operationIdCamelCase}}RequestWrapper
*/
public async {{nickname}}(request: {{operationIdCamelCase}}RequestWrapper): Promise<{ response: HttpResponse; {{#returnType}}body: {{{returnType}}}; {{/returnType}}{{^returnType}}body?: any; {{/returnType}} }> {
public async {{nickname}}(request: {{operationIdCamelCase}}RequestWrapper): Promise<{ response: ApiResponse; {{#returnType}}body: {{{returnType}}}; {{/returnType}}{{^returnType}}body?: any; {{/returnType}} }> {
const requestPath =
this._configuration.getApiBaseUrl() +
'{{{path}}}'{{#pathParams}}.replace(
Expand All @@ -211,7 +428,7 @@ export class {{classname}} {
let queryParameters: any = {};
let headerParams: any = (Object as any).assign({}, this.defaultHeaders);
{{#hasFormParams}}
const formParams: FormParamsType = [];
const formParams: FormParamPairs = [];
{{/hasFormParams}}

{{#allParams}}
Expand Down Expand Up @@ -247,7 +464,7 @@ export class {{classname}} {
}
{{/isFile}}
{{/formParams}}
const requestOptions: HttpOptions = {
const requestOptions: ApiRequestOptions = {
method: '{{httpMethod}}',
qs: queryParameters,
headers: headerParams,
Expand All @@ -272,7 +489,7 @@ export class {{classname}} {

await this._configuration.authentication.applyToRequestAsync(requestOptions);

const result: HttpResult = await this._client.requestAsync(requestOptions);
const result: ApiResult = await this._client.requestAsync(requestOptions);

{{#returnType}}
return {
Expand Down
4 changes: 4 additions & 0 deletions codegen/Templates/nodejs/docs/tsconfig.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ This repository contains Aspose.BarCode Cloud SDK for Node.js source code.

To use these SDKs, you will need Client Id and Client Secret which can be looked up at [Aspose Cloud Dashboard](https://dashboard.aspose.cloud/applications) (free registration in Aspose Cloud is required for this).

## Requirements

- Node.js 18 or later (native `fetch` required).

## How to use the SDK

The complete source code is available in this repository folder. You can either directly use it in your project via source code or get [nmpjs distribution](https://www.npmjs.com/package/aspose-barcode-cloud-node) (recommended).
Expand Down
57 changes: 57 additions & 0 deletions codegen/Templates/nodejs/multipart.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import crypto from 'crypto';

type StringMap = Record<string, string>;

export interface FormParamPairs extends Array<Array<string>> {}

interface IRequestFile {
name: string;
filename: string;
data: Buffer;
contentType?: string;
}

export class RequestFile implements IRequestFile {
constructor(
readonly name: string,
readonly filename: string,
readonly data: Buffer,
readonly contentType?: string
) {}
}

export class Multipart {
readonly boundary: string;
readonly body: Buffer;
readonly headers: StringMap;

constructor(textFields: FormParamPairs, files?: IRequestFile[]) {
const random = crypto.randomUUID();
this.boundary = '------------------------' + random.replace(/-/g, '');

const bodyLines = [];
for (const tuple of textFields) {
bodyLines.push(`--${this.boundary}`);
bodyLines.push(`Content-Disposition: form-data; name="${tuple[0]}"`);
bodyLines.push('');
bodyLines.push(tuple[1]);
}
for (const file of files || []) {
bodyLines.push(`--${this.boundary}`);
bodyLines.push(
`Content-Disposition: form-data; name="${file.name}"; filename="${file.filename || 'filename'}"`
);
bodyLines.push(`Content-Type: ${file.contentType || 'application/octet-stream'}`);
bodyLines.push('');
bodyLines.push(file.data.toString('binary'));
}
bodyLines.push(`--${this.boundary}--`);

this.body = Buffer.from(bodyLines.join('\r\n'), 'binary');

this.headers = {
'Content-Type': `multipart/form-data; boundary=${this.boundary}`,
'Content-Length': this.body.length.toString(),
};
}
}
3 changes: 2 additions & 1 deletion codegen/Templates/nodejs/package.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,11 @@
"dist"
],
"engines": {
"node": ">=16"
"node": ">=18"
},
"scripts": {
"test": "npx jest",
"typecheck": "npx tsc --noEmit",
"cover": "npx jest --coverage",
"lint": "npx eslint src test snippets",
"format": "npx eslint src test snippets eslint.config.mjs --fix",
Expand Down
2 changes: 1 addition & 1 deletion submodules/node
Submodule node updated from d13cbe to 48afd9