Skip to content

Commit

Permalink
feat: add basePath and schemes to custom config (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
bfaulk96 authored Feb 8, 2022
1 parent f514234 commit 52118a5
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 63 deletions.
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

0 comments on commit 52118a5

Please sign in to comment.