Skip to content

Commit

Permalink
Merge pull request #1 from halvardssm/fixes
Browse files Browse the repository at this point in the history
Fixes
  • Loading branch information
halvardssm authored Jul 12, 2020
2 parents d8b1b35 + 09c40fe commit 25a34a5
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 35 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: CI

env:
DENO_VERSION: 1.1.3

on:
push:
branches:
Expand All @@ -21,7 +24,7 @@ jobs:
- name: Install deno
uses: denolib/setup-deno@master
with:
deno-version: 1.0.3
deno-version: ${{env.DENO_VERSION}}
- name: Check formatting
run: deno fmt --check
- name: Run tests
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
test:
deno test
fmt:
deno fmt src *.ts
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Oak Middleware Validator

![CI](https://github.com/halvardssm/oak-middleware-validator/workflows/CI/badge.svg)
[![(Deno)](https://img.shields.io/badge/deno-1.0.3-green.svg)](https://deno.land)
[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/raw.githubusercontent.com/halvardssm/oak-middleware-validator/master/mod.ts)
[![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/halvardssm/oak-middleware-validator/ci/master?style=flat-square&logo=github)](https://github.com/halvardssm/oak-middleware-validator/actions?query=branch%3Amaster+workflow%3ACI)
[![(Deno)](https://img.shields.io/badge/deno-v1.1.3-green.svg?style=flat-square&logo=deno)](https://deno.land)
[![deno doc](https://img.shields.io/badge/deno-doc-blue.svg?style=flat-square&logo=deno)](https://doc.deno.land/https/raw.githubusercontent.com/halvardssm/oak-middleware-validator/master/mod.ts)

Oak middleware for parameter and body validator

Expand All @@ -11,14 +11,14 @@ Oak middleware for parameter and body validator
* As a router middleware

```ts
import { validatorMiddleware, validatorMiddlewareOptions } from "https://raw.githubusercontent.com/halvardssm/oak-middleware-validator/master/mod.ts"
import { validatorMiddleware, ValidatorMiddlewareOptions } from "https://raw.githubusercontent.com/halvardssm/oak-middleware-validator/master/mod.ts"
import { RouterMiddleware } from "https://deno.land/x/oak/mod.ts";

const router = new Router();

const app = new Application();

const options: validatorMiddlewareOptions = {
const options: ValidatorMiddlewareOptions = {
validations: [
{ key: "a", validationOption: "string", isUrlParam: true },
{ key: "b", validationOption: "b", isOptional: true },
Expand All @@ -41,20 +41,22 @@ Oak middleware for parameter and body validator

## Options

* validations: validationT[]; // Array of validations
* validations: ValidationT[]; // Array of validations
* bodyRequired?: boolean; // If true, will return error if body is empty
* errorMessages?: Partial<errorMessagesT>; // Customize your own error messages
* bodyType?: BodyType; // Will validate the body against a provided body type
* errorMessages?: Partial<ErrorMessagesT>; // Customize your own error messages

### validationT

* key: string; // The key
* validationOption: validationFn | string; // The validation function, also accepts a `typeof` or a string to compare strings
* validationOption: ValidationFn | string; // The validation function, also accepts a `typeof` or a string to compare strings
* isOptional?: boolean; // Will allow the key to be missing
* isUrlParam?: boolean; // Will check the url instead of the body

### errorMessagesT

* ERROR_NO_BODY: "No body was provided",
* ERROR_INVALID_BODY: "Invalid body type",
* ERROR_MISSING_REQUIRED: "No value was provided",
* ERROR_VALIDATION_FAILED: "Validation failed",
* ERROR_NOT_IN_ARRAY: "Value not in array",
Expand All @@ -67,9 +69,9 @@ Oak middleware for parameter and body validator
(
key: string, // The key
value: string | Uint8Array, // The value
validationError: validationErrorFn, // The validation error callback
errorMessages: errorMessagesT, // Exposes the error messages
options: validatorMiddlewareOptions // Exposes the options
validationError: ValidationErrorFn, // The validation error callback
errorMessages: ErrorMessagesT, // Exposes the error messages
options: ValidatorMiddlewareOptions // Exposes the options
) => void
```

Expand Down
1 change: 1 addition & 0 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export {
RouterMiddleware,
httpErrors,
Request,
BodyType,
} from "https://deno.land/x/oak@v4.0.0/mod.ts";
export { ErrorStatus } from "https://deno.land/x/oak@v4.0.0/types.ts";
52 changes: 34 additions & 18 deletions src/validatorMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,52 @@
import { RouterMiddleware, ErrorStatus } from "../deps.ts";
import { RouterMiddleware, ErrorStatus, BodyType } from "../deps.ts";

export type validationFn = (
export type ValidationFn = (
key: string,
value: string | Uint8Array,
validationError: validationErrorFn,
errorMessages: errorMessagesT,
options: validatorMiddlewareOptions,
validationError: ValidationErrorFn,
errorMessages: ErrorMessagesT,
options: ValidatorMiddlewareOptions,
) => void;

export type validationErrorFn = (
export type ValidationErrorFn = (
message: string,
key?: string,
value?: any,
shouldBe?: any,
) => never;

export type validationT = {
export type ValidationT = {
key: string;
validationOption: validationFn | string;
validationOption: ValidationFn | string;
isOptional?: boolean;
isUrlParam?: boolean;
};

export type validatorMiddlewareOptions = {
validations: validationT[];
export type ValidatorMiddlewareOptions = {
validations: ValidationT[];
bodyRequired?: boolean;
errorMessages?: Partial<errorMessagesT>;
bodyType?: BodyType;
errorMessages?: Partial<ErrorMessagesT>;
};

export type errorKeys =
export type ErrorKeys =
| "ERROR_NO_BODY"
| "ERROR_INVALID_BODY"
| "ERROR_MISSING_REQUIRED"
| "ERROR_VALIDATION_FAILED"
| "ERROR_NOT_IN_ARRAY"
| "ERROR_NUMBER_MIN"
| "ERROR_NUMBER_MAX";

export type errorMessagesT = Record<errorKeys, string>;
export type ErrorMessagesT = Record<ErrorKeys, string>;

export interface JsonT {
[key: string]: string | undefined; //| JsonT
}

export const errors: errorMessagesT = {
export const errors: ErrorMessagesT = {
ERROR_NO_BODY: "No body was provided",
ERROR_INVALID_BODY: "Invalid body type",
ERROR_MISSING_REQUIRED: "No value was provided",
ERROR_VALIDATION_FAILED: "Validation failed",
ERROR_NOT_IN_ARRAY: "Value not in array",
Expand All @@ -52,11 +55,11 @@ export const errors: errorMessagesT = {
};

/** Validatior middleware */
export const validatorMiddleware = (options: validatorMiddlewareOptions) => {
export const validatorMiddleware = (options: ValidatorMiddlewareOptions) => {
Object.assign(errors, options.errorMessages);

const core: RouterMiddleware = async (ctx, next) => {
const validateElement = (valEl: validationT, element?: string) => {
const validateElement = (valEl: ValidationT, element?: string) => {
if (element) {
if (typeof valEl.validationOption === "function") {
valEl.validationOption(
Expand Down Expand Up @@ -84,7 +87,7 @@ export const validatorMiddleware = (options: validatorMiddlewareOptions) => {
}
};

const validateBody = async (valEl: validationT) => {
const validateBody = async (valEl: ValidationT) => {
const validateForm = (body: URLSearchParams) => {
if (body.has(valEl.key)) {
const values = body.getAll(valEl.key);
Expand Down Expand Up @@ -128,7 +131,7 @@ export const validatorMiddleware = (options: validatorMiddlewareOptions) => {
}
};

const validationError: validationErrorFn = (
const validationError: ValidationErrorFn = (
message,
key,
value,
Expand All @@ -146,6 +149,19 @@ export const validatorMiddleware = (options: validatorMiddlewareOptions) => {
validationError(errors.ERROR_NO_BODY);
}

const body = await ctx.request.body();

if (
ctx.request.hasBody && options.bodyType && body.type !== options.bodyType
) {
validationError(
errors.ERROR_INVALID_BODY,
undefined,
body.type,
options.bodyType,
);
}

for await (const valEl of options.validations) {
if (valEl.isUrlParam) {
validateElement(valEl, ctx.params[valEl.key]);
Expand Down
10 changes: 5 additions & 5 deletions src/validators.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { validationFn, errorMessagesT } from "./validatorMiddleware.ts";
import { ValidationFn, ErrorMessagesT } from "./validatorMiddleware.ts";
const decoder = new TextDecoder();

/** Validates if a string is in the array */
export const validateStringInArray = (...values: string[]): validationFn => {
return (key, value, throws, errorMessages: errorMessagesT) => {
export const validateStringInArray = (...values: string[]): ValidationFn => {
return (key, value, throws, errorMessages: ErrorMessagesT) => {
if (Array.isArray(value)) value = decoder.decode(value as Uint8Array);

if (!values.includes(value as string)) {
Expand All @@ -19,8 +19,8 @@ export const validateIsNumber = (
min?: number,
max?: number,
radix?: number,
): validationFn => {
return (key, value, throws, errorMessages: errorMessagesT) => {
): ValidationFn => {
return (key, value, throws, errorMessages: ErrorMessagesT) => {
if (Array.isArray(value)) value = decoder.decode(value as Uint8Array);

const parsedVal = parseInt(value as string, radix);
Expand Down
34 changes: 34 additions & 0 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ const tests = [
await mw(mockContext({ a: "b" }), mockNext);
},
},
{
name: "Success body type",
async fn() {
const mw = validatorMiddleware({
validations: [
{ key: "a", validationOption: "string" },
],
bodyType: "json",
});

await mw(mockContext({}, JSON.stringify({ a: "b" })), mockNext);
},
},
{
name: "Success body JSON",
async fn() {
Expand Down Expand Up @@ -250,6 +263,27 @@ const tests = [
);
},
},
{
name: "Throws invalid body",
async fn() {
const mw = validatorMiddleware({
validations: [
{ key: "a", validationOption: "string" },
],
bodyType: "json",
});

assertThrowsAsync(
async () =>
await mw(
mockContext({}, "a=b", "application/x-www-form-urlencoded"),
mockNext,
),
httpErrors.UnprocessableEntity,
"Invalid body type; Value: form; Should Be: json;",
);
},
},
{
name: "Throws custom error",
async fn() {
Expand Down

0 comments on commit 25a34a5

Please sign in to comment.