Skip to content

gchumillas/schema-fixer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

166e24e · Jan 8, 2025
Dec 8, 2024
Dec 25, 2024
Oct 26, 2021
Mar 13, 2024
Mar 7, 2024
Dec 10, 2024
Mar 4, 2024
Dec 25, 2024
Oct 26, 2021
Jan 8, 2025
Dec 14, 2024
Dec 6, 2024
Dec 6, 2024
Jan 8, 2025
Dec 2, 2024

Repository files navigation

Schema-Fixer

What is Schema-Fixer?

Schema-Fixer is a lightweight utility designed to sanitize data while preserving its types.

What is it not?

Schema-Fixer is not a transformer or a validator—it doesn't validate correctness or enforce structure. It simply "fixes" data to align with a given schema.

Why use Schema-Fixer?

Handling null, undefined, or unexpected data formats can lead to application crashes or unintended behavior. Schema-Fixer ensures that your data conforms to the expected schema, allowing your application to run smoothly without errors caused by malformed input.

Key Features

  • Reliable and Fail-Safe
    Schema-Fixer can process any data against a given schema without failing.

  • Type Inference
    Automatically infers data types based on the provided schema.

  • Graceful Handling of null and undefined
    Replaces null or undefined values with predefined default values, ensuring data integrity

  • Prevents Unwanted Properties
    Excludes extraneous or unexpected properties, mitigating risks like code injection.

Install

# install from your project directory
npm install @schema-fixer/core
// and import it from your file.js or file.ts
import sf from '@schema-fixer/core'

console.log(sf.fix(100, 'string')) // returns '100'

Example

In the following example we obtain the data from an external API and ensure that it conforms to the expected format:

import sf from '@schema-fixer/core'

// unwanted `null` and `undefined` values are transformed to '', 0, [], etc.
// no need to indicate the return type, since it is inferred from the schema
function getAuthor = async (authorId: string) => {
  const res = await axios.get(`/api/v1/authors/${authorId}`)

  // 'fixes' the data against a given 'schema'
  return sf.fix(res.data, {
    name: sf.text(),
    middleName: sf.text(),
    lastName: sf.text(),
    age: sf.float(),
    isMarried: sf.bool(),
    // an array of strings
    children: sf.list({ of: sf.text() }),
    // an object
    address: {
      street: sf.text(),
      city: sf.text(),
      state: sf.text()
    },
    // array of objects
    books: sf.list({ of: {
      title: sf.text(),
      year: sf.float(),
      // combine multiple 'fixers'
      id: [sf.trim(), sf.upper()]
    }})
  })
}

Sugar syntax

For brevity, you can also use the "sugar syntax", which replaces "aliases" with the corresponding fixers:

import sf from '@schema-fixer/core'

const fixedData = sf.fix(data, {
  name: 'string',       // sf.text()
  age: 'number',        // sf.float()
  isMarried: 'boolean', // sf.bool()
  children: 'string[]', // sf.list({ of: sf.text() })
  years: 'number[]',    // sf.list({ of: sf.float() })
  items: 'boolean[]'    // sf.list({ of: sf.bool() })
})

API

fix(data, schema)        // fixes 'any data' againts a given schema
createFixer(def, fixer)  // creates a custom fixer

// built-in fixers
text({ def = '', required = true, coerce = true })     // fixes a string
float({ def = 0, required = true, coerce = true })     // fixes a number
bool({ def = false, required = true, coerce = true })  // fixes a boolean
list({ of: Schema, def = [], required = true })        // fixes an array

// additional built-in fixers
trim({ def = '', required = true })                    // trims a string
lower({ def = '', required = true })                   // lowercases a string
upper({ def = '', required = true })                   // uppercases a string
floor({ def = 0, required = true })                    // math floor
  • A 'fixer' is the function that fixes the incoming data.

  • A Schema can be a 'fixer', a list of 'fixers' or a record of 'fixers'. For example:

fix(1, bool()) // true
fix('Hello!', [text(), upper()]) // 'HELLO!'
fix({ name: 'John' }, { name: text(), age: float() }) // { name: 'John, age: 0 }
  • The def parameter indicates the value to return when the 'fixer' cannot fix the incoming data or the data is null or undefined. For example:
fix(null, text()) // ''
fix(undefined, text({ def: 'aaa' })) // 'aaa'
fix({}, float({ def: 100 })) // 100, since {} cannot be fixed
  • The coerce parameter indicates that you want to "force" the conversion (default is true). For example:
fix(100, text()) // '100'
fix(100, text({ coerce: false })) // '', since 100 is not a string
  • The required parameter indicates that an incoming value is required. For example:
fix(undefined, float()) // 0, as we expect a number
fix(null, float({ required: false })) // undefined
fix(undefined, text({ required: false })) // undefined

Note

You'll probably never have to use required, def or coerce. But they're there!

Take a look at the TEST FILE for more examples.

Combine fixers

You can apply different "fixers" to the same incoming data by combining them. For example:

fix('Hello!', [text(), upper()]) // 'HELLO!'

Create your own fixer

This is a simple fixer:

import sf from '@schema-fixer/core'

// fixes a color
const colorFixer = sf.createFixer('#000000', (value) => {
  const color = `${value}`.trim().toUpperCase()

  if (color.match(/^#([0-9,A-F]{6})$/)) {
    // nice!
    return color
  } else {
    const matches = color.match(/^#([0-9,A-F]{3})$/)

    if (matches) {
      return `#${matches[1].split('').map(d => d.repeat(2)).join('')}`
    }
  }

  // a default value is provided
  throw new TypeError('not a color')
})

sf.fix('#f6f', colorFixer())    // '#FF66FF'
sf.fix('#f6ef6f', colorFixer()) // '#F6EF6F'
sf.fix('red', colorFixer())     // '#000000', as 'red' is not a valid color

Contributing

Do you want to contribute? Fantastic! Take a look at open issues or help me find and fix bugs.

About

A small utility to fix data, preserving its types

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published