Skip to content

Commit

Permalink
Add option to add header schema
Browse files Browse the repository at this point in the history
  • Loading branch information
lueenavarro committed Jul 12, 2021
1 parent 6156587 commit 630d478
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 29 deletions.
8 changes: 4 additions & 4 deletions examples/route-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ options: {
}
```

Defining schema and adding examples in plugins response.schema. This will prevent hapi from validating your response
Defining payload schema and adding examples in plugins response.schema. This will prevent hapi from validating your response

```javascript
...
Expand All @@ -88,7 +88,7 @@ options: {
"hapi-openapi3": {
response: {
schema: {
schema: Joi.object({
payload: Joi.object({
createdAt: Joi.date().iso()
updatedAt: Joi.date().iso()
}),
Expand All @@ -107,7 +107,7 @@ options: {
}
```

Defining schema and adding examples in plugins response.status. This will prevent hapi from validating your response
Defining payload schema and adding examples in plugins response.status. This will prevent hapi from validating your response

```javascript
...
Expand All @@ -122,7 +122,7 @@ options: {
response: {
status: {
200: {
schema: Joi.object({
payload: Joi.object({
createdAt: Joi.date().iso()
updatedAt: Joi.date().iso()
}),
Expand Down
11 changes: 6 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ See [examples](./examples/route-options.md)

<h4 id="responseOptions">responseOptions:</h4>

| option | description | type | default | required |
| ---------- | ----------------- | --------------------- | ------- | -------- |
| `schema` | Joi Schema | string | | false |
| `example` | Single Example | any | | false |
| `examples` | Multiple Examples | object\<string, any\> | | false |
| option | description | type | default | required |
| ---------- | --------------------- | --------------------- | ------- | -------- |
| `header` | Joi Schema of Header | string | | false |
| `payload` | Joi Schema of Payload | string | | false |
| `example` | Single Example | any | | false |
| `examples` | Multiple Examples | object\<string, any\> | | false |
48 changes: 42 additions & 6 deletions src/lib/response.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe("response.ts", () => {
"hapi-openapi3": {
response: {
schema: {
schema: Joi.object(),
payload: Joi.object(),
example: {},
},
},
Expand Down Expand Up @@ -91,7 +91,10 @@ describe("response.ts", () => {
response: {
status: {
201: {
schema: Joi.object(),
header: Joi.object({
responseTime: Joi.date().iso(),
}),
payload: Joi.object(),
examples: {
firstExample: {},
secondExample: null,
Expand All @@ -104,8 +107,14 @@ describe("response.ts", () => {
};

const result = response.get(mockRouteOption);
const header = _.get(result, ["201", "headers"]);
console.log(header);
const content = _.get(result, ["201", "content", "application/json"]);

expect(header.responseTime.schema).to.eql({
type: "string",
format: "date-time",
});
expect(content.schema.type).to.equal("object");
expect(content.examples.firstExample.value).to.eql({});
expect(content.examples.secondExample.value).to.equal(null);
Expand All @@ -120,7 +129,7 @@ describe("response.ts", () => {
"hapi-openapi3": {
response: {
schema: {
schema: Joi.object(),
payload: Joi.object(),
example: {},
},
},
Expand All @@ -144,11 +153,11 @@ describe("response.ts", () => {
"hapi-openapi3": {
response: {
schema: {
schema: Joi.object(),
payload: Joi.object(),
},
status: {
201: {
schema: Joi.number(),
payload: Joi.number(),
},
},
},
Expand Down Expand Up @@ -178,7 +187,7 @@ describe("response.ts", () => {
response: {
status: {
201: {
schema: Joi.number(),
payload: Joi.number(),
},
},
},
Expand All @@ -196,6 +205,33 @@ describe("response.ts", () => {
expect(error).to.be.an("error");
});

it("should throw error if header is not object schema", () => {
const mockRouteOption: any = {
plugins: {
"hapi-openapi3": {
response: {
status: {
200: {
header: Joi.array().items({
responseTime: Joi.date().iso(),
}),
},
},
},
},
},
};

let error;
try {
response.get(mockRouteOption);
} catch (err) {
error = err;
}

expect(error).to.be.an("error");
});

it("should return undefined", () => {
const mockRouteOption = {};
const result = response.get(mockRouteOption);
Expand Down
45 changes: 33 additions & 12 deletions src/lib/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ const mapResponseSchema = (
const defaultStatusCode = 200;
const responseSchema = schema.traverse(
useCustom
? pluginResponse.schema.schema?.describe()
? pluginResponse.schema.payload?.describe()
: (hapiResponse.schema as Schema).describe()
);

if (!responseSchema) return undefined;
return {
[defaultStatusCode]: {
headers: parseHeader(pluginResponse.schema.header),
content: {
"application/json": {
schema: responseSchema,
Expand All @@ -68,6 +69,7 @@ const mapHapiResponseStatus = (

response[code] = {
description: status(code),
headers: parseHeader(pluginResponseOptions.header),
content: {
"application/json": {
schema: schema.traverse((joiSchema as Schema).describe()),
Expand All @@ -90,10 +92,11 @@ const mapCustomResponseStatus = (
const response = {};
for (let [code, options] of Object.entries(pluginResponse.status)) {
response[code] = {
headers: parseHeader(options.header),
description: options.description || status(code),
content: {
"application/json": {
schema: schema.traverse(options.schema?.describe()),
schema: schema.traverse(options.payload?.describe()),
example: options.example,
examples: options.examples && mapExamples(options.examples),
},
Expand Down Expand Up @@ -130,8 +133,11 @@ const validateResponseOptions = (

const pluginOptionValidator = (schemaErrorMessage: string) =>
Joi.object({
schema: Joi.custom((value, helper) =>
isJoiSchema(value, helper, schemaErrorMessage)
header: Joi.custom((value, helper) =>
isJoiSchema(value, helper, "header" + schemaErrorMessage)
),
payload: Joi.custom((value, helper) =>
isJoiSchema(value, helper, "payload" + schemaErrorMessage)
),
description: Joi.string(),
example: Joi.any(),
Expand All @@ -153,21 +159,18 @@ const validateResponseOptions = (
),
}).without("schema", "status"),
pluginResponse: Joi.object({
schema: pluginOptionValidator("plugin schema is not a joi schema"),
schema: pluginOptionValidator("plugin is not a joi schema"),
status: Joi.object()
.unknown()
.pattern(
/^/,
pluginOptionValidator("plugin status schema is not a schema")
),
.pattern(/^/, pluginOptionValidator("plugin status is not a schema")),
}).without("schema", "status"),
});

const validatorResult = validator.validate(options);
checkValidationResult(validatorResult);

const schemaValidator = Joi.object()
.oxor("response.schema", "pluginResponse.schema.schema")
.oxor("response.schema", "pluginResponse.schema.payload")
.when(
Joi.object().or(
"pluginResponse.schema.example",
Expand All @@ -176,7 +179,7 @@ const validateResponseOptions = (
{
then: Joi.object().or(
"response.schema",
"pluginResponse.schema.schema"
"pluginResponse.schema.payload"
),
}
);
Expand All @@ -186,7 +189,7 @@ const validateResponseOptions = (
const statusValidator = (statusCode: string) =>
Joi.object().oxor(
`response.status.${statusCode}`,
`pluginResponse.status.${statusCode}.schema`
`pluginResponse.status.${statusCode}.payload`
);

if (pluginResponse.status) {
Expand All @@ -205,6 +208,24 @@ const checkValidationResult = (result: ValidationResult) => {
}
};

const parseHeader = (headerSchema: Schema) => {
if (!headerSchema) return undefined;

const headerDescription = headerSchema.describe();
if (headerDescription.type !== "object") {
const errorMessage = "header schema is not of type object";
logger.error("RESPONSE_PLUGIN_OPTION_ERROR", errorMessage);
throw new Error(errorMessage);
}

const headerProperties = headerSchema.describe().keys;
if (!headerProperties) return undefined;

return _.mapObject(headerProperties, (value) => ({
schema: schema.traverse(value),
}));
};

export default {
get,
};
4 changes: 3 additions & 1 deletion src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export interface RequestOptions {
}

export interface ResponseOptions {
schema?: Schema;
header?: Schema;
payload?: Schema;
description?: string;
example?: any;
examples?: Record<string, any>;
Expand All @@ -45,6 +46,7 @@ export interface ResponseOptions {
export interface RoutePluginOptions {
request: RequestOptions;
response?: {
headerSchema: Schema;
schema?: ResponseOptions;
status?: Record<string, ResponseOptions>;
};
Expand Down
1 change: 0 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,5 @@ module.exports = {
sourceMapFilename: "bundle.js.map",
path: path.resolve(__dirname, "dist"),
libraryTarget: "commonjs2",
clean: true,
},
};

0 comments on commit 630d478

Please sign in to comment.