{"id":42880966,"url":"https://github.com/thislooksfun/validate-structure","last_synced_at":"2026-01-30T14:32:47.839Z","repository":{"id":38173722,"uuid":"373544392","full_name":"thislooksfun/validate-structure","owner":"thislooksfun","description":null,"archived":false,"fork":false,"pushed_at":"2023-01-07T08:03:53.000Z","size":634,"stargazers_count":1,"open_issues_count":12,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-30T03:47:40.017Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/thislooksfun.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["thislooksfun"]}},"created_at":"2021-06-03T14:53:14.000Z","updated_at":"2024-09-12T13:20:19.000Z","dependencies_parsed_at":"2023-02-06T15:31:31.955Z","dependency_job_id":null,"html_url":"https://github.com/thislooksfun/validate-structure","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/thislooksfun/validate-structure","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thislooksfun%2Fvalidate-structure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thislooksfun%2Fvalidate-structure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thislooksfun%2Fvalidate-structure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thislooksfun%2Fvalidate-structure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thislooksfun","download_url":"https://codeload.github.com/thislooksfun/validate-structure/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thislooksfun%2Fvalidate-structure/sbom","scorecard":{"id":881727,"data":{"date":"2025-08-11","repo":{"name":"github.com/thislooksfun/validate-structure","commit":"98652ffc3f2c1f44f9ee2b35237459be15fa830b"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.1,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/28 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/commitlint.yaml:1","Warn: no topLevel permission defined: .github/workflows/release.yaml:1","Warn: no topLevel permission defined: .github/workflows/test.yaml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":4,"reason":"dependency not pinned by hash detected -- score normalized to 4","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/commitlint.yaml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/commitlint.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/commitlint.yaml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/commitlint.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yaml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/release.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yaml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/release.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yaml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yaml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/release.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yaml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/test.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yaml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/test.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yaml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/thislooksfun/validate-structure/test.yaml/main?enable=pin","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   4 third-party GitHubAction dependencies pinned","Info:   3 out of   3 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":10,"reason":"project is fuzzed","details":["Info: TypeScriptPropertyBasedTesting integration found: test/helpers.test.ts:1","Info: TypeScriptPropertyBasedTesting integration found: test/matches.test.ts:1","Info: TypeScriptPropertyBasedTesting integration found: test/types.test.ts:1","Info: TypeScriptPropertyBasedTesting integration found: test/validate.test.ts:1","Info: TypeScriptPropertyBasedTesting integration found: test/helpers.test.ts:1","Info: TypeScriptPropertyBasedTesting integration found: test/matches.test.ts:1","Info: TypeScriptPropertyBasedTesting integration found: test/types.test.ts:1","Info: TypeScriptPropertyBasedTesting integration found: test/validate.test.ts:1"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v2.0.0 not signed: https://api.github.com/repos/thislooksfun/validate-structure/releases/45068240","Warn: release artifact v1.3.0 not signed: https://api.github.com/repos/thislooksfun/validate-structure/releases/45060089","Warn: release artifact v1.2.0 not signed: https://api.github.com/repos/thislooksfun/validate-structure/releases/44125063","Warn: release artifact v1.1.1 not signed: https://api.github.com/repos/thislooksfun/validate-structure/releases/44085923","Warn: release artifact v1.1.0 not signed: https://api.github.com/repos/thislooksfun/validate-structure/releases/44073341","Warn: release artifact v2.0.0 does not have provenance: https://api.github.com/repos/thislooksfun/validate-structure/releases/45068240","Warn: release artifact v1.3.0 does not have provenance: https://api.github.com/repos/thislooksfun/validate-structure/releases/45060089","Warn: release artifact v1.2.0 does not have provenance: https://api.github.com/repos/thislooksfun/validate-structure/releases/44125063","Warn: release artifact v1.1.1 does not have provenance: https://api.github.com/repos/thislooksfun/validate-structure/releases/44085923","Warn: release artifact v1.1.0 does not have provenance: https://api.github.com/repos/thislooksfun/validate-structure/releases/44073341"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"43 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-2h3h-q99f-3fhc","Warn: Project is vulnerable to: GHSA-gmw6-94gg-2rc2","Warn: Project is vulnerable to: GHSA-h5c3-5r3r-rr8q","Warn: Project is vulnerable to: GHSA-rmvr-2pp2-xj38","Warn: Project is vulnerable to: GHSA-xx4v-prfh-6cgc","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-5v2h-r2cx-5xgj","Warn: Project is vulnerable to: GHSA-rrrm-qjm4-v8hf","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-hj9c-8jmm-8c52","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-x2pg-mjhr-2m5x","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-44c6-4v22-4mhx","Warn: Project is vulnerable to: GHSA-4x5v-gmq8-25ch","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-38fc-wpqx-33j7","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T08:28:23.007Z","repository_id":38173722,"created_at":"2025-08-24T08:28:23.007Z","updated_at":"2025-08-24T08:28:23.007Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28914244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T12:13:43.263Z","status":"ssl_error","status_checked_at":"2026-01-30T12:13:22.389Z","response_time":66,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":"2026-01-30T14:32:46.989Z","updated_at":"2026-01-30T14:32:47.832Z","avatar_url":"https://github.com/thislooksfun.png","language":"TypeScript","funding_links":["https://github.com/sponsors/thislooksfun"],"categories":[],"sub_categories":[],"readme":"# validate-structure\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/semantic-release/semantic-release\"\n    \u003e\u003cimg\n      alt=\"semantic release\"\n      src=\"https://flat.badgen.net/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80/semantic%20release/e10079\"\n    /\u003e\u003c/a\n  \u003e\n  \u003ca href=\"https://github.com/thislooksfun/validate-structure/releases/latest\"\n    \u003e\u003cimg\n      alt=\"latest release\"\n      src=\"https://flat.badgen.net/github/release/thislooksfun/validate-structure\"\n    /\u003e\u003c/a\n  \u003e\n  \u003ca href=\"https://github.com/thislooksfun/validate-structure/releases\"\n    \u003e\u003cimg\n      alt=\"latest stable release\"\n      src=\"https://flat.badgen.net/github/release/thislooksfun/validate-structure/stable\"\n    /\u003e\u003c/a\n  \u003e\n  \u003ca href=\"#\"\n    \u003e\u003cimg\n      alt=\"checks status\"\n      src=\"https://flat.badgen.net/github/checks/thislooksfun/validate-structure\"\n    /\u003e\u003c/a\n  \u003e\n  \u003ca href=\"https://app.codecov.io/gh/thislooksfun/validate-structure\"\n    \u003e\u003cimg\n      alt=\"coverage\"\n      src=\"https://flat.badgen.net/codecov/c/github/thislooksfun/validate-structure\"\n    /\u003e\u003c/a\n  \u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/validate-structure?activeTab=versions\"\n    \u003e\u003cimg\n      alt=\"npm version\"\n      src=\"https://flat.badgen.net/npm/v/validate-structure\"\n    /\u003e\u003c/a\n  \u003e\n  \u003ca href=\"https://github.com/thislooksfun/validate-structure/tree/master/types\"\n    \u003e\u003cimg\n      alt=\"npm types\"\n      src=\"https://flat.badgen.net/npm/types/validate-structure\"\n    /\u003e\u003c/a\n  \u003e\n  \u003ca href=\"https://www.npmjs.com/package/validate-structure\"\n    \u003e\u003cimg\n      alt=\"weekly npm downloads\"\n      src=\"https://flat.badgen.net/npm/dw/validate-structure\"\n    /\u003e\u003c/a\n  \u003e\n  \u003ca href=\"https://www.npmjs.com/package/validate-structure?activeTab=dependents\"\n    \u003e\u003cimg\n      alt=\"npm dependents\"\n      src=\"https://flat.badgen.net/npm/dependents/validate-structure\"\n    /\u003e\u003c/a\n  \u003e\n  \u003ca href=\"https://github.com/thislooksfun/validate-structure/blob/master/LICENSE\"\n    \u003e\u003cimg\n      alt=\"license\"\n      src=\"https://flat.badgen.net/github/license/thislooksfun/validate-structure\"\n    /\u003e\u003c/a\n  \u003e\n\u003c/p\u003e\n\nCheck that an object matches the expected structure.\n\n## Installation\n\n```sh\nnpm i validate-structure\n```\n\nNote: Node 14+ is required.\n\n## Usage\n\n### Functions\n\n#### validateStructure()\n\nSignature: `validateStructure(val: any, structure: Structure, strict?: boolean, customTypes?: TypeDefs): ValidationError[]`\n\nArguments:\n\n1. `val` The value to validate.\n2. `structure` The expected structure of `val`. This must be a\n   [Structure](#structure).\n3. (optional) `strict` Whether or not extra keys should be treated as\n   a failure (defaults to `true`)\n4. (optional) `customTypes` Any custom types that you want to refer to\n   multiple times can be placed here for convenience. This should be an object\n   where each key is the name of the type and the value is a\n   [Structure](#structure).\n\n#### matchesStructure()\n\nSignature: `matchesStructure(val: any, structure: Structure, strict?: boolean, customTypes?: TypeDefs): boolean`\n\nThis is a simple wrapper around `validateStructure()` that returns `true` if and\nonly if there were no errors returned by `validateStructure()`. It can be used\nif you only care _that_ it doesn't match, but if you need to know _what_ didn't\nmatch you should use `validateStructure()` directly.\n\n### Structure\n\n`validate-structure` is built around a robust type system, which means it can\nhandle any data structure you throw at it.\n\n#### Basic Structure\n\nThe most basic Structure is a single string representing a native type. The\nfollowing `typeof`s are supported out-of-the-box:\n\n1. `\"any\"` (matches anything except `null` and `undefined`)\n1. `\"null\"`\n1. `\"undefined\"` (note that this still requires the key to be present)\n1. `\"boolean\"`\n1. `\"number\"`\n1. `\"bigint\"`\n1. `\"string\"`\n1. `\"symbol\"`\n1. `\"function\"`\n\nThere are also some non-`typeof` checks provided as well:\n\n1. `\"int\"` Matches any integer\n1. `\"array\"` Matches any array\n1. `\"object\"` Matches any object that is not an array or `null`\n\n#### Operators\n\nSometimes you might find that just the basic types aren't enough. That's why\n`validate-structure` has a range of operators you can use to form complex\nStructures.\n\n1. `\"\u003ctype\u003e?\"` Optional: matches `\"\u003ctype\u003e\"` or `null` or `undefined`.\n1. `\"\u003ctype\u003e[]\"` Array: matches an array of `\"\u003ctype\u003e\"`.\n   - To ensure that an array's length is valid, use `\u003ctype\u003e[min...max]`\n   - If you need to enforce a minimum length you can leave off max: `\u003ctype\u003e[min...]`\n   - Likewise for a maximum length: `\u003ctype\u003e[...max]`\n   - If your array has a fixed length (`min === max`) you can use the shorthand\n     form `\u003ctype\u003e[N]`\n   - If the array can't be empty, you can either use `\u003ctype\u003e[1...]` or the\n     shorthand form `\u003ctype\u003e[.]`\n   - If null values are allowed inside the array, put a `?` between the `\u003ctype\u003e` and the `[` (like so: `int?[]`).\n1. `\"[\u003ctype 1\u003e, ..., \u003ctype n\u003e]\"` Tuple: matches an array of `\"\u003ctype\u003e\"`s with\n   strict length. For example, if you have an image size stored as a pair of\n   integers you can validate that with `\"[int, int]\"`.\n1. `\u003ctype 1\u003e | ... | \u003ctype n\u003e` Union: matches at least one of the `\"\u003ctype\u003e\"`s.\n1. `(\u003ctype\u003e)` Group: used to group types together. For instance the structure\n   `(string | int)[]` will match an array where each item is either a string or\n   an integer.\n\n#### Objects\n\nIf your data can't be represented by one of the basic types you can use an\nobject structure. It should exactly match the expected structure with a few\nexceptions:\n\n1. If a value is optional you can append a `'?'` to the key. For example:\n   `{ 'optional?': 'string' }`.\n1. If a value should be an array you can use the array syntax above (`[]`,\n   `[min...max]`, `[min...]`, `[...max]`, `[.]`, and `[N]`).\n   For example: `{ 'arr[]': 'string' }`.\n1. If a value should be a tuple, you can use an array of values. For example:\n   `{ size: [\"int\", \"int\"] }`.\n\n#### Custom types\n\nIf you have a complex type that you would like to reuse in multiple places you\ncan specify it as a custom type. It also allows for recursion.\n\n```ts\nconst types = {\n  Person: {\n    name: \"Name\",\n    \"parents[]?\": \"Person\",\n    \"spouse?\": \"Person\",\n    \"children[]?\": \"Person\",\n  },\n\n  Name: { first: \"string\", last: \"string\" },\n};\n\nvalidateStructure(person, \"Person\", true, types);\n```\n\n#### Functions\n\nIf you have a use case that is not satisfied by the aforementioned methods, you\ncan write a custom validator function. These come in two flavors and can be used\nanywhere a Structure can.\n\n1. Matcher (`type MatcherFn = (val: any) =\u003e boolean`).\n   This is the simpler of the two. It takes in a value and should return `true`\n   if the value matches and `false` if it doesn't.\n1. Validator (`type ValidatorFn = (val: any, path: string, types: TypeValidators, strict: boolean) =\u003e ValidationError[]`).\n   This is more complicated, but gives much more control. The arguments are as follows:\n\n   1. `val` The value to validate.\n   1. `path` The keypath of the value in dot notation, used for error messages.\n   1. `types` A key:value map of other `ValidatorFn`s. For example, `types.array`\n      is the `ValidatorFn` that matches any array. This includes matchers for any\n      [custom types](#custom-types) you have defined.\n   1. `strict` Whether or not the validation is running in strict mode. See\n      [`validateStructure()`](#validatestructure) for details.\n\n   The return type is an array of `ValidationError` objects. Each\n   `ValidationError` is an object consisting of three keys: `msg`, `path`, and\n   `type`. `msg` is a string explaining what the error is, `path` is the path to\n   the invalid item, and `type` is the type of the error. The default types are\n   `\"key\"`, `\"val-start\"` and `\"val-end\"`, depending on where the mismatch was,\n   but any string may be used. Here is an example error:\n   `{ msg: 'array must not be empty', path: ['arr'], type: \"val-start\" }`.\n\n   Here is an example to check if a value is a string that starts with a `$`:\n\n   ```ts\n   import type { ValidatorFn } from \"validate-structure\";\n   import { validateStructure, buildError } from \"validate-structure\";\n\n   const dollarString: ValidatorFn = (val, path, types, strict) =\u003e {\n     // Check if the value is a string\n     const errors = types.string(val, path, types, strict);\n     if (errors.length \u003e 0) return errors;\n\n     // The value is fine, return no errors.\n     if (val.startsWith(\"$\")) return [];\n\n     // The value is invalid, return an error\n     return buildError(`'${val}' does not start with a '$'`, path);\n   };\n\n   validateStructure(\"$12\", dollarString); // -\u003e []\n   validateStructure(\"12\", dollarString); // -\u003e [{msg: \"'12' does not start with a '$'\", path: \"\"}]\n   validateStructure({ price: \"12\" }, { price: dollarString }); // -\u003e [{msg: \"'12' does not start with a '$'\", path: \"price\"}]\n   ```\n\n### Examples\n\n```ts\nimport { matchesStructure } from \"validate-structure\";\n\n// A single string\nmatchesStructure(\"hello world\", \"string\"); // -\u003e true\n\n// A single integer\nmatchesStructure(14, \"int\"); // -\u003e true\nmatchesStructure(14.2, \"int\"); // -\u003e false\n\n// An array of numbers\nmatchesStructure([], \"number[]\"); // -\u003e true\nmatchesStructure([14], \"number[]\"); // -\u003e true\nmatchesStructure([1, 2, 3, 4, 5], \"number[]\"); // -\u003e true\n\n// A tuple of 2 numbers\nconst sizeTuple = \"[number, number]\"; // This could also be written \"number[2]\"\nmatchesStructure([1, 2], sizeTuple); // -\u003e true\nmatchesStructure([1], sizeTuple); // -\u003e false\nmatchesStructure([1, 2, 3], sizeTuple); // -\u003e false\n\n// A tuple of a string and an int\nconst ruleTuple = \"[string, int]\";\nmatchesStructure([\"tabLength\", 2], sizeTuple); // -\u003e true\nmatchesStructure([14, 2], sizeTuple); // -\u003e false\n\n// A custom object structure\nconst structure = { id: \"int\", name: \"string\" };\nmatchesStructure({ id: 14, name: \"John\" }, structure); // -\u003e true\n\n// Strict mode is on by default\nmatchesStructure({ id: 14, name: \"John\" }, { id: \"int\" }); // -\u003e false\nmatchesStructure({ id: 14, name: \"John\" }, { id: \"int\" }, false); // -\u003e true\n\n// Complex structures\nconst structure = {\n  name: \"string\",\n  tabs: {\n    name: \"string\",\n    \"color?\": \"string\",\n    \"contents[.]\": {\n      \"title?\": \"string\",\n      body: \"string\",\n    },\n  },\n};\n\nmatchesStructure(\n  {\n    name: \"Home\",\n    tabs: {\n      name: \"About\",\n      contents: [\n        { title: \"About Us\", body: \"Lorem ipsum dolor sit amet\" },\n        { body: \"consectetur adipiscing elit\" },\n      ],\n    },\n  },\n  structure\n); // -\u003e true\n\nmatchesStructure(\n  {\n    name: \"Home\",\n    tabs: {\n      name: \"About\",\n      contents: [],\n    },\n  },\n  structure\n); // -\u003e false (tabs.contents must be non-empty)\n```\n\n## Contributing\n\nIf you want to help out, please read the [CONTRIBUTING.md][c.md].\n\n\u003c!-- Links --\u003e\n\n[c.md]: https://github.com/thislooksfun/validate-structure/blob/master/CONTRIBUTING.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthislooksfun%2Fvalidate-structure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthislooksfun%2Fvalidate-structure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthislooksfun%2Fvalidate-structure/lists"}