Skip to content
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

feat: add basePath and schemes to custom config #32

Merged
merged 3 commits into from
Feb 8, 2022
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ custom:
swaggerPath?: 'string'
apiKeyName?: 'string'
useStage?: true | false
basePath?: '/string'
schemes?: ['http', 'https', 'ws', 'wss']
```

`generateSwaggerOnDeploy` is a boolean which decides whether to generate a new swagger file on deployment. Default is `true`.
Expand All @@ -52,6 +54,10 @@ custom:

`useStage` is a bool to either use current stage in beginning of path or not. The Default is `false`. For example, if you use it enabled (`true`) and your stage is `dev` the swagger will be in `dev/swagger`

`basePath` is an optional string that can be prepended to every request (i.e. `http://localhost/basePath/my-endpoint`). Should include leading `/`.

`schemes` is an optional array (containing one of `http`, `https`, `ws`, or `wss`) for specifying schemes. If not specified, uses the same scheme as the API specification (reflecting Swagger's behavior)

## Adding more details

The default swagger file from vanilla Serverless framework will have the correct paths and methods but no details about the requests or responses.
Expand Down
56 changes: 30 additions & 26 deletions src/ServerlessAutoSwagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class ServerlessAutoSwagger {
title: '',
version: '1',
},
schemes: ['https'],
paths: {},
definitions: {},
securityDefinitions: {},
Expand Down Expand Up @@ -160,35 +159,40 @@ class ServerlessAutoSwagger {
this.addEndpointsAndLambda();
};

gatherSwaggerFiles = async () => {
const swaggerFiles = this.serverless.service.custom?.autoswagger?.swaggerFiles;
/** Updates this.swagger with serverless custom.autoswagger overrides */
gatherSwaggerOverrides = (): void => {
const autoswagger = this.serverless.service.custom?.autoswagger ?? {};

if (!swaggerFiles || swaggerFiles.length < 1) {
return;
}
if (autoswagger.basePath) this.swagger.basePath = autoswagger.basePath
if (autoswagger.schemes) this.swagger.schemes = autoswagger.schemes

await Promise.all(
swaggerFiles.map(async (filepath) => {
const fileData = fs.readFileSync(filepath, 'utf8');
// There must be at least one or this `if` will be false
if (autoswagger.swaggerFiles?.length) this.gatherSwaggerFiles(autoswagger.swaggerFiles)

const jsonData = JSON.parse(fileData);
}

const { paths = {}, definitions = {}, ...swagger } = jsonData;
/** Updates this.swagger with swagger file overrides */
gatherSwaggerFiles = (swaggerFiles: string[]): void => {
swaggerFiles.forEach((filepath) => {
const fileData = fs.readFileSync(filepath, 'utf8');

this.swagger = {
...this.swagger,
...swagger,
paths: {
...this.swagger.paths,
...paths,
},
definitions: {
...this.swagger.definitions,
...definitions,
},
};
})
);
const jsonData = JSON.parse(fileData);

const { paths = {}, definitions = {}, ...swagger } = jsonData;

this.swagger = {
...this.swagger,
...swagger,
paths: {
...this.swagger.paths,
...paths,
},
definitions: {
...this.swagger.definitions,
...definitions,
},
};
});
};

gatherTypes = async () => {
Expand Down Expand Up @@ -259,7 +263,7 @@ class ServerlessAutoSwagger {
};

generateSwagger = async () => {
await this.gatherSwaggerFiles();
this.gatherSwaggerOverrides();
await this.gatherTypes();
this.generateSecurity();
this.generatePaths();
Expand Down
7 changes: 6 additions & 1 deletion src/serverlessPlugin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface Serverless {
provider?: {
stage?: string
}
}
},
configSchemaHandler: {
defineCustomProperties(schema: unknown): void
defineFunctionEvent(
Expand All @@ -30,6 +30,9 @@ export interface Serverless {
}
}

// ws and wss are WebSocket schemas
type SwaggerScheme = 'http' | 'https' | 'ws' | 'wss'

export interface AutoSwaggerCustomConfig {
autoswagger?: {
apiKeyName?: string
Expand All @@ -38,6 +41,8 @@ export interface AutoSwaggerCustomConfig {
typefiles?: string[]
useStage?: boolean
swaggerPath?: string
basePath?: string
schemes?: SwaggerScheme[]
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/swagger.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface Swagger {
host?: string
basePath?: string
tags?: Tag[]
schemes: string[]
schemes?: string[]
paths: Paths
securityDefinitions?: Record<string, SecurityDefinition>
definitions?: Record<string, Definition>
Expand Down
136 changes: 101 additions & 35 deletions tests/ServerlessAutoSwagger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,31 @@ const generateServerlessFromAnEndpoint = (
}

describe("ServerlessAutoSwagger", () => {
const mockedJsonFiles = new Map<string, string>()

jest
.spyOn<typeof fs, 'readFileSync'>(fs, "readFileSync")
.mockImplementation((fileName: PathOrFileDescriptor): string => {
const content = mockedJsonFiles.get(fileName as string)

if (!content) {
throw new Error(`file ${fileName} not mocked`)
}

return content
})

const mockJsonFile = (
fileName: string,
content: Record<string, unknown>
): void => {
mockedJsonFiles.set(fileName, JSON.stringify(content))
}

beforeEach(() => {
mockedJsonFiles.clear()
})

describe("generatePaths", () => {
it("should generate minimal endpoint", () => {
const serverlessAutoSwagger = new ServerlessAutoSwagger(
Expand Down Expand Up @@ -413,7 +438,7 @@ describe("ServerlessAutoSwagger", () => {
expect(serverlessAutoSwagger.swagger.paths).toEqual({})
})

it("should add path without remove existings", () => {
it("should add path without remove existing", () => {
const serverlessAutoSwagger = new ServerlessAutoSwagger(
generateServerlessFromAnEndpoint([
{
Expand Down Expand Up @@ -469,39 +494,83 @@ describe("ServerlessAutoSwagger", () => {
})
})

describe("gatherSwaggerFiles", () => {
const mockedJsonFiles = new Map<string, string>()

jest
.spyOn(fs, "readFileSync")
.mockImplementation((fileName: PathOrFileDescriptor): string => {
const content = mockedJsonFiles.get(fileName as string)
describe('gatherSwaggerOverrides', () => {
it('should use defaults if overrides are not specified', () => {
const serverlessAutoSwagger = new ServerlessAutoSwagger(
generateServerlessFromAnEndpoint(
[
{
http: {
path: "hello",
method: "post",
exclude: true,
},
},
],
),
{}
)

if (!content) {
throw new Error(`file ${fileName} not mocked`)
}
serverlessAutoSwagger.gatherSwaggerOverrides();

return content
expect(serverlessAutoSwagger.swagger).toEqual({
definitions: expect.any(Object),
info: expect.any(Object),
paths: expect.any(Object),
securityDefinitions: expect.any(Object),
swagger: '2.0'
})
});

const mockJsonFile = (
fileName: string,
content: Record<string, unknown>
): void => {
mockedJsonFiles.set(fileName, JSON.stringify(content))
}
it('should use overrides if specified', () => {
const fileName = "test.json";
const swaggerDoc = { foo: { bar: true } };
mockJsonFile(fileName, swaggerDoc)

beforeEach(() => {
mockedJsonFiles.clear()
})
const serverlessAutoSwagger = new ServerlessAutoSwagger(
generateServerlessFromAnEndpoint(
[
{
http: {
path: "hello",
method: "post",
exclude: true,
},
},
],
{
basePath: '/bp',
schemes: ['ws'],
swaggerFiles: [fileName]
}
),
{}
)

serverlessAutoSwagger.gatherSwaggerOverrides();

expect(serverlessAutoSwagger.swagger).toEqual({
definitions: expect.any(Object),
info: expect.any(Object),
paths: expect.any(Object),
securityDefinitions: expect.any(Object),
swagger: '2.0',
schemes: ['ws'],
basePath: '/bp',
...swaggerDoc
})
});
});

describe("gatherSwaggerFiles", () => {
it("should add additionalProperties", async () => {
mockJsonFile("test.json", {
foo: {
bar: true,
},
})

const swaggerFiles = ["test.json"]
const serverlessAutoSwagger = new ServerlessAutoSwagger(
generateServerlessFromAnEndpoint(
[
Expand All @@ -513,19 +582,16 @@ describe("ServerlessAutoSwagger", () => {
},
},
],
{
swaggerFiles: ["test.json"],
}
{ swaggerFiles }
),
{}
)

await serverlessAutoSwagger.gatherSwaggerFiles()
await serverlessAutoSwagger.gatherSwaggerFiles(swaggerFiles)

expect(serverlessAutoSwagger.swagger).toEqual({
swagger: "2.0",
info: { title: "", version: "1" },
schemes: ["https"],
paths: {},
definitions: {},
securityDefinitions: {},
Expand All @@ -537,6 +603,8 @@ describe("ServerlessAutoSwagger", () => {
mockJsonFile("test.json", {
schemes: ["http"],
})

const swaggerFiles = ["test.json"];
const serverlessAutoSwagger = new ServerlessAutoSwagger(
generateServerlessFromAnEndpoint(
[
Expand All @@ -548,14 +616,12 @@ describe("ServerlessAutoSwagger", () => {
},
},
],
{
swaggerFiles: ["test.json"],
}
{ swaggerFiles }
),
{}
)

await serverlessAutoSwagger.gatherSwaggerFiles()
await serverlessAutoSwagger.gatherSwaggerFiles(swaggerFiles)

expect(serverlessAutoSwagger.swagger).toEqual({
swagger: "2.0",
Expand All @@ -579,6 +645,7 @@ describe("ServerlessAutoSwagger", () => {
},
},
})

mockJsonFile("helloworld.json", {
paths: {
"/hello": "world",
Expand All @@ -592,6 +659,8 @@ describe("ServerlessAutoSwagger", () => {
},
},
})

const swaggerFiles = ["helloworld.json", "foobar.json"];
const serverlessAutoSwagger = new ServerlessAutoSwagger(
generateServerlessFromAnEndpoint(
[
Expand All @@ -603,19 +672,16 @@ describe("ServerlessAutoSwagger", () => {
},
},
],
{
swaggerFiles: ["helloworld.json", "foobar.json"],
}
{ swaggerFiles }
),
{}
)

await serverlessAutoSwagger.gatherSwaggerFiles()
await serverlessAutoSwagger.gatherSwaggerFiles(swaggerFiles)

expect(serverlessAutoSwagger.swagger).toEqual({
swagger: "2.0",
info: { title: "", version: "1" },
schemes: ["https"],
paths: {
"/foo": "whatever",
"/bar": "something else",
Expand Down