Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

api-doc-validator

Package Overview
Dependencies
Maintainers
1
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

api-doc-validator

api doc and validator

  • 1.9.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
5
decreased by-94.19%
Maintainers
1
Weekly downloads
 
Created
Source

api ➡️ doc ➡️ validator

Build Status

Annotations

Parameters of annotations

Usage

@url

@url [METHOD] path
/**
 * @url POST /path/:param
 */

@params

Validate parameters of @url path

@params [OBJECT_NAME =] json-schema|OBJECT_NAME
/**
 * @url GET /users/:id
 * @params {
 *     id: number,
 * }
 * @call users.get(id)
 */

or with OBJECT_NAME assign for future use

/**
 * @url GET /users/:id
 * @params User = {
 *     id: number,
 * }
 */

or use an external schema as root schema

/**
 * @url GET /users/:id
 * @params User
 */

or extend external schema

/**
 * @url GET /users/:id
 * @params {
 *     ...User,
 *     name: string,
 * }
 */

@query

Validate @url query parameters

@query [OBJECT_NAME =] json-schema|OBJECT_NAME
/**
 * @url GET /users
 * @query {
 *     id: number,
 * }
 * @call users.get(id)
 */

Example of valid request GET /users?id=1

Names of fields in @params and query should be different to use them in @call

/**
 * @url GET /users/:id
 * @params {
 *     id: number,
 * }
 * @query {
 *     name: string,
 * }
 * @call users.get(id, name)
 */

@body

@body [OBJECT_NAME =] json-schema|OBJECT_NAME
/**
 * @body {
 *     id: number,
 *     name: string,
 * }
 */

@response

Response http code and validation of response body.

@response [CODE] [OBJECT_NAME =] json-schema|OBJECT_NAME

Response for 200 code

/**
 * @response {
 *     id: number,
 *     name: string,
 * }
 */

Validators for different codes of same request

/**
 * @response 200 {
 *     id: number,
 *     name: string,
 * }
 * @response 500 {
 *     message: string,
 * }
 */

@namespace

Word used to filter validators in target file. You can use shortcut @ns

/**
 * Namespace for this validator will be "default"
 * 
 * @url POST /users
 * @body {id: number}
 */

/**
 * @namespace test
 * @url POST /test-success
 * @response 2xx {success: boolean}
 */

/**
 * @ns test
 * @url POST /test-error
 * @response 5xx {error: boolean}
 */

Example of generation express middleware with only test validators

npx adv -c path/to/config.json -n test -e ./test-validator.js

@schema

Define the new schema for future usage

@schema OBJECT_NAME = json-schema|OBJECT_NAME
/**
 * @schema User = {
 *     id: number,
 *     name: string,
 * }
 */

or just to make shorter schema name

/**
 * @schema User = SomeVeryLongSchemaName
 */

@call

You should provide valid js code of method call. This code will be used in your API tests.

@call object-method-call
/**
 * @call object.method(param1, param2)
 */

METHOD

HTTP request method. Default is config.defaultMethod

GET|POST|PUT|DELETE|HEAD|OPTIONS

CODE

HTTP response code. Default is config.defaultCode

Formats:

  • 200 regular code number
  • 2xx any code between 200 and 299
  • 200 - 400 any code between 200 and 400
  • 200 || 3xx || 400 - 500 or expression

Example

/**
 * @response 2xx || 301 User = {id: number}
 */

path

URL pathname. For path parsing used path-to-regexp lib.

Parameters like :id can be used in @call as parameter of method call with same name.

/**
 * @url GET /users/:id(\d+)
 * @call users.get(id)
 */

OBJECT_NAME

Any valid js object name like objectName or with field of any deep objectName.fieldName.field

json-schema

Object that describes how validate another object. For object validation used ajv lib with few modifications for less code writing.

Default ajv schema

schema = {
    type: "object",
    additionalProperties: false,
    required: ['id', /* "name", */ 'enabled', 'list', 'user', 'enumOfStrings'],
    properties: {
        id: {
            type: "number",
            // extra fields for number: maximum, minimum, exclusiveMaximum, exclusiveMinimum, multipleOf
        },
        name: {
            type: "string",
            // extra fields for string: maxLength, minLength, pattern, format
        },
        enabled: {
            type: "boolean",
            // no extra fields
        },
        list: {
            type: "array",
            // extra fields for array: maxItems, minItems, uniqueItems, items, additionalItems, contains
        },
        user: {
            type: "object",
            // extra fields for object: maxProperties, minProperties, required, properties, patternProperties, 
            //                          additionalProperties, dependencies, propertyNames
        },
        enumOfStrings: {
            type: "string",
            enum: ["user", "guest", "owner"]
        },
    }
}

Simplified description of schema

schema = {
    id: number,
    [name]: string,
    enabled: boolean,
    listOfObjects: [{
        id: number,
        type: string,
    }],

    listOfNumbers: [{
        type: "number"
    }],

    // which means list is array of numbers

    user: {
        id: number,
        type: string,
    },
    enumOfStrings: "user" || "guest" || "owner",
}

So, if any object in a schema (including root) has field type with one of the string values "number", "integer", "string", "boolean", "array", "object" or "null" than it means this object is validator. In any other cases this object will be converted to "object" validator. Example

schema = {
    days: [number],
    list: [{
        id: number,
        type: string,
    }],
    user: {
        id: number,
        type: string,
    },
    parent: {
        type: "object",
    },
}

Will be converted to

schema = {
    type: "object",
    additionalProperties: false,
    required: ['days', 'list', 'user', 'parent'],
    properties: {
        days: {
            type: "array",
            items: {
                type: "number"
            }    
        },
        list: {
            type: "array",
            items: {
                type: "object",
                required: ["id", "type"],
                properties: {
                    id: {
                        type: "number"
                    },
                    type: {
                        type: "string"
                    },            
                }   
            }    
        },
        user: {
            type: "object",
            required: ["id", "type"],
            properties: {
                id: {
                    type: "number"
                },
                type: {
                    type: "string"
                },            
            }   
        },
        parent: {
            type: "object",
        },
    }
}

Optional object fields

By default, all fields in an object are required. To make field optional just put it in brackets.

schema = {
    id: number,
    [name]: string,
}
schema = {
    type: "object",
    additionalProperties: false,
    required: ["id"],
    properties: {
        id: {type: "number"},
        name: {type: "string"},
    },
}

Number patterns

Instead of short number validator you can use one of following number patterns as value of object field.

  • int number without floating-point
  • positive positive number including 0
  • negative negative number excluding 0
  • id number more than 0
schema = {
    id: id,
    price: positive,
    list: [int],
}

Will be converted to

schema = {
    type: "object",
    additionalProperties: false,
    required: ['id', 'price', 'list'],
    properties: {
        id: {
            type: "number",
            minimum: 1,
        },
        price: {
            type: "number",
            minimum: 0,
        },
        list: {
            type: "array",
            items: {
                type: "integer",
            }
        },
    },
}

String patterns

Instead of short string validator you can use one of following string patterns as value of object field.

  • date full-date according to RFC3339.
  • time time with optional time-zone.
  • date-time date-time from the same source (time-zone is optional, in ajv it's mandatory)
  • date-time-tz date-time with time-zone required
  • uri full URI.
  • uri-reference URI reference, including full and relative URIs.
  • uri-template URI template according to RFC6570
  • email email address.
  • hostname host name according to RFC1034.
  • filename name (words with dashes) with extension
  • ipv4 IP address v4.
  • ipv6 IP address v6.
  • regex tests whether a string is a valid regular expression by passing it to RegExp constructor.
  • uuid Universally Unique Identifier according to RFC4122.
schema = {
    id: uuid,
    email: email,
    created_at: date-time,
    days: [date],
}

Will be converted to

schema = {
    type: "object",
    additionalProperties: false,
    required: ['id', 'email', 'created_at', 'days'],
    properties: {
        id: {
            type: "string",
            format: "uuid",
        },
        email: {
            type: "string",
            format: "email",
        },
        created_at: {
            type: "string",
            format: "date-time",
        },
        days: {
            type: "array",
            items: {
                type: "string",
                format: "date",
            }
        },
    }
}

Inject external schema

Using OBJECT_NAME you can inject an external schema in a current schema.

/**
 * @url GET /users/:id
 * @response User = {
 *     id: number,
 *     name: string,
 * }
 */
/**
 * @url POST /users
 * @body {
 *     action: 'update' || 'delete',
 *     user: User,
 * }
 */

anyOf schema

Instead of anyOf you can use || operator

schema = {
    data: User || Account || {type: "object"}
}

will be

schema = {
    type: "object",
    additionalProperties: false,
    required: ['data'],
    properties: {
        data: {
            anyOf: [
                {/* schema of User */},
                {/* schema of Account */},
                {type: "object"},
            ]
        }
    }
}

allOf schema

Instead of allOf you can use && operator

schema = {
    data: User && Account && {type: "object"}
}

will be

schema = {
    type: "object",
    additionalProperties: false,
    required: ['data'],
    properties: {
        data: {
            allOf: [
                {/* schema of User */},
                {/* schema of Account */},
                {type: "object"},
            ]
        }
    }
}

Extend schema

To extend you can use object spread operator

User = {
    id: number,
    data: string,
}

UserExtra = {
    name: string,
    created_at: date,
}

schema = {
   ...User,
   ...UserExtra,
   age: number, // add field
   data: undefined, // remove field
   created_at: date-time, // overwrite field
}

will be

schema = {
   type: "object",
   additionalProperties: false,
   required: ['id', 'name', 'created_at', 'age'],
   properties: {
      id: {type: "number"},
      name: {type: "string"},
      created_at: {type: "string", format: "date-time"},
      age: {type: "number"},
   }
}

Also, you can overwrite validator options

schema = {
   ...User,
   type: "object",
   additionalProperties: true,
}

Important to add type: "object" it says to compiler that this object is pure ajv validator, not simplified version.

schema = {
   type: "object",
   additionalProperties: true,
   properties: {
      id: {type: "number"},
      data: {type: "string"},
   }
}

You extend even non object validators

phone = {
	type: "string",
    pattern: "^\\d+$"
}

schema = {
	...phone,
    type: "string",
    maxLength: 20,
}

object-method-call

Uniq sample of JavaScript code with some method call of some object, which will be generated for testing purposes. In method call you can use named parameters from @url

/**
 * @url GET /users
 * @call users.get()
 */

/**
 * @url GET /users/:id
 * @call users.get(id)
 */

/**
 * @url POST /users/:id/settings
 * @call users.setSettings(id)
 */

/**
 * @url POST /users/:id/settings
 * @call users.settings.update(id)
 */

There can be any number of nested objects and any method name. Only names of parameters should be equal.

CLI

with npx

npx adv -c path/to/config.json

Parameters:

  -c, --config <path> path to config json file
  -a, --api-client <path> generate api client
  -b, --base-url <url> default Api.baseUrl
  -e, --express <path>  generate express middleware validator
  -n, --namespace <namespace>  generate validators only with this namespace or comma separated namespaces
  -M, --default-method <method>  default @url METHOD
  -C, --default-code <code>  default @response CODE
  -T, --jsdoc-typedefs <boolean> generate typedef, default true
  -R, --jsdoc-refs <boolean> use references to jsdoc @typedef or replace them with reference body, default true
  -P, --extra-props <boolean> value for ajv "object" additionalProperties, default false

API client

Install in to your project packages ajv, ajv-formats (optional if you not using String patterns), request (if you don't like request then you will need to implement Api.request) and path-to-regexp. Client will depend on them.

Generate the API client with npx adv -c path/to/config.json -a path/to/your/app/api-client.js

Example

/**
 * @url POST /users
 * @body User = {id: number, name: string}
 * @response 200 User
 * @call addUser()
 */

/**
 * @url GET /users/:id
 * @params {id: number}
 * @response 200 User
 * @call users.get()
 */

Generated api-client.js will export class Api.

const Api = require('./path/to/your/app/api-client.js');
const client = new Api(/* "https://new.base.url" optionaly, default is Api.baseUrl */);

console.log(Api.baseUrl); // value from config.baseUrl or --base-url cli option
console.log(Api.endpoints); // parsed endpoints from comments

// optionaly
Api.getAjv = () => createYourAjvInstance();
Api.request = function ({
   method, 
   url, 
   params /* url params like /:id */, 
   query, 
   body, 
   endpoint /* object from Api.endpoints */, 
   context /* Api class instance */
}) {
	return sendRequestReturnPromise(context.baseUrl + url);
}
// --

await client.addUser({id: 1, name: 'Test'});

client.users.get({id: 1} /* or just 1 if @params has only one parameter */)
  .then(user => {
     console.log(user.name);
     console.log(client.requestCookieJar); // @see request.jar() https://github.com/request/request#examples
  })
  .catch(err => {
     // @see Validation errors handling
  });

Express middleware

Install in to your project packages ajv, ajv-formats (optional if you not using String patterns) and path-to-regexp. Middleware depends on them.

Generate the middleware with npx adv -c path/to/config.json -e path/to/your/app/validator.js

Then add middleware to your express app

const validator = require('./path/to/your/app/validator.js');

// optionaly
validator.getAjv = () => createYourAjvInstance();
// --

app.use(validator);
app.post('...', (req, res) => {});
app.use(function (err, req, res, next) {
    if (err instanceof validator.RequestValidationError) {
        // @see Validation errors handling
    }
    else if (err instanceof validator.ResponseValidationError) {
       // @see Validation errors handling
    }
    else {
    	next(err);
    }
    
    // or use base class
   
    if (err instanceof validator.ValidationError) {
       // @see Validation errors handling
    }
    else {
       next(err);
    }
});

Universal middleware

Generate the middleware with npx adv -c path/to/config.json -e path/to/your/app/validator.js

Then add it to your app

const validator = require('./validator.js');

function sendMessage(path, data) {
    try {
       var validateResponse = validator({
          url: path,
          body: data,
       });
    }
    catch (err) {
       // @see RequestValidationError
    }
    
    return ajax(path, data).then(function (result) {
    	if (validateResponse) {
           try {
              validateResponse(result);
           }
           catch (err) {
              // @see ResponseValidationError
           }
        }
    	
    	return result;
    });
}

Validation errors handling

Both Api class and middleware exports three classes:

  • ValidationError - base class, extends Error
  • RequestValidationError - class of request validation error, extends ValidationError
  • ResponseValidationError - class of response validation error, extends ValidationError
let err; // error from api client or middleware validator
let context; // Api class or middleware

if (err instanceof context.RequestValidationError) {
   console.log(err.message);
   console.log(err.property); // query | params | body
   console.log(err.errors); // @see https://github.com/ajv-validator/ajv/blob/master/docs/api.md#validation-errors
}
else if (err instanceof context.ResponseValidationError) {
   console.log(err.message); // "Invalid response body"
   console.log(err.errors); // @see https://github.com/ajv-validator/ajv/blob/master/docs/api.md#validation-errors
}

// or use base class

if (err instanceof context.ValidationError) {
   console.log(err.message);
   console.log(err.property);
   console.log(err.errors);
}

Config

  • include array of paths to files relative to config path, glob pattern used
  • exclude array of paths to files to be excluded
  • defaultMethod overwrites default METHOD. Default is GET
  • defaultCode overwrites default CODE. Default is 200
  • jsdocTypedefs generate @typedef for each named schema. Default is true
  • jsdocRefs use references to @typedef or replace them with reference body. Default is true
  • apiClient same as --api-client <path>
  • express same as --express <path>
  • namespace same as --namespace <namespace>
  • extraProps same as --extra-props <boolean>

All paths are relative to config file location.

Example

{
  "include": [
    "src/**/*.js",
    "src/**/*.php"
  ],
  "exclude": [
    "src/tests"
  ],
  "defaultMethod": "POST",
  "defaultCode": "2xx || 301"
}

Keywords

FAQs

Package last updated on 05 Mar 2021

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc