{"id":21082301,"url":"https://github.com/olsonpm/madonna-fp","last_synced_at":"2025-08-23T16:08:30.382Z","repository":{"id":80606407,"uuid":"58582590","full_name":"olsonpm/madonna-fp","owner":"olsonpm","description":null,"archived":false,"fork":false,"pushed_at":"2016-11-08T04:35:28.000Z","size":52,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"dev","last_synced_at":"2025-05-07T21:02:23.623Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/olsonpm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-05-11T21:21:42.000Z","updated_at":"2019-07-19T14:57:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"3a4c03a0-26e5-44c2-b356-5c13f6840745","html_url":"https://github.com/olsonpm/madonna-fp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/olsonpm/madonna-fp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olsonpm%2Fmadonna-fp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olsonpm%2Fmadonna-fp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olsonpm%2Fmadonna-fp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olsonpm%2Fmadonna-fp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/olsonpm","download_url":"https://codeload.github.com/olsonpm/madonna-fp/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olsonpm%2Fmadonna-fp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271755406,"owners_count":24815398,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-23T02:00:09.327Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T20:13:36.916Z","updated_at":"2025-08-23T16:08:30.354Z","avatar_url":"https://github.com/olsonpm.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MadonnaFp\nA functional approach to javascript object validation\n - MadonnaFp knows *exactly* what she wants\n - She'll accept nothing less\n - If you mess up, she'll tell you exactly what you did wrong\n\n**Tested against**\n - node 0.10.0 for the (default) es5 version\n - node 6.0.0 for es6 @ `require('madonna-fp/es6')`\n\n## Table of Contents\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n - [About](#about)\n - [Features](#features)\n - [Schema Limitations](#schema-limitations)\n - [Terminology](#terminology)\n - [Examples](#examples)\n - [API](#api)\n - [Catching Errors](#catching-errors)\n - [Reference](#reference)\n - [To Test](#test)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## About\n`madonna-fp` is a full-featured javascript object validator.  The errors\nreturned are detailed yet friendly, and make the programmer error obvious.  I\nbuilt this library because I wasn't satisfied with existing object validators.\nIt was built with lodash/fp and exposes a lot of lodash's\nboolean-returning functions.\n\n## Features\n - Validators are just key-function pairs, where the key gives meaning when\n   an error occurs.  It's easy to create custom validators with this approach.\n - There are many built-in common validators.\n - Unary validators (e.g. isString) are just flags passed as strings, which in\n   turn are validated by this library to make sure you don't make a typo.  If\n   one of [our many unary operators](#valid-flags) doesn't suit your needs, you\n   can create your own.\n - There exists a shorthand syntax when your validations are simple, and a\n   verbose syntax when you need customization.  This keeps code size to a\n   minimum, helping with readability.\n - Nested validations are kept sane via two constructs 'passTo' and\n   'passEachTo'.  These allow complex schema compositions while maintaining\n   friendly and detailed error messages.\n - When the MadonnaFp schema can't support your needs [(see Limitations\n   below)](#schema-limitations), there is a callback allowing you to\n   further validate.\n - [There are many types of errors](#error-info), each with their own id and\n   informational data. This allows you to write a custom detailed error message\n   when you need it.\n\n## Schema limitations\n - I have no plans on implementing a schema API for asynchronous validations or\n   inter-dependent validations (i.e. properties validating dependent on other\n   properties).  Rather, MadonnaFp supports these in its optional callback.\n\n## Terminology\n - **flag**: Used in this library to mean 'unary function returning a boolean'.\n - **laden**: Used as a positive form of 'nonEmpty'.  If you're aware of a better\n   term, please let me know.\n\n## Examples\n - All examples assume `const madonna = require('madonna-fp');` and `let res;`\n - Error messages are written in comments following invalid calls.  Keep in mind\n   MadonnaFp returns the error object in the result.  [See the API](#api)\n   for clarifications\n\n##### Require a property 'name' that passes 'isString'\n```js\nconst nameValidator = madonna.createValidator({\n  name: {\n    flags: ['require', 'isString']\n  }\n});\n\nres = nameValidator();\n// res.error\n// Invalid Input: Not all required keys were passed\n// missing keys: name\n\nres = nameValidator({ name: 1 });\n// res.error\n// Invalid Input: The following arguments didn't pass their criterion\n// invalid arguments: {\n//   \"name\": 1\n// }\n// failed criterion per argument: {\n//   \"name\": {\n//     \"flags\": [\n//       \"isString\"\n//     ]\n//   }\n// }\n\nres = nameValidator({ name: 'phil' });\n// res.isValid === true\n```\n\n##### More complex - validate a car object\n - requires argument 'year'\n   - isInteger\n   - between the years 1950 and 2016 inclusive\n - requires argument 'make'\n   - isString\n   - containedIn([Ford, Toyota, Chevy, Honda])\n - optionally allows 'name'\n   - isString\n   - custom:\n     - has size between 6 and 10 inclusive\n\n```js\nconst fp = require('lodash/fp')\n  , betweenI = madonna.CRITERION_FNS.betweenI;\n\nconst validateCar = madonna.createValidator({\n  year: {\n    flags: ['require', 'isInteger']\n    , betweenI: [1950, 2016]\n  }\n  , make: {\n    flags: ['require', 'isString']\n    , containedIn: ['Ford', 'Toyota', 'Chevy', 'Honda']\n  }\n  , name: {\n    flags: ['isString']\n    , custom: {\n      sizeBetween6and10: fp.flow(fp.size, betweenI([6, 10]))\n    }\n  }\n});\n\nres = validateCar({\n  year: 1950\n  , make: 'Ford'\n});\n// res.isValid === true\n\nres = validateCar({\n  year: 1949\n  , make: 'Ford'\n});\n// res.error\n// Invalid Input: The following arguments didn't pass their criterion\n// invalid arguments and values: {\n//   \"year\": 1949\n// }\n// failed criterion per argument: {\n//   \"year\": {\n//     \"betweenI\": [\n//       1950,\n//       2016\n//     ]\n//   }\n// }\n\nres = validateCar({\n  year: 1950\n  , make: 'Volvo'\n});\n// res.error\n// Invalid Input: The following arguments didn't pass their criterion\n// invalid arguments and values: {\n//   \"make\": \"Volvo\"\n// }\n// failed criterion per argument: {\n//   \"make\": {\n//     \"containedIn\": [\n//       \"Ford\",\n//       \"Toyota\",\n//       \"Chevy\",\n//       \"Honda\"\n//     ]\n//   }\n// }\n\nres = validateCar({\n  year: 1950\n  , make: 'Ford'\n  , name: 'short'\n});\n// res.error\n// Invalid Input: The following arguments didn't pass their criterion\n// invalid arguments and values: {\n//   \"name\": \"short\"\n// }\n// failed criterion per argument: {\n//   \"name\": {\n//     \"custom\": [\n//       \"sizeBetween6and10\"\n//     ]\n//   }\n// }\n\nres = validateCar({\n  year: 1950\n  , make: 'Ford'\n  , name: 'shorty'\n});\n// res.isValid === true\n```\n\n##### A complex case using the passTo and passEachTo criterion\n - Note passTo and passEachTo require named validators, which are configured via\n   [opts.name](#opts).  Either createValidator or createSternValidator can\n   be used.\n - We declare three validators: 'validateGame', 'vModifiedDeck', and 'vCard'\n - 'validateGame' is made up of two properties: 'numberOfDice' and 'modifiedDeck'\n   - The 'modifiedDeck' property passes its value to 'vModifiedDeck'\n - 'vModifiedDeck' is made up of two properties: 'name' and 'cards'\n   - The 'cards' property passes each of its values to 'vCard'\n - 'vCard' is made up of two properties: 'suit' and 'rank'\n\n```js\nconst vCard = madonna.createValidator({\n  schema: {\n    suit: {\n      flags: ['require']\n      , containedIn: ['spade', 'heart', 'diamond', 'club']\n    }\n    , rank: {\n      flags: ['require']\n      , containedIn: ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']\n    }\n  }\n  , opts: {\n    name: 'vCard'\n  }\n});\nconst vModifiedDeck = madonna.createValidator({\n  schema: {\n    name: ['isLadenString']\n    , cards: {\n      flags: ['require', 'isLaden']\n      , passEachTo: vCard\n    }\n  }\n  , opts: {\n    name: 'vModifiedDeck'\n  }\n});\nconst validateGame = madonna.createValidator({\n  numberOfDice: {\n    flags: ['isInteger']\n  }\n  , modifiedDeck: {\n    flags: ['require']\n    , passTo: vModifiedDeck\n  }\n});\n\nlet dirtyGameArgs = {\n  modifiedDeck: {\n    name: 'a quick game of war'\n    , cards: [\n      {\n        suit: 'heart'\n        , rank: '2'\n      }, {\n        suit: 'heart'\n        , rank: '3'\n      }\n    ]\n  }\n};\n\nres = validateGame(dirtyGameArgs);\n// res.isValid === true\n\n\n// what about an error case?  Let's add a card with suit 'hearts'\ndirtyGameArgs.modifiedDeck.cards.push({ suit: 'hearts', rank: '4' });\nres = validateGame(dirtyGameArgs);\n// res.error\n// Invalid Input: While validating: modifiedDeck (vModifiedDeck) -\u003e cards (vCard)\n// The following arguments didn't pass their criterion\n// invalid arguments and values: {\n//   \"suit\": \"hearts\"\n// }\n// failed criterion per argument: {\n//   \"suit\": {\n//     \"containedIn\": [\n//       \"spade\",\n//       \"heart\",\n//       \"diamond\",\n//       \"club\"\n//     ]\n//   }\n// }\n\n```\n\n*Ideally there would be many more examples.  However I'm going to wait on ironing*\n*out the documentation until I've spent more time consuming this library.*\n\n## API\n\n```js\nconst madonna = require('madonna-fp');\n```\n\n### madonna-fp\nExposes three functions:\n - [validate](#validate)\n - [createValidator](#createvalidator)\n - [createSternValidator](#createsternvalidator)\n\nAnd the following enumerable, frozen constants\n - **ERROR_IDS** an object containing the ids per [error thrown by this\n   library](#error-info).  For more information, read [Catching Errors](#catching-errors)\n - **CRITERION_FNS** an object containing a subset of [valid criterion](#valid-criterion)\n   intended to be consumed when defining custom criterion.  The list of functions\n   [can be found here](#criterion-functions).  Example usage\n   [can be found here](#more-complex-validate-a-car-object).\n\n### validate\n - Takes one or two arguments, described below\n - Returns a [result](#result)\n - This function validates the passed object per the schema and returns the result.\n\nThe first argument is required and often referred to as `marg`, which just\nstands for 'madonna argument'.  It can either represent the object schema or a\nmore verbose object encompassing both the schema and options passed.\n\n - Succinct: [schema](#schema)\n - Verbose: [`isPlainObject`](#valid-criterion) with the following properties\n   - schema *required*: [schema](#schema)\n   - opts: [opts](#opts)\n\n\nThe second argument is optional and when passed, represents the object to be\nvalidated (must pass `fp.isPlainObject`).\n\n\n\\*\\*Note: Whether you pass a verbose or succinct `marg` is determined by the\npresence of the `schema` property.  If it exists, then verbose is assumed.  If\nyou need to use a property named `schema`, then this restricts you to the\nverbose version.\n\n\n#### opts\n\n - `fp.isPlainObject` with the following optional properties\n   - name: [`isLadenString`](#valid-flags).  Used for creating named validators\n     which are required by passTo and passEachTo.\n   - cb: `fp.isFunction`.  This will be called post validation with the\n     validated arguments object as its one and only argument.  The intent of\n     this option is to further validate the arguments.  Things like asynchronous\n     and inter-dependent validations should use this callback.\n\n\n#### schema\n\n - [`isLadenPlainObject`](#valid-flags)\n - **key**: represents the name of the argument\n - **value**: Must be a valid [criterion](#criterion).\n\n\n#### criterion\n\nTakes one of two forms\n - succinct\n   - [`isLadenArray`](#valid-flags) [`allStrings`](#internal-boolean-functions)\n   - Must be contained in the set of [valid flags](#valid-flags)\n - verbose\n   - [`isLadenPlainObject`](#valid-flags)\n   - Must be contained in the set of [valid criterion](#valid-criterion)\n\n\n#### result\n\nJust an `fp.isPlainObject` with the following properties\n - isValid `fp.isBoolean`\n   - Same as `!err`\n - [err (defined below)](#err)\n\n\n#### err\n\n`fp.isError` with the following properties attached\n - id [`isLadenString`](#valid-flags)\n   - [List of possible error ids](#error-info)\n - data [`isLadenPlainObject`](#valid-flags)\n   - [List of data properties per id](#error-info)\n\n\n### validateSternly\nalias `identityValidate`\n\n - The api is almost the same as [`validate`](#validate).  The only difference\n   is that instead of returning [`result`](#result), this function throws\n   validation errors and returns the validated object.\n\n\n### createValidator\n\nThis is just a convenience function taking the first argument required by\n[`validate`](#validate) and returns a function expecting\n[`validate's`](#validate) optional second argument.\n\n\n### createSternValidator\nalias `createIdentityValidator`\n\nSimilar to [`createValidator`](#createValidator), these are just convenience\nmethods taking the first argument required by\n[`validateSternly`](#validatesternly) and returning a function expecting its\noptional second argument.\n\n\n## Catching Errors\n - MadonnaFp will return very specific errors during object validation, some of\n   which you may want to handle specific to your needs.  This library allows\n   that by attaching a property `id` to the error.  The list of ids [can be\n   found here](#error-info).  Errors caused during validator creation for\n   example, can be handled:\n\n```js\nconst invalidSchemaCriterion = madonna.ERROR_IDS.invalidCriterion\n  , nameValidator;\n\n// let's try to create a validator with an invalid criterion\ntry {\n  nameValidator = madonna.createValidator({\n    name: {\n      flags: { require: true }\n    }\n  })\n} catch(err) {\n  if (err.id === invalidSchemaCriterion) {\n\n    // do something\n  }\n}\n```\n\n## Reference\n\n### Valid Criterion\n - allContainedIn [`isLadenArray`](#valid-flags)\n   - Takes an array as input, and tests to make sure all its elements are\n     contained in the original array.\n - betweenE `fp.isArray` [`hasSize(2)`](#internal-boolean-functions)\n   [`allStringOrNumber`](#internal-boolean-functions)\n   - Input type must match the criterion type (`fp.isString` or `fp.isNumber`)\n   - Has complementary `outsideI`\n - betweenI `fp.isArray` [`hasSize(2)`](#internal-boolean-functions)\n   [`allStringOrNumber`](#internal-boolean-functions)\n   - Input type must match the criterion type (`fp.isString` or `fp.isNumber`)\n   - Has complementary `outsideE`\n - containedIn [`isLadenArray`](#valid-flags)\n - custom [`isLadenPlainObject`](#valid-flags) [`allFunctions`](#internal-boolean-functions)\n - flags [`isLadenArray`](#valid-flags) [`allStrings`](#internal-boolean-functions)\n   [`allContainedIn(\u003cvalidFlags\u003e)`](#internal-boolean-functions)\n   - [Valid flags reference](#valid-flags)\n - gt [`isStringOrNumber`](#internal-boolean-functions)\n   - Input type must match the criterion type (`fp.isString` or `fp.isNumber`)\n   - Has complementary `lte`\n - gte [`isStringOrNumber`](#internal-boolean-functions)\n   - Input type must match the criterion type (`fp.isString` or `fp.isNumber`)\n   - Has complementary `lt`\n - hasSize [`isZeroOrPositiveInteger`](#internal-boolean-functions)\n - instance_of `fp.isFunction`\n   - Has complementary `not_instance_of`\n - matchesRegex `fp.isRegex`\n   - Takes a string as input and returns whether it matches the regex\n - passTo [`isNamedValidator`](#internal-boolean-functions)\n   - Input must be `fp.isPlainObject` and will be passed to the named validator.\n   - Only the [`require`](#valid-flags) flag is allowed alongside passTo.\n   - Example usage [can be found here.](#a-complex-case-using-the-passto-and-passeachto-criterion)\n - passEachTo [`isNamedValidator`](#internal-boolean-functions)\n   - Input must be `fp.isArray` and `fp.all(fp.isPlainObject)` where each item\n     will be passed to the named validator.\n   - Only the [`require`, `isLaden` flags](#valid-flags), and the\n     [`hasSize` criterion](#valid-criterion) are allowed alongside passEachTo.\n   - Example usage [can be found here.](#a-complex-case-using-the-passto-and-passeachto-criterion)\n - type_of [`isLadenString`](#valid-flags)\n   - Has complementary `not_type_of`\n\n#### Criterion Notes\n - The 'E' and 'I' in `between` and `outside` stand for 'exclusive' and 'inclusive'\n - gt, gte, lt, and lte are taken from lodash and use the original argument\n   ordering as opposed to fp's.  This is to make the criterion read more naturally.\n - between and outside use lodash's gt/lt functions to calculate the result.\n - `passTo` and `passEachTo` are necessary because developers are lazy and don't\n   like constructors.  I believe the clean solution is to create constructors and\n   validate via instanceof, but we live in a world of json.  People don't want\n   to create new card objects prior to inserting them in a deck!  That would be\n   crazy!  They just want to write the json and pass that into whatever function\n   requires it, darn it!\n\n\n### Valid Flags\n#### Custom to this library\n - require\n - isCharacter\n   - `fp.allPass([fp.isString, hasSize(1)])`\n - isDefined\n   - `fp.negate(fp.isUndefined)`\n - isLaden\n   - `fp.size`\n - isLadenArray:\n   - `fp.allPass([fp.size, fp.isArray])`\n - isLadenPlainObject\n   - `fp.allPass([fp.size, fp.isPlainObject])`\n - isLadenString\n   - `fp.allPass([fp.size, fp.isString])`\n - isPositiveNumber\n```js\nfp.anyPass([\n fp.allPass([fp.isNumber, fp.inRange(1, Infinity)])\n , fp.eq(Infinity)\n])\n```\n\n - isNegativeNumber\n   - `fp.allPass([fp.isNumber, fp.inRange(-Infinity, 0)])`\n - isStringOrNumber\n   - `fp.anyPass([fp.isString, fp.isNumber])`\n\n#### Unary lodash functions exposed\nAll the below strings also have complementary 'isNot' flags\n - isArguments\n - isArray\n - isArrayBuffer\n - isArrayLike\n - isArrayLikeObject\n - isBoolean\n - isBuffer\n - isDate\n - isElement\n - isEmpty\n - isEqual\n - isEqualWith\n - isError\n - isFinite\n - isFunction\n - isInteger\n - isLength\n - isMap\n - isNaN\n - isNative\n - isNil\n - isNull\n - isNumber\n - isObject\n - isObjectLike\n - isPlainObject\n - isRegExp\n - isSafeInteger\n - isSet\n - isString\n - isSymbol\n - isTypedArray\n - isUndefined\n - isWeakMap\n - isWeakSet\n\n## Internal boolean functions\n - allContainedIn\n   - A curried function taking two arrays, testing whether the second array has only\n     values contained in the first.\n\n```js\nconst allContainedIn = fp.curry((srcArr, arrToTest) =\u003e {\n  return !fp.without(arrToTest, srcArr).length;\n});\n```\n\n - allFunctions\n   - `fp.all(fp.isFunction)`\n - allStringOrNumber\n   - `fp.anyPass([ fp.all(fp.isString), fp.all(fp.isNumber) ])`\n - allStrings\n   - `fp.all(fp.isString)`\n - isZeroOrPositiveInteger\n   - `fp.allPass([ fp.isInteger, betweenI([0, Infinity]) ])`\n - isLadenArray\n   - `fp.allPass([ fp.isArray, fp.size ])`\n - isLadenPlainObject\n   - `fp.allPass([ fp.isPlainObject, fp.size ])`\n - isLadenString\n   - `fp.allPass([ fp.isString, fp.size ])`\n - isNamedValidatorFn\n   - A function taking one parameter that must pass\n     - isFunction\n     - has property `_id` === 'madonnafp'\n     - has property `_name` passing isLadenString\n\n```js\nfp.allPass([\n  fp.isFunction\n  , fp.flow(\n    fp.get('_id')\n    , fp.eq('madonnafp')\n  ), fp.flow(\n    fp.get('name')\n    , fp.isString\n  )\n])\n```\n\n - isStringOrNumber\n   - `fp.anyPass([ fp.isString, fp.isNumber ])`\n\n\n## Error Info\n\nThe following are error ids with their corresponding data properties.  When\ncalling stern validations, these errors are thrown, otherwise they are returned\nvia the [`result object`](#result).\n\nErrors during object validation, accessible via `madonna.ERROR_IDS`\n - atMostOneArgument: argsLength, args\n - argNotIsPlainObject: type_of, arg\n - invalidArgKeys: invalidKeys, keysAllowed\n - missingRequiredKeys: keysMissing, passedArgs\n - criterionFailed: invalidArgs, failedCriterion\n\nErrors thrown during [**cb**](#createvalidator) that want to pass the error info\ndown to consumers should define their own id and data object.  If you just want\npeople to know an error happened in cb as opposed to when validating against the\nschema, then setting 'id' to the string 'cb' seems like a reasonable convention.\n\n\n## Criterion Functions\n\nThese are the curried functions exposed by `madonna.CRITERION_FNS`.  Keep in\nmind criterion input is not validated when used inside the 'custom' criterion.\n - allContainedIn\n - betweenE\n - betweenI\n - containedIn\n - gt\n - gte\n - hasSize\n - instance_of\n - lt\n - lte\n - not_instance_of\n - not_type_of\n - outsideE\n - outsideI\n - type_of\n\n# Test\n`npm test`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Folsonpm%2Fmadonna-fp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Folsonpm%2Fmadonna-fp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Folsonpm%2Fmadonna-fp/lists"}