Skip to content

Commit

Permalink
feat(core): introduce a new core package (#34)
Browse files Browse the repository at this point in the history
closes #29
  • Loading branch information
derevnjuk authored Nov 17, 2021
1 parent 4683ac0 commit cbdac1c
Show file tree
Hide file tree
Showing 46 changed files with 2,151 additions and 33 deletions.
65 changes: 65 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# @har-sdk/core

The base package can be used to import specification files (i.e. HAR, OAS and Postman Collection) and detect their type.

## Setup

```bash
npm i --save @har-sdk/core
```

## Usage

To import a specification you just need to create an instance of `SpecImporter` and call its `import` method passing the data file. The importer performs syntactic analysis and parses a provided file.

```ts
import { SpecImporter } from '@har-sdk/core';
import petstore from './petstore.collection.json';

const result = await new SpecImporter().tryToImportSpec(petstore);
console.log(result);
// {
// type: 'postman',
// format: 'json',
// doc: {
// info: {
// name: 'Swagger Petstore',
// schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
// },
// item: [
// // ...
// ]
// },
// name: 'Swagger Petstore.json'
// }
```

To configure the list of importers, you can pass them as an array to the constructor.

```ts
import { SpecImporter, HarImporter } from '@har-sdk/core';

const explorer = new SpecImporter([new HarImporter()]);
```

To extend an explorer by adding a new custom importer, you can easily implement an `Importer` interface.

```ts
import { Importer, Doc, Spec, Importer } from '@har-sdk/core';

class RamlImporter implements Importer<'raml'> {
get type(): 'raml' {
return 'raml';
}

async import(content: string): Promise<Spec<'raml'>> {
// your code

return {
// other fields
type: this.type,
format: 'yaml'
}
}
}
```
37 changes: 37 additions & 0 deletions packages/core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "@har-sdk/core",
"version": "0.0.0",
"private": false,
"description": "The base package can be used to import specification files (i.e. HAR, OAS and Postman Collection) and detect their type.",
"repository": {
"type": "git",
"url": "git+https://github.com/NeuraLegion/har-sdk.git"
},
"author": {
"name": "Artem Derevnjuk",
"email": "[email protected]"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/NeuraLegion/har-sdk/issues"
},
"publishConfig": {
"access": "public"
},
"files": [
"dist/**"
],
"keywords": [
"postman",
"oas",
"openapi",
"swagger",
"har",
"api"
],
"main": "./dist/bundle.umd.js",
"module": "./dist/bundle.esm5.js",
"es2015": "./dist/index.js",
"types": "./dist/index.d.ts",
"homepage": "https://github.com/NeuraLegion/har-sdk/tree/master/packages/core#readme",
"scripts": {
"prebuild": "npm run compile -- --clean",
"build": "npm run compile && npm run bundle",
"bundle": "rollup -c ../../rollup.config.js",
"compile": "tsc -b tsconfig.build.json",
"lint": "eslint --ignore-path ../../.eslintignore .",
"format": "prettier --ignore-path ../../.prettierignore --check **/*.ts",
"test": "cross-env NODE_ENV=test mocha tests/**/*.spec.ts",
"coverage": "nyc npm t -- --reporter xunit --reporter-options output=.mocha/testspec.xunit.xml"
},
"dependencies": {
"js-yaml": "^4.1.0",
"openapi-types": "^9.1.0",
"tslib": "~2.2.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.4"
}
}
72 changes: 72 additions & 0 deletions packages/core/src/importers/BaseImporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Importer } from './Importer';
import { DocFormat, Spec, Doc, DocType } from './Spec';
import { load } from 'js-yaml';

interface LoadedFile {
doc: unknown;
format: DocFormat;
}

export abstract class BaseImporter<
TDocType extends DocType,
TDoc = Doc<TDocType>
> implements Importer<TDocType, TDoc>
{
protected constructor() {
// noop
}

abstract get type(): TDocType;

public abstract isSupported(spec: unknown): spec is TDoc;

public async import(
content: string
): Promise<Spec<TDocType, TDoc> | undefined> {
const file: LoadedFile | undefined = this.load(content.trim());

if (file && this.isSupported(file.doc)) {
const { format, doc } = file;
const name = this.fileName({ format, doc });

return {
doc,
name,
format,
type: this.type
};
}
}

protected fileName(_: { doc: TDoc; format: DocFormat }): string | undefined {
return;
}

protected load(content: string): LoadedFile | undefined {
let doc: unknown | undefined = this.loadFromJson(content);
let format: DocFormat = 'json';

if (!doc) {
doc = this.loadFromYaml(content);
format = 'yaml';
}

return doc ? { doc, format } : undefined;
}

private loadFromJson(source: string): unknown | undefined {
try {
return JSON.parse(source);
} catch {
// noop
}
}

private loadFromYaml(source: string): unknown | undefined {
try {
return load(source, { json: true });
} catch {
// noop
}
}
}
24 changes: 24 additions & 0 deletions packages/core/src/importers/BaseOASImporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BaseImporter } from './BaseImporter';
import { OpenAPI } from '../types';
import { DocFormat } from './Spec';
import { ImporterType } from './ImporterType';

export abstract class BaseOASImporter<
TDocType extends ImporterType.OASV2 | ImporterType.OASV3
> extends BaseImporter<TDocType> {
protected constructor() {
super();
}

protected fileName({
doc,
format
}: {
doc: OpenAPI.Document;
format: DocFormat;
}): string {
return `${[doc.info.title, doc.info.version]
.map((x: string) => x.trim())
.join(' ')}.${format}`;
}
}
24 changes: 24 additions & 0 deletions packages/core/src/importers/HARImporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BaseImporter } from './BaseImporter';
import { ImporterType } from './ImporterType';
import { Har } from '../types';

export class HARImporter extends BaseImporter<ImporterType.HAR> {
private readonly SUPPORTED_HAR_VERSION = /^1\.\d+$/; // 1.x

constructor() {
super();
}

get type(): ImporterType.HAR {
return ImporterType.HAR;
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public isSupported(spec: any): spec is Har {
return !!(
spec?.log?.version &&
Array.isArray(spec.log.entries) &&
this.SUPPORTED_HAR_VERSION.test(spec.log.version)
);
}
}
5 changes: 5 additions & 0 deletions packages/core/src/importers/Importer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Doc, DocType, Spec } from './Spec';

export interface Importer<TDocType extends DocType, TDoc = Doc<TDocType>> {
import(content: string): Promise<Spec<TDocType, TDoc> | undefined>;
}
6 changes: 6 additions & 0 deletions packages/core/src/importers/ImporterType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const enum ImporterType {
HAR = 'har',
OASV3 = 'oasv3',
OASV2 = 'oasv2',
POSTMAN = 'postman'
}
20 changes: 20 additions & 0 deletions packages/core/src/importers/OASV2Importer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ImporterType } from './ImporterType';
import { OpenAPIV2 } from '../types';
import { BaseOASImporter } from './BaseOASImporter';

export class OASV2Importer extends BaseOASImporter<ImporterType.OASV2> {
private readonly SUPPORTED_SWAGGER_VERSION = '2.0';

constructor() {
super();
}

get type(): ImporterType.OASV2 {
return ImporterType.OASV2;
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public isSupported(spec: any): spec is OpenAPIV2.Document {
return spec?.swagger?.trim() === this.SUPPORTED_SWAGGER_VERSION;
}
}
22 changes: 22 additions & 0 deletions packages/core/src/importers/OASV3Importer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ImporterType } from './ImporterType';
import { OpenAPIV3 } from '../types';
import { BaseOASImporter } from './BaseOASImporter';

export class OASV3Importer extends BaseOASImporter<ImporterType.OASV3> {
private readonly SUPPORTED_OPENAPI_VERSION = /^3\.0\.\d+$/; // 3.0.x

constructor() {
super();
}

get type(): ImporterType.OASV3 {
return ImporterType.OASV3;
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public isSupported(spec: any): spec is OpenAPIV3.Document {
return !!(
spec?.openapi && this.SUPPORTED_OPENAPI_VERSION.test(spec.openapi)
);
}
}
36 changes: 36 additions & 0 deletions packages/core/src/importers/PostmanImporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { BaseImporter } from './BaseImporter';
import { ImporterType } from './ImporterType';
import { Postman } from '../types';
import { DocFormat } from './Spec';

export class PostmanImporter extends BaseImporter<ImporterType.POSTMAN> {
private readonly POSTMAN_SCHEMAS: ReadonlySet<string> = new Set([
'https://schema.getpostman.com/json/collection/v2.0.0/collection.json',
'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
]);

constructor() {
super();
}

get type(): ImporterType.POSTMAN {
return ImporterType.POSTMAN;
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public isSupported(spec: any): spec is Postman.Document {
const schemaId = spec?.info?.schema;

return this.POSTMAN_SCHEMAS.has(schemaId?.trim().toLowerCase());
}

protected fileName({
doc,
format
}: {
doc: Postman.Document;
format: DocFormat;
}): string | undefined {
return doc.info.name ? `${doc.info.name.trim()}.${format}` : undefined;
}
}
Loading

0 comments on commit cbdac1c

Please sign in to comment.