Nerdiest Precious Modules

    This package has been deprecated

    Author message:

    The package as been renamed as fluent-json-schema. Run: npm install fluent-json-schema

    fluent-schema
    TypeScript icon, indicating that this package has built-in type declarations

    1.1.0 • Public • Published

    fluent-schema

    A fluent API to generate JSON schemas (draft-07) for Node.js and browser. Framework agnostic.

    view on npm Build Status

    Features

    • Fluent schema implements JSON Schema draft-07 standards
    • Faster and shorter way to write a JSON Schema via a fluent API
    • Runtime errors for invalid options or keywords misuse
    • Javascript constants can be used in the JSON schema (e.g. enum, const, default ) avoiding discrepancies between model and schema
    • Typescript definitions
    • Coverage 99%

    Install

    npm install fluent-schema --save
    

    or

    yarn add fluent-schema
    

    Usage

    const S = require('fluent-schema')
    
    const ROLES = {
      ADMIN: 'ADMIN',
      USER: 'USER',
    }
    
    const schema = S.object()
      .id('http://foo/user')
      .title('My First Fluent JSON Schema')
      .description('A simple user')
      .prop(
        'email',
        S.string()
          .format(S.FORMATS.EMAIL)
          .required()
      )
      .prop(
        'password',
        S.string()
          .minLength(8)
          .required()
      )
      .prop(
        'role',
        S.string()
          .enum(Object.values(ROLES))
          .default(ROLES.USER)
      )
      .prop(
        'birthday',
        S.raw({ type: 'string', format: 'date', formatMaximum: '2020-01-01' }) // formatMaximum is an AJV custom keywords
      )
      .definition(
        'address',
        S.object()
          .id('#address')
          .prop('line1', S.anyOf([S.string(), S.null()])) // JSON Schema nullable
          .prop('line2', S.string().raw({ nullable: true })) // Open API / Swagger  nullable
          .prop('country', S.string())
          .prop('city', S.string())
          .prop('zipcode', S.string())
          .required(['line1', 'country', 'city', 'zipcode'])
      )
      .prop('address', S.ref('#address'))
    
    console.log(JSON.stringify(schema.valueOf(), undefined, 2))

    Schema generated:

    {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "definitions": {
        "address": {
          "type": "object",
          "$id": "#address",
          "properties": {
            "line1": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "line2": {
              "type": "string",
              "nullable": true
            },
            "country": {
              "type": "string"
            },
            "city": {
              "type": "string"
            },
            "zipcode": {
              "type": "string"
            }
          },
          "required": ["line1", "country", "city", "zipcode"]
        }
      },
      "type": "object",
      "$id": "http://foo/user",
      "title": "My First Fluent JSON Schema",
      "description": "A simple user",
      "properties": {
        "email": {
          "type": "string",
          "format": "email"
        },
        "password": {
          "type": "string",
          "minLength": 8
        },
        "birthday": {
          "type": "string",
          "format": "date",
          "formatMaximum": "2020-01-01"
        },
        "role": {
          "type": "string",
          "enum": ["ADMIN", "USER"],
          "default": "USER"
        },
        "address": {
          "$ref": "#address"
        }
      },
      "required": ["email", "password"]
    }

    Typescript

    with "esModuleInterop": true activated in the tsconfig.json

    import S from 'fluent-schema'
    
    const schema = S.object()
      .prop('foo', S.string())
      .prop('bar', S.number())
      .valueOf()

    with "esModuleInterop": false in the tsconfig.json

    import * as S from 'fluent-schema'
    
    const schema = S.object()
      .prop('foo', S.string())
      .prop('bar', S.number())
      .valueOf()

    Validation

    Fluent schema doesn't validate a JSON schema. However there are many libraries that can do that for you. Below a few examples using AJV

    npm install ajv --save
    

    or

    yarn add ajv
    

    Validate an empty model

    Snippet

    const ajv = new Ajv({ allErrors: true })
    const validate = ajv.compile(schema.valueOf())
    let user = {}
    let valid = validate(user)
    console.log({ valid }) //=> {valid: false}
    console.log(validate.errors) //=> {valid: false}

    Output:

    {valid: false}
    errors: [
      {
        keyword: 'required',
        dataPath: '',
        schemaPath: '#/required',
        params: { missingProperty: 'email' },
        message: "should have required property 'email'",
      },
      {
        keyword: 'required',
        dataPath: '',
        schemaPath: '#/required',
        params: { missingProperty: 'password' },
        message: "should have required property 'password'",
      },
    ]
    
    

    Validate a partially filled model

    Snippet

    user = { email: 'test', password: 'password' }
    valid = validate(user)
    console.log({ valid })
    console.log(validate.errors)

    Output:

    {valid: false}
    errors:
    [ { keyword: 'format',
        dataPath: '.email',
        schemaPath: '#/properties/email/format',
        params: { format: 'email' },
        message: 'should match format "email"' } ]
    
    

    Validate a model with a wrong format attribute

    Snippet

    user = { email: 'test@foo.com', password: 'password' }
    valid = validate(user)
    console.log({ valid })
    console.log('errors:', validate.errors)

    Output:

    {valid: false}
    errors: [ { keyword: 'required',
        dataPath: '.address',
        schemaPath: '#definitions/address/required',
        params: { missingProperty: 'country' },
        message: 'should have required property \'country\'' },
      { keyword: 'required',
        dataPath: '.address',
        schemaPath: '#definitions/address/required',
        params: { missingProperty: 'city' },
        message: 'should have required property \'city\'' },
      { keyword: 'required',
        dataPath: '.address',
        schemaPath: '#definitions/address/required',
        params: { missingProperty: 'zipcoce' },
        message: 'should have required property \'zipcode\'' } ]
    

    Valid model

    Snippet

    user = { email: 'test@foo.com', password: 'password' }
    valid = validate(user)
    console.log({ valid })

    Output:

    {valid: true}
    

    Extend schema

    Normally inheritance with JSON Schema is achieved with allOf. However when .additionalProperties(false) is used the validator won't understand which properties come from the base schema. S.extend creates a schema merging the base into the new one so that the validator knows all the properties because it's evaluating only a single schema. For example in a CRUD API POST /users could use the userBaseSchema rather than GET /users or PATCH /users use the userSchema which contains the id, createdAt and updatedAt generated server side.

    const S = require('fluent-schema')
    const userBaseSchema = S.object()
      .additionalProperties(false)
      .prop('username', S.string())
      .prop('password', S.string())
    
    const userSchema = S.object()
      .prop('id', S.string().format('uuid'))
      .prop('createdAt', S.string().format('time'))
      .prop('updatedAt', S.string().format('time'))
      .extend(userBaseSchema)
    
    console.log(userSchema)

    Selecting only certain properties of your schema

    In addition to extending schemas, it is also possible to reduce them into smaller schemas. This comes in handy when you have a large Fluent Schema, and would like to re-use some of its properties.

    const S = require('fluid-schema')
    const userSchema = S.object()
      .prop('username', S.string())
      .prop('password', S.string())
      .prop('id', S.string().format('uuid'))
      .prop('createdAt', S.string().format('time'))
      .prop('updatedAt', S.string().format('time'))
    
    const loginSchema = userSchema.only(['username', 'password'])

    Detect Fluent Schema objects

    Every Fluent Schema objects contains a boolean isFluentSchema. In this way you can write your own utilities that understands the Fluent Schema API and improve the user experience of your tool.

    const S = require('fluent-schema')
    const schema = S.object()
      .prop('foo', S.string())
      .prop('bar', S.number())
    console.log(schema.isFluentSchema) // true

    Documentation

    Acknowledgments

    Thanks to Matteo Collina for pushing me to implement this utility! 🙏

    Related projects

    Licence

    Licensed under MIT.

    Install

    npm i fluent-schema

    DownloadsWeekly Downloads

    3,678

    Version

    1.1.0

    License

    MIT

    Unpacked Size

    248 kB

    Total Files

    36

    Last publish

    Collaborators

    • aboutlo
    • matteo.collina