@versionzero/schema 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +177 -0
- package/README.md +246 -0
- package/package.json +84 -0
- package/src/compilation/handler-compilation.js +28 -0
- package/src/compilation/metadata-compilation.js +35 -0
- package/src/compilation/schema-compilation.js +142 -0
- package/src/compilation/selection-compilation.js +84 -0
- package/src/compilation/union-compilation.js +510 -0
- package/src/compilation/values-compilation.js +35 -0
- package/src/compiled-schema.js +1709 -0
- package/src/constants.js +1 -0
- package/src/core-library/index.js +32 -0
- package/src/core-library/processors/aggregation-operators.js +75 -0
- package/src/core-library/processors/alpha-constraint.js +20 -0
- package/src/core-library/processors/alphanum-constraint.js +20 -0
- package/src/core-library/processors/array-operator.js +51 -0
- package/src/core-library/processors/assert-constraint.js +75 -0
- package/src/core-library/processors/base64-constraint.js +26 -0
- package/src/core-library/processors/camel-case-operator.js +24 -0
- package/src/core-library/processors/capitalize-operator.js +16 -0
- package/src/core-library/processors/cardnum-constraint.js +193 -0
- package/src/core-library/processors/ceil-operator.js +44 -0
- package/src/core-library/processors/collapse-operator.js +29 -0
- package/src/core-library/processors/compact-operator.js +34 -0
- package/src/core-library/processors/compile-operator.js +65 -0
- package/src/core-library/processors/concat-operator.js +51 -0
- package/src/core-library/processors/conditional-operators.js +301 -0
- package/src/core-library/processors/constant-case-operator.js +16 -0
- package/src/core-library/processors/data-size-operator.js +86 -0
- package/src/core-library/processors/date-object-operator.js +54 -0
- package/src/core-library/processors/date-operator.js +67 -0
- package/src/core-library/processors/date-range-constraint.js +76 -0
- package/src/core-library/processors/defined-constraint.js +30 -0
- package/src/core-library/processors/each-operator.js +57 -0
- package/src/core-library/processors/email-constraint.js +112 -0
- package/src/core-library/processors/entries-operator.js +25 -0
- package/src/core-library/processors/eq-constraint.js +37 -0
- package/src/core-library/processors/filter-operator.js +74 -0
- package/src/core-library/processors/find-schema-operator.js +45 -0
- package/src/core-library/processors/flatten-operator.js +40 -0
- package/src/core-library/processors/floor-operator.js +47 -0
- package/src/core-library/processors/get-operator.js +44 -0
- package/src/core-library/processors/group-by-operator.js +84 -0
- package/src/core-library/processors/has-prefix-constraint.js +37 -0
- package/src/core-library/processors/has-suffix-constraint.js +35 -0
- package/src/core-library/processors/hex-constraint.js +20 -0
- package/src/core-library/processors/hostname-constraint.js +22 -0
- package/src/core-library/processors/http-url-constraint.js +27 -0
- package/src/core-library/processors/in-constraint.js +66 -0
- package/src/core-library/processors/index-by-operator.js +98 -0
- package/src/core-library/processors/index.js +131 -0
- package/src/core-library/processors/input-operator.js +23 -0
- package/src/core-library/processors/instanceof-constraint.js +38 -0
- package/src/core-library/processors/integer-constraint.js +22 -0
- package/src/core-library/processors/invoke-operator.js +33 -0
- package/src/core-library/processors/ipv4-constraint.js +188 -0
- package/src/core-library/processors/ipv6-constraint.js +205 -0
- package/src/core-library/processors/is-array-constraint.js +21 -0
- package/src/core-library/processors/is-date-constraint.js +22 -0
- package/src/core-library/processors/is-number-constraint.js +21 -0
- package/src/core-library/processors/is-object-constraint.js +21 -0
- package/src/core-library/processors/is-string-constraint.js +21 -0
- package/src/core-library/processors/join-operator.js +41 -0
- package/src/core-library/processors/json-constraint.js +22 -0
- package/src/core-library/processors/json-decode-operator.js +25 -0
- package/src/core-library/processors/json-encode-operator.js +35 -0
- package/src/core-library/processors/kebab-case-operator.js +23 -0
- package/src/core-library/processors/keys-operator.js +20 -0
- package/src/core-library/processors/length-constraint.js +85 -0
- package/src/core-library/processors/lookup-operator.js +84 -0
- package/src/core-library/processors/lowercase-operator.js +14 -0
- package/src/core-library/processors/map-operator.js +84 -0
- package/src/core-library/processors/match-operator.js +64 -0
- package/src/core-library/processors/matches-constraint.js +54 -0
- package/src/core-library/processors/math-operators.js +151 -0
- package/src/core-library/processors/merge-deep-operator.js +61 -0
- package/src/core-library/processors/merge-operator.js +54 -0
- package/src/core-library/processors/metadata-operator.js +100 -0
- package/src/core-library/processors/negative-constraint.js +23 -0
- package/src/core-library/processors/never-constraint.js +69 -0
- package/src/core-library/processors/non-empty-constraint.js +59 -0
- package/src/core-library/processors/not-constraint.js +71 -0
- package/src/core-library/processors/number-operator.js +24 -0
- package/src/core-library/processors/numeric-constraint.js +22 -0
- package/src/core-library/processors/object-operator.js +38 -0
- package/src/core-library/processors/omit-operator.js +57 -0
- package/src/core-library/processors/parallel-operator.js +64 -0
- package/src/core-library/processors/pascal-case-operator.js +16 -0
- package/src/core-library/processors/phone-constraint.js +235 -0
- package/src/core-library/processors/pick-operator.js +62 -0
- package/src/core-library/processors/pipeline-operator.js +63 -0
- package/src/core-library/processors/port-constraint.js +22 -0
- package/src/core-library/processors/positive-constraint.js +23 -0
- package/src/core-library/processors/process-operator.js +55 -0
- package/src/core-library/processors/property-operator.js +49 -0
- package/src/core-library/processors/range-constraint.js +72 -0
- package/src/core-library/processors/reference-operator.js +79 -0
- package/src/core-library/processors/require-constraint.js +74 -0
- package/src/core-library/processors/reverse-operator.js +20 -0
- package/src/core-library/processors/round-operator.js +53 -0
- package/src/core-library/processors/schema-handler-operators.js +54 -0
- package/src/core-library/processors/semver-constraint.js +282 -0
- package/src/core-library/processors/sequence-processors.js +406 -0
- package/src/core-library/processors/sort-operator.js +52 -0
- package/src/core-library/processors/split-operator.js +43 -0
- package/src/core-library/processors/string-extra-operators.js +141 -0
- package/src/core-library/processors/string-operator.js +34 -0
- package/src/core-library/processors/target-operator.js +30 -0
- package/src/core-library/processors/template-operator.js +60 -0
- package/src/core-library/processors/title-case-operator.js +17 -0
- package/src/core-library/processors/trim-operator.js +14 -0
- package/src/core-library/processors/truthy-constraint.js +35 -0
- package/src/core-library/processors/type-operator.js +24 -0
- package/src/core-library/processors/unique-operator.js +21 -0
- package/src/core-library/processors/uppercase-operator.js +14 -0
- package/src/core-library/processors/url-constraint.js +31 -0
- package/src/core-library/processors/url-decode-operator.js +50 -0
- package/src/core-library/processors/url-encode-operator.js +44 -0
- package/src/core-library/processors/uuid-constraint.js +31 -0
- package/src/core-library/processors/values-operator.js +20 -0
- package/src/core-library/schemas/any-schema.js +23 -0
- package/src/core-library/schemas/array-schema.js +8 -0
- package/src/core-library/schemas/boolean-schema.js +10 -0
- package/src/core-library/schemas/date-schema.js +12 -0
- package/src/core-library/schemas/function-schema.js +40 -0
- package/src/core-library/schemas/number-schema.js +9 -0
- package/src/core-library/schemas/object-schema.js +10 -0
- package/src/core-library/schemas/root-schema.js +21 -0
- package/src/core-library/schemas/string-schema.js +9 -0
- package/src/core-library-node/index.js +47 -0
- package/src/core-library-node/processors/base64-decode-operator.js +20 -0
- package/src/core-library-node/processors/base64-encode-operator.js +20 -0
- package/src/core-library-node/processors/buffer-operator.js +39 -0
- package/src/core-library-node/processors/directory-constraint.js +35 -0
- package/src/core-library-node/processors/executable-constraint.js +34 -0
- package/src/core-library-node/processors/file-constraint.js +34 -0
- package/src/core-library-node/processors/file-size-constraint.js +94 -0
- package/src/core-library-node/processors/is-buffer-constraint.js +21 -0
- package/src/core-library-node/processors/reachable-constraint.js +28 -0
- package/src/core-library-node/processors/readable-constraint.js +34 -0
- package/src/core-library-node/processors/writable-constraint.js +59 -0
- package/src/core-library-node/schemas/buffer-schema.js +10 -0
- package/src/errors.js +209 -0
- package/src/executor/array-executor.js +78 -0
- package/src/executor/conditional-executor.js +134 -0
- package/src/executor/each-executor.js +68 -0
- package/src/executor/executor.js +123 -0
- package/src/executor/object-executor.js +98 -0
- package/src/executor/parallel-executor.js +43 -0
- package/src/executor/pipeline-executor.js +65 -0
- package/src/executor/sequence-executor.js +206 -0
- package/src/executor/serial-executor.js +24 -0
- package/src/executor/step-executor.js +68 -0
- package/src/helpers/case.js +124 -0
- package/src/helpers/data-size.js +144 -0
- package/src/helpers/debug-sink.js +15 -0
- package/src/helpers/deep.js +280 -0
- package/src/helpers/format.js +121 -0
- package/src/helpers/has-string-properties.js +30 -0
- package/src/helpers/index.js +16 -0
- package/src/helpers/object.js +115 -0
- package/src/helpers/parse-date.js +75 -0
- package/src/helpers/path.js +28 -0
- package/src/helpers/regex.js +18 -0
- package/src/helpers/stringify.js +309 -0
- package/src/helpers/to-data.js +64 -0
- package/src/helpers/truthy.js +55 -0
- package/src/index.js +29 -0
- package/src/schema-compiler.js +531 -0
- package/src/schema-location.js +200 -0
- package/src/schema-resolver.js +546 -0
- package/src/schema.js +1182 -0
- package/src/traversal/executors/check-condition.js +42 -0
- package/src/traversal/executors/check-input.js +27 -0
- package/src/traversal/executors/check-required.js +19 -0
- package/src/traversal/executors/check-schema.js +45 -0
- package/src/traversal/executors/defaults.js +21 -0
- package/src/traversal/executors/enter-existing.js +25 -0
- package/src/traversal/executors/enter-input.js +25 -0
- package/src/traversal/executors/enter.js +37 -0
- package/src/traversal/executors/exit.js +74 -0
- package/src/traversal/executors/finalize.js +64 -0
- package/src/traversal/executors/index.js +42 -0
- package/src/traversal/executors/normalize.js +38 -0
- package/src/traversal/executors/prepare-existing.js +27 -0
- package/src/traversal/executors/prepare-pending.js +54 -0
- package/src/traversal/executors/resolve-union.js +50 -0
- package/src/traversal/executors/serialize.js +48 -0
- package/src/traversal/executors/transform-early.js +51 -0
- package/src/traversal/executors/transform.js +68 -0
- package/src/traversal/executors/traversal-state-executor.js +46 -0
- package/src/traversal/executors/validate.js +63 -0
- package/src/traversal/traversal-context.js +231 -0
- package/src/traversal/traversal-state.js +809 -0
- package/src/types.js +102 -0
- package/src/value-processor/composed-value-processor.js +43 -0
- package/src/value-processor/defined-value-processor.js +72 -0
- package/src/value-processor/function-value-processor.js +68 -0
- package/src/value-processor/parameterized-value-processor.js +45 -0
- package/src/value-processor/parameters-value-processor.js +178 -0
- package/src/value-processor/spec.js +89 -0
- package/src/value-processor/value-processor.js +105 -0
- package/types/compilation/handler-compilation.d.ts +13 -0
- package/types/compilation/metadata-compilation.d.ts +6 -0
- package/types/compilation/schema-compilation.d.ts +32 -0
- package/types/compilation/selection-compilation.d.ts +9 -0
- package/types/compilation/union-compilation.d.ts +42 -0
- package/types/compilation/values-compilation.d.ts +12 -0
- package/types/compiled-schema.d.ts +883 -0
- package/types/constants.d.ts +1 -0
- package/types/core-library/index.d.ts +7 -0
- package/types/core-library/processors/aggregation-operators.d.ts +24 -0
- package/types/core-library/processors/alpha-constraint.d.ts +9 -0
- package/types/core-library/processors/alphanum-constraint.d.ts +9 -0
- package/types/core-library/processors/array-operator.d.ts +12 -0
- package/types/core-library/processors/assert-constraint.d.ts +30 -0
- package/types/core-library/processors/base64-constraint.d.ts +11 -0
- package/types/core-library/processors/camel-case-operator.d.ts +17 -0
- package/types/core-library/processors/capitalize-operator.d.ts +11 -0
- package/types/core-library/processors/cardnum-constraint.d.ts +51 -0
- package/types/core-library/processors/ceil-operator.d.ts +30 -0
- package/types/core-library/processors/collapse-operator.d.ts +24 -0
- package/types/core-library/processors/compact-operator.d.ts +29 -0
- package/types/core-library/processors/compile-operator.d.ts +34 -0
- package/types/core-library/processors/concat-operator.d.ts +23 -0
- package/types/core-library/processors/conditional-operators.d.ts +219 -0
- package/types/core-library/processors/constant-case-operator.d.ts +9 -0
- package/types/core-library/processors/data-size-operator.d.ts +31 -0
- package/types/core-library/processors/date-object-operator.d.ts +16 -0
- package/types/core-library/processors/date-operator.d.ts +21 -0
- package/types/core-library/processors/date-range-constraint.d.ts +26 -0
- package/types/core-library/processors/defined-constraint.d.ts +20 -0
- package/types/core-library/processors/each-operator.d.ts +34 -0
- package/types/core-library/processors/email-constraint.d.ts +54 -0
- package/types/core-library/processors/entries-operator.d.ts +13 -0
- package/types/core-library/processors/eq-constraint.d.ts +20 -0
- package/types/core-library/processors/filter-operator.d.ts +35 -0
- package/types/core-library/processors/find-schema-operator.d.ts +28 -0
- package/types/core-library/processors/flatten-operator.d.ts +26 -0
- package/types/core-library/processors/floor-operator.d.ts +33 -0
- package/types/core-library/processors/get-operator.d.ts +31 -0
- package/types/core-library/processors/group-by-operator.d.ts +36 -0
- package/types/core-library/processors/has-prefix-constraint.d.ts +22 -0
- package/types/core-library/processors/has-suffix-constraint.d.ts +20 -0
- package/types/core-library/processors/hex-constraint.d.ts +9 -0
- package/types/core-library/processors/hostname-constraint.d.ts +11 -0
- package/types/core-library/processors/http-url-constraint.d.ts +9 -0
- package/types/core-library/processors/in-constraint.d.ts +27 -0
- package/types/core-library/processors/index-by-operator.d.ts +26 -0
- package/types/core-library/processors/index.d.ts +8 -0
- package/types/core-library/processors/input-operator.d.ts +20 -0
- package/types/core-library/processors/instanceof-constraint.d.ts +23 -0
- package/types/core-library/processors/integer-constraint.d.ts +9 -0
- package/types/core-library/processors/invoke-operator.d.ts +12 -0
- package/types/core-library/processors/ipv4-constraint.d.ts +37 -0
- package/types/core-library/processors/ipv6-constraint.d.ts +34 -0
- package/types/core-library/processors/is-array-constraint.d.ts +10 -0
- package/types/core-library/processors/is-date-constraint.d.ts +10 -0
- package/types/core-library/processors/is-number-constraint.d.ts +10 -0
- package/types/core-library/processors/is-object-constraint.d.ts +10 -0
- package/types/core-library/processors/is-string-constraint.d.ts +10 -0
- package/types/core-library/processors/join-operator.d.ts +29 -0
- package/types/core-library/processors/json-constraint.d.ts +10 -0
- package/types/core-library/processors/json-decode-operator.d.ts +9 -0
- package/types/core-library/processors/json-encode-operator.d.ts +27 -0
- package/types/core-library/processors/kebab-case-operator.d.ts +16 -0
- package/types/core-library/processors/keys-operator.d.ts +9 -0
- package/types/core-library/processors/length-constraint.d.ts +34 -0
- package/types/core-library/processors/lookup-operator.d.ts +36 -0
- package/types/core-library/processors/lowercase-operator.d.ts +9 -0
- package/types/core-library/processors/map-operator.d.ts +38 -0
- package/types/core-library/processors/match-operator.d.ts +34 -0
- package/types/core-library/processors/matches-constraint.d.ts +29 -0
- package/types/core-library/processors/math-operators.d.ts +91 -0
- package/types/core-library/processors/merge-deep-operator.d.ts +32 -0
- package/types/core-library/processors/merge-operator.d.ts +26 -0
- package/types/core-library/processors/metadata-operator.d.ts +56 -0
- package/types/core-library/processors/negative-constraint.d.ts +13 -0
- package/types/core-library/processors/never-constraint.d.ts +26 -0
- package/types/core-library/processors/non-empty-constraint.d.ts +28 -0
- package/types/core-library/processors/not-constraint.d.ts +28 -0
- package/types/core-library/processors/number-operator.d.ts +9 -0
- package/types/core-library/processors/numeric-constraint.d.ts +10 -0
- package/types/core-library/processors/object-operator.d.ts +10 -0
- package/types/core-library/processors/omit-operator.d.ts +24 -0
- package/types/core-library/processors/parallel-operator.d.ts +41 -0
- package/types/core-library/processors/pascal-case-operator.d.ts +9 -0
- package/types/core-library/processors/phone-constraint.d.ts +65 -0
- package/types/core-library/processors/pick-operator.d.ts +27 -0
- package/types/core-library/processors/pipeline-operator.d.ts +40 -0
- package/types/core-library/processors/port-constraint.d.ts +11 -0
- package/types/core-library/processors/positive-constraint.d.ts +13 -0
- package/types/core-library/processors/process-operator.d.ts +37 -0
- package/types/core-library/processors/property-operator.d.ts +34 -0
- package/types/core-library/processors/range-constraint.d.ts +30 -0
- package/types/core-library/processors/reference-operator.d.ts +38 -0
- package/types/core-library/processors/require-constraint.d.ts +29 -0
- package/types/core-library/processors/reverse-operator.d.ts +9 -0
- package/types/core-library/processors/round-operator.d.ts +34 -0
- package/types/core-library/processors/schema-handler-operators.d.ts +28 -0
- package/types/core-library/processors/semver-constraint.d.ts +43 -0
- package/types/core-library/processors/sequence-processors.d.ts +213 -0
- package/types/core-library/processors/sort-operator.d.ts +31 -0
- package/types/core-library/processors/split-operator.d.ts +33 -0
- package/types/core-library/processors/string-extra-operators.d.ts +83 -0
- package/types/core-library/processors/string-operator.d.ts +10 -0
- package/types/core-library/processors/target-operator.d.ts +27 -0
- package/types/core-library/processors/template-operator.d.ts +31 -0
- package/types/core-library/processors/title-case-operator.d.ts +12 -0
- package/types/core-library/processors/trim-operator.d.ts +9 -0
- package/types/core-library/processors/truthy-constraint.d.ts +23 -0
- package/types/core-library/processors/type-operator.d.ts +11 -0
- package/types/core-library/processors/unique-operator.d.ts +10 -0
- package/types/core-library/processors/uppercase-operator.d.ts +9 -0
- package/types/core-library/processors/url-constraint.d.ts +20 -0
- package/types/core-library/processors/url-decode-operator.d.ts +31 -0
- package/types/core-library/processors/url-encode-operator.d.ts +36 -0
- package/types/core-library/processors/uuid-constraint.d.ts +20 -0
- package/types/core-library/processors/values-operator.d.ts +9 -0
- package/types/core-library/schemas/any-schema.d.ts +2 -0
- package/types/core-library/schemas/array-schema.d.ts +2 -0
- package/types/core-library/schemas/boolean-schema.d.ts +2 -0
- package/types/core-library/schemas/date-schema.d.ts +2 -0
- package/types/core-library/schemas/function-schema.d.ts +2 -0
- package/types/core-library/schemas/number-schema.d.ts +2 -0
- package/types/core-library/schemas/object-schema.d.ts +2 -0
- package/types/core-library/schemas/root-schema.d.ts +2 -0
- package/types/core-library/schemas/string-schema.d.ts +2 -0
- package/types/core-library-node/index.d.ts +12 -0
- package/types/core-library-node/processors/base64-decode-operator.d.ts +9 -0
- package/types/core-library-node/processors/base64-encode-operator.d.ts +9 -0
- package/types/core-library-node/processors/buffer-operator.d.ts +15 -0
- package/types/core-library-node/processors/directory-constraint.d.ts +14 -0
- package/types/core-library-node/processors/executable-constraint.d.ts +17 -0
- package/types/core-library-node/processors/file-constraint.d.ts +13 -0
- package/types/core-library-node/processors/file-size-constraint.d.ts +43 -0
- package/types/core-library-node/processors/is-buffer-constraint.d.ts +10 -0
- package/types/core-library-node/processors/reachable-constraint.d.ts +13 -0
- package/types/core-library-node/processors/readable-constraint.d.ts +17 -0
- package/types/core-library-node/processors/writable-constraint.d.ts +18 -0
- package/types/core-library-node/schemas/buffer-schema.d.ts +2 -0
- package/types/errors.d.ts +58 -0
- package/types/executor/array-executor.d.ts +17 -0
- package/types/executor/conditional-executor.d.ts +45 -0
- package/types/executor/each-executor.d.ts +15 -0
- package/types/executor/executor.d.ts +84 -0
- package/types/executor/object-executor.d.ts +14 -0
- package/types/executor/parallel-executor.d.ts +27 -0
- package/types/executor/pipeline-executor.d.ts +11 -0
- package/types/executor/sequence-executor.d.ts +32 -0
- package/types/executor/serial-executor.d.ts +16 -0
- package/types/executor/step-executor.d.ts +14 -0
- package/types/helpers/case.d.ts +30 -0
- package/types/helpers/data-size.d.ts +25 -0
- package/types/helpers/debug-sink.d.ts +9 -0
- package/types/helpers/deep.d.ts +33 -0
- package/types/helpers/format.d.ts +14 -0
- package/types/helpers/has-string-properties.d.ts +5 -0
- package/types/helpers/index.d.ts +13 -0
- package/types/helpers/object.d.ts +46 -0
- package/types/helpers/parse-date.d.ts +6 -0
- package/types/helpers/path.d.ts +13 -0
- package/types/helpers/regex.d.ts +7 -0
- package/types/helpers/stringify.d.ts +33 -0
- package/types/helpers/to-data.d.ts +13 -0
- package/types/helpers/truthy.d.ts +26 -0
- package/types/index.d.ts +6 -0
- package/types/schema-compiler.d.ts +49 -0
- package/types/schema-location.d.ts +64 -0
- package/types/schema-resolver.d.ts +145 -0
- package/types/schema.d.ts +586 -0
- package/types/traversal/executors/check-condition.d.ts +8 -0
- package/types/traversal/executors/check-input.d.ts +6 -0
- package/types/traversal/executors/check-required.d.ts +6 -0
- package/types/traversal/executors/check-schema.d.ts +7 -0
- package/types/traversal/executors/defaults.d.ts +8 -0
- package/types/traversal/executors/enter-existing.d.ts +6 -0
- package/types/traversal/executors/enter-input.d.ts +8 -0
- package/types/traversal/executors/enter.d.ts +7 -0
- package/types/traversal/executors/exit.d.ts +6 -0
- package/types/traversal/executors/finalize.d.ts +6 -0
- package/types/traversal/executors/index.d.ts +15 -0
- package/types/traversal/executors/normalize.d.ts +7 -0
- package/types/traversal/executors/prepare-existing.d.ts +6 -0
- package/types/traversal/executors/prepare-pending.d.ts +6 -0
- package/types/traversal/executors/resolve-union.d.ts +6 -0
- package/types/traversal/executors/serialize.d.ts +11 -0
- package/types/traversal/executors/transform-early.d.ts +6 -0
- package/types/traversal/executors/transform.d.ts +6 -0
- package/types/traversal/executors/traversal-state-executor.d.ts +19 -0
- package/types/traversal/executors/validate.d.ts +6 -0
- package/types/traversal/traversal-context.d.ts +67 -0
- package/types/traversal/traversal-state.d.ts +97 -0
- package/types/types.d.ts +218 -0
- package/types/value-processor/composed-value-processor.d.ts +17 -0
- package/types/value-processor/defined-value-processor.d.ts +16 -0
- package/types/value-processor/function-value-processor.d.ts +15 -0
- package/types/value-processor/parameterized-value-processor.d.ts +14 -0
- package/types/value-processor/parameters-value-processor.d.ts +28 -0
- package/types/value-processor/spec.d.ts +22 -0
- package/types/value-processor/value-processor.d.ts +92 -0
|
@@ -0,0 +1,1709 @@
|
|
|
1
|
+
import { toData } from './helpers/to-data.js';
|
|
2
|
+
import { SchemaLocation } from './schema-location.js';
|
|
3
|
+
import { TraversalContext } from './traversal/traversal-context.js';
|
|
4
|
+
import { PipelineExecutor } from "./executor/pipeline-executor.js";
|
|
5
|
+
import { ValueProcessor } from './value-processor/value-processor.js';
|
|
6
|
+
import { EMPTY } from './constants.js';
|
|
7
|
+
import {
|
|
8
|
+
PROCESS_EXECUTOR,
|
|
9
|
+
PRELOAD_EXECUTOR,
|
|
10
|
+
SERIALIZE_EXECUTOR,
|
|
11
|
+
VALIDATE_EXECUTOR
|
|
12
|
+
} from './traversal/executors/index.js';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
FinalizeError,
|
|
16
|
+
NormalizeError,
|
|
17
|
+
SchemaError,
|
|
18
|
+
SerializeError,
|
|
19
|
+
TransformError,
|
|
20
|
+
ValidationError
|
|
21
|
+
} from './errors.js';
|
|
22
|
+
import { deepEquals, deepPrune } from './helpers/deep.js';
|
|
23
|
+
import { isTruthy } from './helpers/truthy.js';
|
|
24
|
+
import { isPlainObject } from './helpers/object.js';
|
|
25
|
+
import { formatValue } from './helpers/format.js';
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/** @import { TraversalContextOptions } from './traversal/traversal-context.js' */
|
|
29
|
+
/** @import { ISchema, ISchemaOptions, ISchemaMetadata, SchemaData } from './types.js' */
|
|
30
|
+
|
|
31
|
+
/** @typedef {ISchemaMetadata} CompiledSchemaMetadata */
|
|
32
|
+
/** @typedef {ISchemaOptions} CompiledSchemaOptions */
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {object} CompiledSchemaHandlers
|
|
36
|
+
* @property {Array<ValueProcessor>} [normalizers]
|
|
37
|
+
* @property {Array<ValueProcessor>} [conditions]
|
|
38
|
+
* @property {Array<ValueProcessor>} [transformers]
|
|
39
|
+
* @property {Array<ValueProcessor>} [finalizers]
|
|
40
|
+
* @property {Array<ValueProcessor>} [validators]
|
|
41
|
+
* @property {Array<ValueProcessor>} [serializers]
|
|
42
|
+
* @property {Array<ValueProcessor>} [discriminators]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/** @typedef {{[key:string]:CompiledSchema}} CompiledSchemaProperties */
|
|
46
|
+
/** @typedef {{[key:string]:CompiledSchema}} CompiledSchemaUnionSchemas */
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} SharedOptions
|
|
50
|
+
* @property {SchemaLocation} [location]
|
|
51
|
+
* @property {TraversalContext|TraversalContextOptions} [context]
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/** @typedef {SharedOptions & {[key:string]: any}} ValidateOptions */
|
|
55
|
+
/** @typedef {SharedOptions & {[key:string]: any}} SerializeOptions */
|
|
56
|
+
/** @typedef {SharedOptions & {assignments?:Map<string,any>} & {[key:string]: any}} ProcessOptions */
|
|
57
|
+
/** @typedef {SharedOptions & {[key:string]: any}} ProcessAssignmentsOptions */
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* CompiledSchema - the resolved version of a schema usable for processing input values into output values
|
|
61
|
+
*
|
|
62
|
+
* The SchemaResolver compiler takes an input Schema and constructs a CompiledSchema:
|
|
63
|
+
* - The base schema hierarchy is resolved and flattened.
|
|
64
|
+
* - Handlers have their input specifications converted into value processor executors.
|
|
65
|
+
* - Unions may trigger property hoisting and discriminator synthesis.
|
|
66
|
+
* - Core options are converted to standardized forms.
|
|
67
|
+
* - Metadata is expanded by introspecting the resolved schema.
|
|
68
|
+
* - Errors are thrown if the input Schema is invalid, inconsistent, or missing required data.
|
|
69
|
+
*
|
|
70
|
+
* @augments {ISchema}
|
|
71
|
+
*/
|
|
72
|
+
export class CompiledSchema
|
|
73
|
+
{
|
|
74
|
+
/** @internal */
|
|
75
|
+
static __TOKEN = Symbol('CONSTRUCT_USING_COMPILER')
|
|
76
|
+
|
|
77
|
+
/** @type {Map<string,CompiledSchema>} */
|
|
78
|
+
#propertiesMap = new Map();
|
|
79
|
+
/** @type {CompiledSchemaProperties|undefined} */
|
|
80
|
+
#properties;
|
|
81
|
+
/** @type {Map<string,CompiledSchema>} */
|
|
82
|
+
#unionSchemasMap = new Map();
|
|
83
|
+
/** @type {CompiledSchemaUnionSchemas|undefined} */
|
|
84
|
+
#unionSchemas;
|
|
85
|
+
/** @type {Map<string, ValueProcessor>} */
|
|
86
|
+
#valueProcessorMap = new Map();
|
|
87
|
+
|
|
88
|
+
/** @type {CompiledSchemaHandlers} */
|
|
89
|
+
#handlers = {};
|
|
90
|
+
/** @type {CompiledSchemaOptions} */
|
|
91
|
+
#options = {};
|
|
92
|
+
/** @type {CompiledSchemaMetadata} */
|
|
93
|
+
#metadata = {};
|
|
94
|
+
/** @type {boolean} */
|
|
95
|
+
#frozen = false;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* CompiledSchema constructor - do not call directly (use SchemaResolver.compile())
|
|
99
|
+
*
|
|
100
|
+
* @param {symbol} token - magic to reduce shenanigans
|
|
101
|
+
*/
|
|
102
|
+
constructor(token) {
|
|
103
|
+
if (token !== CompiledSchema.__TOKEN) {
|
|
104
|
+
throw new SchemaError('CompiledSchema must be created via compilation');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Options contains information that changes schema parsing and processing.
|
|
110
|
+
*
|
|
111
|
+
* @type {CompiledSchemaOptions}
|
|
112
|
+
*/
|
|
113
|
+
get options() {
|
|
114
|
+
return this.#options;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Metadata contains information for describing the schema behavior to users and hints for tools.
|
|
119
|
+
*
|
|
120
|
+
* @type {CompiledSchemaMetadata}
|
|
121
|
+
*/
|
|
122
|
+
get metadata() {
|
|
123
|
+
return this.#metadata;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Properties are named child schemas, defining a hierarchical schema structure.
|
|
128
|
+
*
|
|
129
|
+
* This is an (inefficient) cache for compatibility with the ISchema "interface".
|
|
130
|
+
*
|
|
131
|
+
* @type {CompiledSchemaProperties}
|
|
132
|
+
*/
|
|
133
|
+
get properties() {
|
|
134
|
+
return this.#properties ??=
|
|
135
|
+
Object.freeze(Object.fromEntries(this.#propertiesMap));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
*
|
|
140
|
+
* @type {IteratorObject<[string, CompiledSchema]>}
|
|
141
|
+
*/
|
|
142
|
+
get propertyEntries() {
|
|
143
|
+
return this.#propertiesMap.entries();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handlers are associated with asynchronous value processors.
|
|
148
|
+
*
|
|
149
|
+
* The "friendly" handler definitions from the source Schema are each compiled into asynchronous functions
|
|
150
|
+
* that run as a pipeline.
|
|
151
|
+
*
|
|
152
|
+
* All handlers have the same async signature, receiving:
|
|
153
|
+
* 1. a value to be processed by the current schema
|
|
154
|
+
* 2. a reference to the top-level aggregate target being built or processed by the entire schema hierarchy
|
|
155
|
+
* 3. a location defining the current schema and the traversal path to where it was encountered
|
|
156
|
+
* 5. any (unmanaged / developer defined) options passed to whatever invoked the handler processing
|
|
157
|
+
*
|
|
158
|
+
* The compiled handlers may vary in their return types and exception handling behavior.
|
|
159
|
+
*
|
|
160
|
+
* @type {CompiledSchemaHandlers}
|
|
161
|
+
*/
|
|
162
|
+
get handlers() {
|
|
163
|
+
return this.#handlers;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get a value processor by handler name
|
|
168
|
+
*
|
|
169
|
+
* Handler definitions are compiled into a single value processor pipeline per handler name.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} handlerName
|
|
172
|
+
* @returns {ValueProcessor|undefined}
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
getValueProcessor(handlerName) {
|
|
176
|
+
return this.#valueProcessorMap.get(handlerName);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
*
|
|
181
|
+
* @param {string} handlerName
|
|
182
|
+
* @param {ValueProcessor} valueProcessor
|
|
183
|
+
* @returns {ValueProcessor}
|
|
184
|
+
* @internal
|
|
185
|
+
*/
|
|
186
|
+
_setValueProcessor(handlerName, valueProcessor) {
|
|
187
|
+
this.#valueProcessorMap.set(handlerName, valueProcessor);
|
|
188
|
+
return valueProcessor;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* If this schema defines a union, return the schema member elements of the union.
|
|
193
|
+
*
|
|
194
|
+
* Unions use a discriminator handler to attempt to resolve to one of the unionSchema member elements
|
|
195
|
+
* based on either the value being processed or the overall aggregated data. The key of the
|
|
196
|
+
* unionSchema in this collection is sometimes used as part of this process.
|
|
197
|
+
*
|
|
198
|
+
* Once the discriminator succeeds, the active schema switches to the resolved unionSchema.
|
|
199
|
+
*
|
|
200
|
+
* @type {CompiledSchemaUnionSchemas}
|
|
201
|
+
*/
|
|
202
|
+
get unionSchemas() {
|
|
203
|
+
return this.#unionSchemas ??=
|
|
204
|
+
Object.freeze(Object.fromEntries(this.#unionSchemasMap));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
*
|
|
209
|
+
* @type {IteratorObject<[string, CompiledSchema]>}
|
|
210
|
+
*/
|
|
211
|
+
get unionSchemaEntries() {
|
|
212
|
+
return this.#unionSchemasMap.entries();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Extract this schema as a raw data object usable for cloning this schema.
|
|
217
|
+
*
|
|
218
|
+
* Note that the data may include handler functions, so it cannot be assumed to be serializable to JSON!
|
|
219
|
+
*
|
|
220
|
+
* @returns {SchemaData|undefined}
|
|
221
|
+
*/
|
|
222
|
+
toData() {
|
|
223
|
+
return toData(this);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Return true if this schema has any child schemas.
|
|
228
|
+
*
|
|
229
|
+
* @type {boolean}
|
|
230
|
+
*/
|
|
231
|
+
get hasChildren() {
|
|
232
|
+
return this.#propertiesMap.size > 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Return true if this schema should be treated as a container.
|
|
237
|
+
*
|
|
238
|
+
* Any schema that has children is a container, but also those explicitly marked as a container.
|
|
239
|
+
*
|
|
240
|
+
* @returns {boolean}
|
|
241
|
+
*/
|
|
242
|
+
get isContainer() {
|
|
243
|
+
return this.hasChildren || Boolean(this.options.container);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Return true if this schema supports wildcard properties.
|
|
248
|
+
*
|
|
249
|
+
* @type {boolean}
|
|
250
|
+
*/
|
|
251
|
+
get hasWildcard() {
|
|
252
|
+
return this.#propertiesMap.has('*');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Return true if this schema defines an array.
|
|
257
|
+
*
|
|
258
|
+
* Arrays sometimes need special treatment; the built-in 'array' base schema sets this option.
|
|
259
|
+
*
|
|
260
|
+
* @type {boolean}
|
|
261
|
+
*/
|
|
262
|
+
get isArray() {
|
|
263
|
+
return this.options.type === 'array';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Return true if this schema defines a function value.
|
|
268
|
+
*
|
|
269
|
+
* Functions passed to most operations are interpreted as dynamic values (called to retrieve actual value).
|
|
270
|
+
* This setting overrides that behavior, and forces a passed function to be treated as a simple value.
|
|
271
|
+
*
|
|
272
|
+
* @type {boolean}
|
|
273
|
+
*/
|
|
274
|
+
get isFunction() {
|
|
275
|
+
return this.options.type === 'function'
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Return true if this schema defines a union.
|
|
281
|
+
* Unions adopt the behavior of one of their unionSchema member elements based on a discriminator handler function.
|
|
282
|
+
*
|
|
283
|
+
* @type {boolean}
|
|
284
|
+
*/
|
|
285
|
+
get isUnion() {
|
|
286
|
+
return this.#unionSchemasMap.size > 0
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Return true if this schema is used to select union keys.
|
|
291
|
+
*
|
|
292
|
+
* (This doesn't guarantee that there is a matching discriminator on the parent that uses it!)
|
|
293
|
+
* todo - convert this option into a generated normalizer that enforces union key values at runtime?
|
|
294
|
+
*
|
|
295
|
+
* @type {boolean}
|
|
296
|
+
*/
|
|
297
|
+
get isUnionKey() {
|
|
298
|
+
return !!this.options.unionKey;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Return true if the schema acts as a selector.
|
|
303
|
+
*
|
|
304
|
+
* Selectors control the activation or deactivation of peer selection schemas using a conditional handler
|
|
305
|
+
* synthesized during compilation.
|
|
306
|
+
*
|
|
307
|
+
* @type {boolean}
|
|
308
|
+
*/
|
|
309
|
+
get isSelector() {
|
|
310
|
+
return !!this.options.selector;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Return true if this schema contains a selector as a child.
|
|
315
|
+
* @type {boolean}
|
|
316
|
+
*/
|
|
317
|
+
get hasChildSelector() {
|
|
318
|
+
for (const propertySchema of this.#propertiesMap.values()) {
|
|
319
|
+
if (propertySchema.isSelector) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Return true if this schema is a selection conditionally activated by a peer selector.
|
|
328
|
+
*
|
|
329
|
+
* @type {boolean}
|
|
330
|
+
*/
|
|
331
|
+
get isSelection() {
|
|
332
|
+
return this.options.selection !== undefined && this.options.selection !== false; // todo - use a symbol to trigger name rather than "true"...
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Return true if this schema contains a selection as a child.
|
|
337
|
+
* @type {boolean}
|
|
338
|
+
*/
|
|
339
|
+
get hasChildSelection() {
|
|
340
|
+
for (const propertySchema of this.#propertiesMap.values()) {
|
|
341
|
+
if (propertySchema.isSelection) {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get the selector value that triggers this selection. The default value for a selection is its own property name.
|
|
350
|
+
*
|
|
351
|
+
* @type {string|boolean|undefined}
|
|
352
|
+
*/
|
|
353
|
+
get selection() {
|
|
354
|
+
return this.options.selection;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Return the legal (normalized) values this schema accepts, if defined.
|
|
359
|
+
*
|
|
360
|
+
* (This acts as an "upstream" check before calling any transform handlers.)
|
|
361
|
+
*
|
|
362
|
+
* @type {Array<NonNullable<any>>|undefined}
|
|
363
|
+
*/
|
|
364
|
+
get values() {
|
|
365
|
+
return this.options.values;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Returns true if this schema defines any values it accepts.
|
|
370
|
+
*
|
|
371
|
+
* @type {boolean}
|
|
372
|
+
*/
|
|
373
|
+
get hasValues() {
|
|
374
|
+
const v = this.options.values;
|
|
375
|
+
return Array.isArray(v) && v.length > 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Returns whether this schema enforces strict/lax checking.
|
|
380
|
+
*
|
|
381
|
+
* - returns true if the schema uses strict checking
|
|
382
|
+
* - returns false if the schema uses lax checking
|
|
383
|
+
* - if undefined, it depends on the traversal context and the behavior of individual processors
|
|
384
|
+
*
|
|
385
|
+
* This setting is useful for preventing validation errors when transforms return new objects
|
|
386
|
+
* that contain extra properties that don't match the schema.
|
|
387
|
+
*
|
|
388
|
+
* In most contexts (e.g. assignment processing and validation) strict processing is the default.
|
|
389
|
+
* Setting lax mode changes several behaviors:
|
|
390
|
+
* - If a union is marked lax, it is not an error to fail to resolve the union
|
|
391
|
+
* - If a leaf schema is marked lax, it is not an error if a value fails to assign to it
|
|
392
|
+
*
|
|
393
|
+
* Lax mode does *not* mean that errors during normalization
|
|
394
|
+
*
|
|
395
|
+
* @type {boolean|undefined}
|
|
396
|
+
*/
|
|
397
|
+
get strict() {
|
|
398
|
+
return this.options.strict;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Returns true if the value defined by this schema is required to exist in the output to be valid.
|
|
403
|
+
*
|
|
404
|
+
* Under the normal pathways, this is a shallow requirement:
|
|
405
|
+
* - Each schema checks its own input; if the required flag is set, the input must not be undefined.
|
|
406
|
+
* - If the input value is defined and the schema defines child properties, the input will be traversed
|
|
407
|
+
* and recursively checked against the child property schemas.
|
|
408
|
+
* - If the input value is undefined, child property schemas are NOT checked.
|
|
409
|
+
*
|
|
410
|
+
* If the "deep" flag is set on the schema, this behavior changes:
|
|
411
|
+
* - If the input value is undefined on a schema with "deep" set, child properties ARE checked.
|
|
412
|
+
*
|
|
413
|
+
* If a schema sets a default value, it will generally satisfy the required setting (unless set to a
|
|
414
|
+
* value function that returns undefined!)
|
|
415
|
+
*
|
|
416
|
+
* @type {boolean}
|
|
417
|
+
*/
|
|
418
|
+
get required() {
|
|
419
|
+
return this.options.required ?? false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Returns the default value this schema provides if the input is undefined.
|
|
424
|
+
*
|
|
425
|
+
* This is normally interpreted as a "shallow" default;
|
|
426
|
+
* - If the schema being actively processed has a default value and the input value is undefined, the default is used.
|
|
427
|
+
* - If the schema being actively processed defines child properties, and is passed an input value that does not
|
|
428
|
+
* contain a value for a specific child that defines a default, the child's default will be used.
|
|
429
|
+
* - If the schema being actively processed defines child properties, but is passed an undefined input value,
|
|
430
|
+
* the child property schemas will not be traversed, and thus any child defaults will not take effect.
|
|
431
|
+
*
|
|
432
|
+
* This last behavior changes if the "deep" schema option is set:
|
|
433
|
+
* - If the schema being actively processed defines child properties is passed an undefined input value when
|
|
434
|
+
* the "deep" option is enabled, child property schemas will be traversed and any defaults will be used.
|
|
435
|
+
*
|
|
436
|
+
* The default may also be set to a value function:
|
|
437
|
+
* `async (value: any, target: any, location:SchemaLocation, options: Object): any`
|
|
438
|
+
* that will be called during the normalization phase. This can be useful for late binding, remote lookups,
|
|
439
|
+
* side effects, or lazy evaluation of expensive values.
|
|
440
|
+
*
|
|
441
|
+
* @type {any|undefined}
|
|
442
|
+
*/
|
|
443
|
+
get default() {
|
|
444
|
+
return this.options.default;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Returns whether this schema should be deeply traversed even when the input is empty.
|
|
449
|
+
*
|
|
450
|
+
* This is useful for triggering required/defaults settings. (Note that a schema with
|
|
451
|
+
* child properties that is marked required is deep by default, because that's almost
|
|
452
|
+
* always what is wanted/expected.)
|
|
453
|
+
*
|
|
454
|
+
* Returns undefined if unset, signaling that the traversal context will decide.
|
|
455
|
+
*
|
|
456
|
+
* @type {boolean|undefined}
|
|
457
|
+
*/
|
|
458
|
+
get deep() {
|
|
459
|
+
if (this.options.deep !== undefined) {
|
|
460
|
+
return this.options.deep;
|
|
461
|
+
}
|
|
462
|
+
if (this.hasChildren && this.required) {
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Return true if the schema always returns an inherited or referenced value.
|
|
470
|
+
*
|
|
471
|
+
* Referenced properties never accept a direct assignment, and will always return the value
|
|
472
|
+
* corresponding to the first matching property name found higher in the schema.
|
|
473
|
+
*
|
|
474
|
+
* @type {boolean}
|
|
475
|
+
*/
|
|
476
|
+
get isReference() {
|
|
477
|
+
return this.options.reference ?? false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Returns true if this schema defines a value that can be assumed to always exist and be valid.
|
|
482
|
+
*
|
|
483
|
+
* The implicit setting implies that values passed to this schema should not be visited or validated.
|
|
484
|
+
*
|
|
485
|
+
* @type {boolean}
|
|
486
|
+
*/
|
|
487
|
+
get isImplicit() {
|
|
488
|
+
return this.options.implicit ?? false;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Returns true if the container allows incremental assignment to children.
|
|
493
|
+
* Deprecated - use "opaque" as a more accurate signal of intent.
|
|
494
|
+
*
|
|
495
|
+
* @type {boolean}
|
|
496
|
+
* @deprecated
|
|
497
|
+
*/
|
|
498
|
+
get allowIncremental() {
|
|
499
|
+
return this.options.allowIncremental ?? this.hasChildren;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Returns true if the schema defines a value whose internals are hidden after transformation.
|
|
504
|
+
*
|
|
505
|
+
* Opaque schemas will typically need a custom validator that understands the post-transform contract.
|
|
506
|
+
*
|
|
507
|
+
* Transformation of opaque schemas is delayed until all known child assignments have been staged into a
|
|
508
|
+
* pending container. Once an opaque schema is transformed, it no longer accepts assignments. (This
|
|
509
|
+
* may cause sequencing issues with late-resolved conditional assignments!)
|
|
510
|
+
*
|
|
511
|
+
* @type {boolean}
|
|
512
|
+
*/
|
|
513
|
+
get isOpaque() {
|
|
514
|
+
return !this.hasChildren || this.options.allowIncremental === false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Check if the provided value (and/or current output target) passes the schema conditional check.
|
|
519
|
+
*
|
|
520
|
+
* Schemas that don't have a condition handler defined will always succeed.
|
|
521
|
+
*
|
|
522
|
+
* Failed conditions will be repeatedly re-checked during assignment processing until the final pass.
|
|
523
|
+
* Errors encountered while checking conditions are caught and simply result in a failed condition.
|
|
524
|
+
*
|
|
525
|
+
* (This is an executor function that may return synchronous or asynchronous results.)
|
|
526
|
+
*
|
|
527
|
+
* @param {any} value
|
|
528
|
+
* @param {any} [target]
|
|
529
|
+
* @param {SchemaLocation} [location]
|
|
530
|
+
* @param {object} [options]
|
|
531
|
+
* @returns {boolean|Promise<boolean>}
|
|
532
|
+
* @internal
|
|
533
|
+
*/
|
|
534
|
+
_checkCondition(value, target, location = new SchemaLocation(this), options) {
|
|
535
|
+
if (location.schema !== this) {
|
|
536
|
+
return location.schema._checkCondition(value, target, location, options);
|
|
537
|
+
}
|
|
538
|
+
if (value instanceof Error && !this.options.allowErrors) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const conditional = this.getValueProcessor('conditions');
|
|
543
|
+
|
|
544
|
+
if (!conditional) {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const result = conditional.execute(value, target, location, options);
|
|
549
|
+
if (result instanceof Promise) {
|
|
550
|
+
return result.then(
|
|
551
|
+
resolved => (resolved instanceof Error && !this.options.allowErrors)? false : isTruthy(resolved),
|
|
552
|
+
_ => false
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
if (result instanceof Error && !this.options.allowErrors) {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
return isTruthy(result);
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Check if the provided value (and/or current output target) passes the schema conditional check.
|
|
566
|
+
*
|
|
567
|
+
* Failed conditions will be repeatedly re-checked during assignment processing until the final pass.
|
|
568
|
+
* Errors encountered while checking conditions are caught and simply result in a failed condition.
|
|
569
|
+
*
|
|
570
|
+
* (This an async wrapper around the internal `_checkCondition` executor function.)
|
|
571
|
+
*
|
|
572
|
+
* @param {any} value
|
|
573
|
+
* @param {any} [target]
|
|
574
|
+
* @param {SchemaLocation} [location]
|
|
575
|
+
* @param {object} [options]
|
|
576
|
+
* @returns {Promise<boolean>}
|
|
577
|
+
* @internal
|
|
578
|
+
*/
|
|
579
|
+
async checkCondition(value, target, location = new SchemaLocation(this), options) {
|
|
580
|
+
return this._checkCondition(value, target, location, options);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Return true if this schema is conditional
|
|
585
|
+
*
|
|
586
|
+
* @returns {boolean}
|
|
587
|
+
*/
|
|
588
|
+
get hasConditions() {
|
|
589
|
+
const conditions = this.handlers.conditions;
|
|
590
|
+
return Boolean(conditions && Array.isArray(conditions) && conditions.length);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Return true if this schema requires finalization
|
|
595
|
+
*
|
|
596
|
+
* @returns {boolean}
|
|
597
|
+
*/
|
|
598
|
+
get requiresFinalization() {
|
|
599
|
+
const finalizers = this.handlers.finalizers;
|
|
600
|
+
return Boolean(finalizers && Array.isArray(finalizers) && finalizers.length);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
_checkValue(value, ErrorClass) {
|
|
604
|
+
if (value instanceof Error && !this.options.allowErrors) {
|
|
605
|
+
if (ErrorClass && value instanceof ErrorClass) {
|
|
606
|
+
throw value;
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
const err = new ErrorClass(value.message, {cause: value});
|
|
610
|
+
Error?.captureStackTrace(err, this._checkValue);
|
|
611
|
+
throw err;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
}
|
|
615
|
+
return value;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Use the registered discriminator to return a matching union schema, or undefined if the union cannot be resolved.
|
|
620
|
+
* Discriminator functions must return either one of the unionSchema members, a unionSchema key, or undefined.
|
|
621
|
+
*
|
|
622
|
+
* (This is an executor function that may return synchronous or asynchronous results.)
|
|
623
|
+
*
|
|
624
|
+
* @param {any} value
|
|
625
|
+
* @param {any} [target]
|
|
626
|
+
* @param {SchemaLocation} [location]
|
|
627
|
+
* @param {object} [options]
|
|
628
|
+
* @returns {CompiledSchema|undefined|Promise<CompiledSchema|undefined>}
|
|
629
|
+
* @internal
|
|
630
|
+
*/
|
|
631
|
+
_discriminateUnion(value, target, location = new SchemaLocation(this), options = {}) {
|
|
632
|
+
if (location.schema !== this) {
|
|
633
|
+
return location.schema._discriminateUnion(value, target, location, options);
|
|
634
|
+
}
|
|
635
|
+
const discriminator = this.getValueProcessor('discriminators');
|
|
636
|
+
|
|
637
|
+
if (!this.isUnion || !discriminator) {
|
|
638
|
+
return undefined;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (value instanceof Error && !this.options.allowErrors) {
|
|
642
|
+
if (options?.strict) {
|
|
643
|
+
throw value;
|
|
644
|
+
}
|
|
645
|
+
return undefined;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
let result;
|
|
650
|
+
try {
|
|
651
|
+
result = discriminator.execute(value, target, location, options);
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
if (options?.strict) {
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
if (result instanceof Promise) {
|
|
660
|
+
return result.then(
|
|
661
|
+
resolved => this.getUnionSchema(resolved),
|
|
662
|
+
rejected => {
|
|
663
|
+
if (options.strict) { throw rejected; }
|
|
664
|
+
return undefined;
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
return this.getUnionSchema(result);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Use the registered discriminator to return a matching union schema, or undefined if the union cannot be resolved.
|
|
671
|
+
* Discriminator functions must return either one of the unionSchema members, a unionSchema key, or undefined.
|
|
672
|
+
*
|
|
673
|
+
* (This an async wrapper around the internal `_discriminateUnion` executor function.)
|
|
674
|
+
*
|
|
675
|
+
* @param {any} value
|
|
676
|
+
* @param {any} [target]
|
|
677
|
+
* @param {SchemaLocation} [location]
|
|
678
|
+
* @param {object} [options]
|
|
679
|
+
* @returns {Promise<CompiledSchema|undefined>}
|
|
680
|
+
* @internal
|
|
681
|
+
*/
|
|
682
|
+
async discriminateUnion(value, target, location = new SchemaLocation(this), options) {
|
|
683
|
+
return this._discriminateUnion(value, target, location, options);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Ensure the input is of an expected shape that can be handled by this schema.
|
|
688
|
+
*
|
|
689
|
+
* Runs all normalizer value processors in a pipeline until completion or an error is thrown.
|
|
690
|
+
* As external data may originate in the form of strings or JSON structures, the main task of
|
|
691
|
+
* a normalizer is to "canonicalize" these inputs:
|
|
692
|
+
* - The normalized output should be accepted by the transformer handler.
|
|
693
|
+
* - Normalizers should usually pass through valid transformed values unchanged.
|
|
694
|
+
* - By contract, when passed "true", a container schema should construct an "empty"
|
|
695
|
+
* container (e.g. {} or []). (Even a schema that defines transformation to a
|
|
696
|
+
* complex class should have a normalized empty container format to use for construction).
|
|
697
|
+
*
|
|
698
|
+
* The normalize process will throw an exception if the input is incompatible.
|
|
699
|
+
*
|
|
700
|
+
* Unlike other handlers, normalizers should generally not depend on the overall
|
|
701
|
+
* target state, as they are sometimes invoked in isolation (even during compilation!)
|
|
702
|
+
* and thus shouldn't assume the "undefined means retry later" behavior of other handlers.
|
|
703
|
+
*
|
|
704
|
+
* Also note that normalizeValue does not recursively examine child properties.
|
|
705
|
+
*
|
|
706
|
+
* (This is an executor function that may return synchronous or asynchronous results.)
|
|
707
|
+
*
|
|
708
|
+
* @param {any} value - value to normalize
|
|
709
|
+
* @param {any} [target] - top level output target being built (avoid using for normalizers!)
|
|
710
|
+
* @param {SchemaLocation} [location] - path to this value in the output target
|
|
711
|
+
* @param {object} [options] - optional tweaks to normalizer behavior
|
|
712
|
+
* @returns {any|Promise<any>}
|
|
713
|
+
* @internal
|
|
714
|
+
*/
|
|
715
|
+
|
|
716
|
+
_normalizeValue(value, target, location = new SchemaLocation(this), options = {}) {
|
|
717
|
+
if (location.schema !== this) {
|
|
718
|
+
return location.schema._normalizeValue(value, target, location, options);
|
|
719
|
+
}
|
|
720
|
+
if (value instanceof Error && !this.options.allowErrors) {
|
|
721
|
+
throw new NormalizeError(value.message, {cause: value});
|
|
722
|
+
}
|
|
723
|
+
const sync = options?.sync ?? false;
|
|
724
|
+
const dynamic = options?.dynamic ?? this.options?.dynamic ?? true; // todo - make dynamic default to false?
|
|
725
|
+
|
|
726
|
+
if ((typeof value === 'function' || value instanceof ValueProcessor) && dynamic && !options.compiling) {
|
|
727
|
+
const result = (value instanceof ValueProcessor)?
|
|
728
|
+
value.execute(true, target, location, options) : value(true, target, location, options);
|
|
729
|
+
|
|
730
|
+
if (result instanceof Promise) {
|
|
731
|
+
if (sync) {
|
|
732
|
+
throw new NormalizeError('Encountered an async value function during a forced-sync normalization', {location});
|
|
733
|
+
}
|
|
734
|
+
return result.then(resolved => {
|
|
735
|
+
if (resolved === undefined || resolved === null) {
|
|
736
|
+
return resolved; // a dynamic function must not fall back to defaults
|
|
737
|
+
}
|
|
738
|
+
// recurse so that we pick up the normal flow.
|
|
739
|
+
return this._normalizeValue(resolved, target, location, {...options, dynamic: false});
|
|
740
|
+
})
|
|
741
|
+
}
|
|
742
|
+
value = result; // fall through...
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (value === null || (value === undefined && !this.options.allowUndefined)) {
|
|
746
|
+
return value;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const normalizer = this.getValueProcessor('normalizers');
|
|
750
|
+
if (normalizer === undefined) {
|
|
751
|
+
// If we are a container type without a normalizer, create a default container if passed EMPTY
|
|
752
|
+
// todo - consider making this a compilation error! all container schemas need a normalizer of some sort!
|
|
753
|
+
if (this.isContainer && value === EMPTY) {
|
|
754
|
+
return this.isArray? [] : {}
|
|
755
|
+
}
|
|
756
|
+
return value;
|
|
757
|
+
}
|
|
758
|
+
let result;
|
|
759
|
+
try {
|
|
760
|
+
result = normalizer.execute(value, target, location, options);
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
throw new NormalizeError('Unable to normalize', {location, cause: error});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (result instanceof Promise) {
|
|
767
|
+
if (sync) {
|
|
768
|
+
throw new NormalizeError('Encountered an async processor during a forced-sync normalization', {location});
|
|
769
|
+
}
|
|
770
|
+
return result.then(
|
|
771
|
+
resolved => this._checkValue(resolved, NormalizeError),
|
|
772
|
+
rejected => { throw new NormalizeError('Unable to normalize', {value, location, cause: rejected})}
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
return this._checkValue(result);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Ensure the input is of an expected shape that can be handled by this schema.
|
|
780
|
+
*
|
|
781
|
+
* Runs all normalizer value processors in a pipeline until completion or an error is thrown.
|
|
782
|
+
* As many external input values will originate in the form of strings or JSON
|
|
783
|
+
* structures, the main task of a normalizer is to "canonicalize" these inputs:
|
|
784
|
+
* - The normalized output should be accepted by the transformer handler.
|
|
785
|
+
* - Normalizers should usually pass through valid transformed values unchanged.
|
|
786
|
+
* - By contract, when passed "true", a container schema should construct an "empty"
|
|
787
|
+
* container (e.g. {} or []). (Even a schema that defines transformation to a
|
|
788
|
+
* complex class should have a normalized empty container format to use for construction).
|
|
789
|
+
*
|
|
790
|
+
* The normalize process will throw an exception if the input is incompatible.
|
|
791
|
+
*
|
|
792
|
+
* Unlike other handlers, normalizers should generally not depend on the overall
|
|
793
|
+
* target state, as they are sometimes invoked in isolation (even during compilation!)
|
|
794
|
+
* and thus shouldn't assume the "undefined means retry later" behavior of other handlers.
|
|
795
|
+
*
|
|
796
|
+
* Also note that normalizeValue does not recursively examine child properties.
|
|
797
|
+
*
|
|
798
|
+
* (This an async wrapper around the internal `_normalizeValue` executor function.)
|
|
799
|
+
*
|
|
800
|
+
* @param {any} value - value to normalize
|
|
801
|
+
* @param {any} [target] - top level output target being built (avoid using for normalizers!)
|
|
802
|
+
* @param {SchemaLocation} [location] - path to this value in the output target
|
|
803
|
+
* @param {object} [options] - optional tweaks to normalizer behavior
|
|
804
|
+
* @returns {Promise<any>}
|
|
805
|
+
* @internal
|
|
806
|
+
*/
|
|
807
|
+
|
|
808
|
+
async normalizeValue(value, target, location = new SchemaLocation(this), options) {
|
|
809
|
+
return this._normalizeValue(value, target, location, options)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Transform a normalized input value for the final target based on this schema and provided context.
|
|
814
|
+
*
|
|
815
|
+
* Runs all transformer value processors in a pipeline until one returns undefined or throws an error.
|
|
816
|
+
* - The input to the pipeline is assumed to be normalized.
|
|
817
|
+
* - An error may be thrown if the input cannot be transformed.
|
|
818
|
+
* - If a transformer depends upon the overall target, it may return undefined to signal
|
|
819
|
+
* that the transform should be retried when the target is updated.
|
|
820
|
+
*
|
|
821
|
+
* A schema's transform is allowed to enforce validation internally, or it can delegate this to
|
|
822
|
+
* a finalizer or validator. In any case, the output from the transform is not guaranteed to be valid.
|
|
823
|
+
* (Note that conditions and unions are not checked if you call this directly.)
|
|
824
|
+
*
|
|
825
|
+
* Child properties are not traversed in this call, and are presumed to already have been transformed.
|
|
826
|
+
*
|
|
827
|
+
* (This is an executor function that may return synchronous or asynchronous results.)
|
|
828
|
+
*
|
|
829
|
+
* @param {any} value - input value to transform
|
|
830
|
+
* @param {any} [target] - global target in case the transformer depends on it
|
|
831
|
+
* @param {SchemaLocation} [location] - the traversal location of the schema
|
|
832
|
+
* @param {object} [options] - any tweaks to the transformer behavior
|
|
833
|
+
* @returns {any|Promise<any>} - transformed value
|
|
834
|
+
* @internal
|
|
835
|
+
*/
|
|
836
|
+
_transformValue(value, target, location = new SchemaLocation(this), options = {}) {
|
|
837
|
+
if (location.schema !== this) {
|
|
838
|
+
return location.schema._transformValue(value, target, location, options);
|
|
839
|
+
}
|
|
840
|
+
if (value instanceof Error && !this.options.allowErrors) {
|
|
841
|
+
throw new TransformError(value.message, {cause: value});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (this.isImplicit) {
|
|
845
|
+
throw new TransformError('Cannot transform a value for an implicit schema', {location});
|
|
846
|
+
}
|
|
847
|
+
if (value === null || (value === undefined && !this.options.allowUndefined)) {
|
|
848
|
+
return value;
|
|
849
|
+
}
|
|
850
|
+
let result;
|
|
851
|
+
try {
|
|
852
|
+
this.ensureAccepts(value);
|
|
853
|
+
const transformer = this.getValueProcessor('transformers');
|
|
854
|
+
if (transformer === undefined) {
|
|
855
|
+
return value;
|
|
856
|
+
}
|
|
857
|
+
result = transformer.execute(value, target, location, options);
|
|
858
|
+
}
|
|
859
|
+
catch (error) {
|
|
860
|
+
throw new TransformError('Unable to transform', {value, location, cause: error});
|
|
861
|
+
}
|
|
862
|
+
if (result instanceof Promise) {
|
|
863
|
+
return result.then(
|
|
864
|
+
resolved => this._checkValue(resolved, TransformError),
|
|
865
|
+
rejected => { throw new TransformError('Unable to transform', {value, location, cause: rejected}) }
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
return this._checkValue(result, TransformError);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Transform a normalized input value for the final target based on this schema and provided context.
|
|
872
|
+
*
|
|
873
|
+
* Runs all transformer value processors in a pipeline until one returns undefined or throws an error.
|
|
874
|
+
* - The input to the pipeline is assumed to be normalized.
|
|
875
|
+
* - An error may be thrown if the input cannot be transformed.
|
|
876
|
+
* - If a transformer depends upon the overall target, it may return undefined to signal
|
|
877
|
+
* that the transform should be retried when the target is updated.
|
|
878
|
+
*
|
|
879
|
+
* A schema's transform is allowed to enforce validation internally, or it can delegate this to
|
|
880
|
+
* a finalizer or validator. In any case, the output from the transform is not guaranteed to be valid.
|
|
881
|
+
* (Note that conditions and unions are not checked if you call this directly.)
|
|
882
|
+
*
|
|
883
|
+
* Child properties are not traversed in this call, and are presumed to already have been transformed.
|
|
884
|
+
*
|
|
885
|
+
* (This an async wrapper around the internal `_transformValue` executor function.)
|
|
886
|
+
*
|
|
887
|
+
* @param {any} value - input value to transform
|
|
888
|
+
* @param {any} [target] - global target in case the transformer depends on it
|
|
889
|
+
* @param {SchemaLocation} [location] - the traversal location of the schema
|
|
890
|
+
* @param {object} [options] - any tweaks to the transformer behavior
|
|
891
|
+
* @returns {Promise<any>} - transformed value
|
|
892
|
+
* @internal
|
|
893
|
+
*/
|
|
894
|
+
async transformValue(value, target, location = new SchemaLocation(this), options) {
|
|
895
|
+
return this._transformValue(value, target, location, options);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Finalize a transformed input value by running any necessary post-processing steps.
|
|
900
|
+
*
|
|
901
|
+
* Runs all finalizer value processors in a pipeline until one returns undefined or throws an error.
|
|
902
|
+
* - The input to the pipeline is be assumed to be transformed.
|
|
903
|
+
* - Finalizers are generally only required for incremental schemas that need to check child values.
|
|
904
|
+
* - A finalizer on the root schema (or that checks for the root path, if the root schema is shared)
|
|
905
|
+
* can act as an "entire output" finalizer.
|
|
906
|
+
*
|
|
907
|
+
* (This is an executor function that may return synchronous or asynchronous results.)
|
|
908
|
+
*
|
|
909
|
+
* @param {any} value - input value to finalize
|
|
910
|
+
* @param {any} [target] - global target in case the finalizer depends on it
|
|
911
|
+
* @param {SchemaLocation} [location] - the traversal location of the schema
|
|
912
|
+
* @param {object} [options] - any tweaks to the finalizer behavior
|
|
913
|
+
* @returns {any|Promise<any>} - finalized value
|
|
914
|
+
* @internal
|
|
915
|
+
*/
|
|
916
|
+
_finalizeValue(value, target, location = new SchemaLocation(this), options = {}) {
|
|
917
|
+
if (location.schema !== this) {
|
|
918
|
+
return location.schema._finalizeValue(value, target, location, options);
|
|
919
|
+
}
|
|
920
|
+
if (value instanceof Error && !this.options.allowErrors) {
|
|
921
|
+
throw new FinalizeError(value.message, {cause: value});
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (this.isImplicit) {
|
|
925
|
+
throw new TransformError('Cannot finalize a value for an implicit schema', {location});
|
|
926
|
+
}
|
|
927
|
+
if (value === null || (value === undefined && !this.options.allowUndefined)) {
|
|
928
|
+
return value;
|
|
929
|
+
}
|
|
930
|
+
let result;
|
|
931
|
+
try {
|
|
932
|
+
const finalizer = this.getValueProcessor('finalizers');
|
|
933
|
+
if (finalizer === undefined) {
|
|
934
|
+
return value;
|
|
935
|
+
}
|
|
936
|
+
result = finalizer.execute(value, target, location, options);
|
|
937
|
+
}
|
|
938
|
+
catch (error) {
|
|
939
|
+
throw new FinalizeError('Unable to finalize', {value, location, cause: error});
|
|
940
|
+
}
|
|
941
|
+
if (result instanceof Promise) {
|
|
942
|
+
return result.then(
|
|
943
|
+
resolved => this._checkValue(resolved, FinalizeError),
|
|
944
|
+
rejected => { throw new FinalizeError('Unable to finalize', {value, location, cause: rejected}) }
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
return this._checkValue(result, FinalizeError);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Finalize a transformed input value by running any necessary post-processing steps.
|
|
951
|
+
*
|
|
952
|
+
* Runs all finalizer value processors in a pipeline until one returns undefined or throws an error.
|
|
953
|
+
* - The input to the pipeline is be assumed to be transformed.
|
|
954
|
+
* - Finalizers are generally only required for incremental schemas that need to check child values.
|
|
955
|
+
* - A finalizer on the root schema (or that checks for the root path, if the root schema is shared)
|
|
956
|
+
* can act as an "entire output" finalizer.
|
|
957
|
+
*
|
|
958
|
+
* (This an async wrapper around the internal `_finalizeValue` executor function.)
|
|
959
|
+
*
|
|
960
|
+
* @param {any} value - input value to transform
|
|
961
|
+
* @param {any} [target] - global target in case the transformer depends on it
|
|
962
|
+
* @param {SchemaLocation} [location] - the traversal location of the schema
|
|
963
|
+
* @param {object} [options] - any tweaks to the transformer behavior
|
|
964
|
+
* @returns {Promise<any>} - transformed value
|
|
965
|
+
* @internal
|
|
966
|
+
*/
|
|
967
|
+
async finalizeValue(value, target, location = new SchemaLocation(this), options) {
|
|
968
|
+
return this._finalizeValue(value, target, location, options);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Validate the provided input value.
|
|
973
|
+
*
|
|
974
|
+
* Runs all validator value processors in a pipeline until one throws an error.
|
|
975
|
+
* Validators can return a different value from the input (presumably "more valid",
|
|
976
|
+
* e.g. strings trimmed, case made consistent, etc.) but must throw if the input is invalid.
|
|
977
|
+
*
|
|
978
|
+
* Children are not traversed in this call.
|
|
979
|
+
*
|
|
980
|
+
* (This is an executor function that may return synchronous or asynchronous results.)
|
|
981
|
+
*
|
|
982
|
+
* @param {any} value - value to validate
|
|
983
|
+
* @param {any} [target] - complete output target
|
|
984
|
+
* @param {SchemaLocation} [location] - traversal location of this schema
|
|
985
|
+
* @param {object} [options] - options to tweak validation behavior
|
|
986
|
+
* @returns {any|Promise<any>}
|
|
987
|
+
* @internal
|
|
988
|
+
*/
|
|
989
|
+
_validateValue(value, target, location = new SchemaLocation(this), options = {}) {
|
|
990
|
+
if (location.schema !== this) {
|
|
991
|
+
return location.schema._validateValue(value, target, location, options);
|
|
992
|
+
}
|
|
993
|
+
if (this.required === true && value === undefined) {
|
|
994
|
+
throw new ValidationError('Missing required value', {location});
|
|
995
|
+
}
|
|
996
|
+
if (value === null || value === undefined) {
|
|
997
|
+
return value;
|
|
998
|
+
}
|
|
999
|
+
if (value instanceof Error && !this.options.allowErrors) {
|
|
1000
|
+
throw new ValidationError(value.message, {cause: value});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const validator = this.getValueProcessor('validators');
|
|
1004
|
+
|
|
1005
|
+
if (!validator) {
|
|
1006
|
+
return value;
|
|
1007
|
+
}
|
|
1008
|
+
const revalidate = options?.revalidate ?? location.schema.options?.revalidate ?? true;
|
|
1009
|
+
|
|
1010
|
+
let result;
|
|
1011
|
+
try {
|
|
1012
|
+
result = validator.execute(value, target, location, options);
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
if (error instanceof ValidationError) {
|
|
1016
|
+
throw error;
|
|
1017
|
+
}
|
|
1018
|
+
// for debugging: result = validator.execute(value, target, location, options);
|
|
1019
|
+
throw new ValidationError('Validation failed', {value, location, cause: error});
|
|
1020
|
+
}
|
|
1021
|
+
if (result instanceof Promise) {
|
|
1022
|
+
return result.then(
|
|
1023
|
+
resolved => {
|
|
1024
|
+
if (resolved !== value && resolved !== undefined && revalidate) {
|
|
1025
|
+
return this._validateValue(resolved, target, location, {...options, revalidate: false})
|
|
1026
|
+
}
|
|
1027
|
+
return (resolved === undefined)? value : this._checkValue(resolved, ValidationError);
|
|
1028
|
+
}, // validation cannot clear data
|
|
1029
|
+
rejected => {
|
|
1030
|
+
if (rejected instanceof ValidationError) {
|
|
1031
|
+
throw rejected;
|
|
1032
|
+
}
|
|
1033
|
+
throw new ValidationError('Validation failed', {value, location, cause: rejected});
|
|
1034
|
+
}
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
if (result !== value && result !== undefined && revalidate) {
|
|
1038
|
+
return this._validateValue(result, target, location, {...options, revalidate: false})
|
|
1039
|
+
}
|
|
1040
|
+
return (result === undefined)? value : this._checkValue(result, ValidationError);
|
|
1041
|
+
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Validate the provided input value.
|
|
1045
|
+
*
|
|
1046
|
+
* Runs all validator value processors in a pipeline until one throws an error.
|
|
1047
|
+
* Validators can return a different value from the input (presumably "more valid",
|
|
1048
|
+
* e.g. strings trimmed, case made consistent, etc.) but must throw if the input is invalid.
|
|
1049
|
+
*
|
|
1050
|
+
* Children are not traversed in this call.
|
|
1051
|
+
*
|
|
1052
|
+
* @param {any} value - value to validate
|
|
1053
|
+
* @param {any} [target] - complete output target
|
|
1054
|
+
* @param {SchemaLocation} [location] - traversal location of this schema
|
|
1055
|
+
* @param {object} [options] - options to tweak validation behavior
|
|
1056
|
+
* @returns {Promise<any>}
|
|
1057
|
+
* @internal
|
|
1058
|
+
*/
|
|
1059
|
+
async validateValue(value, target, location, options) {
|
|
1060
|
+
return this._validateValue(value, target, location, options);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Serialize the provided input value.
|
|
1066
|
+
*
|
|
1067
|
+
* (This is an executor function that may return synchronous or asynchronous results.)
|
|
1068
|
+
*
|
|
1069
|
+
* @param {any} value - value to serialize
|
|
1070
|
+
* @param {any} [target] - entire output being serialized
|
|
1071
|
+
* @param {SchemaLocation} [location] - traversal location to current schema
|
|
1072
|
+
* @param {object} [options] - options to tweak serialization behavior
|
|
1073
|
+
* @returns {any|Promise<any>}
|
|
1074
|
+
* @internal
|
|
1075
|
+
*/
|
|
1076
|
+
_serializeValue(value, target, location = new SchemaLocation(this), options = {}) {
|
|
1077
|
+
if (location.schema !== this) {
|
|
1078
|
+
return location.schema._serializeValue(value, target, location, options);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
if (value === undefined || value === null || this.isImplicit || isTruthy(this.metadata.omitFromSerialize)) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
const serializer = this.getValueProcessor('serializers') ?? this.getValueProcessor('normalizers');
|
|
1085
|
+
|
|
1086
|
+
if (!serializer) {
|
|
1087
|
+
return value;
|
|
1088
|
+
/* todo - remove, or test thoroughly before reactivating, potential impact on children feels sus...
|
|
1089
|
+
if (this.isOpaque) {
|
|
1090
|
+
try {
|
|
1091
|
+
// perhaps they implemented JSON.stringify?
|
|
1092
|
+
return parse(stringify(value))
|
|
1093
|
+
}
|
|
1094
|
+
catch (error) {
|
|
1095
|
+
return value;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
else if (this.isContainer) {
|
|
1099
|
+
if (this.isArray && Array.isArray(value)) {
|
|
1100
|
+
return [...value];
|
|
1101
|
+
}
|
|
1102
|
+
else if (isPlainObject(value)) {
|
|
1103
|
+
return {...value};
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return value;
|
|
1107
|
+
|
|
1108
|
+
*/
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
let result;
|
|
1112
|
+
try {
|
|
1113
|
+
result = serializer.execute(value, target, location, options);
|
|
1114
|
+
}
|
|
1115
|
+
catch (error) {
|
|
1116
|
+
if (options?.strict) {
|
|
1117
|
+
throw new SerializeError('Unable to serialize', {value, location, cause: error});
|
|
1118
|
+
}
|
|
1119
|
+
return undefined;
|
|
1120
|
+
}
|
|
1121
|
+
if (result instanceof Promise) {
|
|
1122
|
+
return result.then(
|
|
1123
|
+
resolved => options?.strict? this._checkValue(resolved, SerializeError) : resolved,
|
|
1124
|
+
rejected => {
|
|
1125
|
+
if (options?.strict) {
|
|
1126
|
+
throw new SerializeError('Unable to serialize', {value, location, cause: rejected});
|
|
1127
|
+
}
|
|
1128
|
+
return undefined;
|
|
1129
|
+
}
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
return options?.strict? this._checkValue(result, SerializeError) : result;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Serialize the provided input value.
|
|
1136
|
+
*
|
|
1137
|
+
* @param {any} value - value to serialize
|
|
1138
|
+
* @param {any} [target] - entire output being serialized
|
|
1139
|
+
* @param {SchemaLocation} [location] - traversal location to current schema
|
|
1140
|
+
* @param {object} [options] - options to tweak serialization behavior
|
|
1141
|
+
* @returns {Promise<any>}
|
|
1142
|
+
* @internal
|
|
1143
|
+
*/
|
|
1144
|
+
async serializeValue(value, target, location = new SchemaLocation(this), options) {
|
|
1145
|
+
return this._serializeValue(value, target, location, options);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Throw an exception if this schema seems able to handle a given input value.
|
|
1150
|
+
*
|
|
1151
|
+
* @param {any} value
|
|
1152
|
+
*/
|
|
1153
|
+
ensureAccepts(value) {
|
|
1154
|
+
if (value === undefined) {
|
|
1155
|
+
if (this.options.allowUndefined) {
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
throw new ValidationError('Schema does not accept an undefined input');
|
|
1159
|
+
}
|
|
1160
|
+
if (!Array.isArray(this.values) || this.values.length === 0) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (this.options.allowUnknownValues) {
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
const found = this.values.some(v => deepEquals(v, value));
|
|
1167
|
+
|
|
1168
|
+
if (!found) {
|
|
1169
|
+
// todo - consider using valueDescription metadata
|
|
1170
|
+
const valueDescription = this.metadata.valueDescription ?? `{${this.values.map(formatValue).join('|')}}`;
|
|
1171
|
+
const details = (valueDescription.length > 80)? `not found in schema "values" option` : `expected one of ${valueDescription}`;
|
|
1172
|
+
|
|
1173
|
+
throw new ValidationError(`Invalid value ${formatValue(value)}, ${details}`);
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* @param {any} value
|
|
1180
|
+
* @returns {boolean}
|
|
1181
|
+
*/
|
|
1182
|
+
checkAccepts(value) {
|
|
1183
|
+
try {
|
|
1184
|
+
this.ensureAccepts(value);
|
|
1185
|
+
return true;
|
|
1186
|
+
}
|
|
1187
|
+
catch (e) {
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Return a validated output if and only if the input fully matches the schema definition.
|
|
1194
|
+
*
|
|
1195
|
+
* Note: depending on the processors used for validation, the input value may be mutated during validation!
|
|
1196
|
+
* (todo - create an option that prevents this from happening?)
|
|
1197
|
+
*
|
|
1198
|
+
* @param {any} value - input value to validate
|
|
1199
|
+
* @param {ValidateOptions} [options] - any tweaks to the validator behavior
|
|
1200
|
+
* @returns {any|Promise<any>} - validated value
|
|
1201
|
+
* @internal
|
|
1202
|
+
*/
|
|
1203
|
+
_validate(value, options = {}) {
|
|
1204
|
+
const location = options.location ?? new SchemaLocation(this);
|
|
1205
|
+
const context = (options.context instanceof TraversalContext) ? options.context : new TraversalContext(location, options);
|
|
1206
|
+
|
|
1207
|
+
const executors = [
|
|
1208
|
+
() => {
|
|
1209
|
+
context.setAssignedInput(value, '');
|
|
1210
|
+
return context.traverse(PRELOAD_EXECUTOR);
|
|
1211
|
+
},
|
|
1212
|
+
() => {
|
|
1213
|
+
for (const state of context.stateMap.values()) {
|
|
1214
|
+
state.completed = false; // yuck.
|
|
1215
|
+
}
|
|
1216
|
+
return context.traverse(VALIDATE_EXECUTOR);
|
|
1217
|
+
},
|
|
1218
|
+
() => context.getValue()
|
|
1219
|
+
];
|
|
1220
|
+
|
|
1221
|
+
return new PipelineExecutor(executors).execute(context);
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Return a validated output if and only if the input fully matches the schema definition.
|
|
1225
|
+
*
|
|
1226
|
+
* Note: depending on the processors used for validation, the input value may be mutated during validation!
|
|
1227
|
+
* (todo - create an option that prevents this from happening?)
|
|
1228
|
+
*
|
|
1229
|
+
* @param {any} value - input value to validate
|
|
1230
|
+
* @param {ValidateOptions} [options] - any tweaks to the validator behavior
|
|
1231
|
+
* @returns {Promise<any>} - validated value
|
|
1232
|
+
*/
|
|
1233
|
+
async validate(value, options) {
|
|
1234
|
+
return this._validate(value, options);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Process an input value to an output value based on this schema.
|
|
1239
|
+
*
|
|
1240
|
+
* Errors are thrown if:
|
|
1241
|
+
* - the input value doesn't match the schema
|
|
1242
|
+
* - a value processor throws an error
|
|
1243
|
+
* - a value cannot be processed (value processors return undefined) after repeated attempts
|
|
1244
|
+
* - a union cannot be resolved
|
|
1245
|
+
*
|
|
1246
|
+
* If an output target is provided, it is assumed to already be valid.
|
|
1247
|
+
* A few traversal options are supported; pass in a TraversalContext instance for full customization.
|
|
1248
|
+
*
|
|
1249
|
+
* (This is an executor function that may return synchronous or asynchronous results.)
|
|
1250
|
+
*
|
|
1251
|
+
* @param {any} input - the value to process
|
|
1252
|
+
* @param {any} [target] - preexisting output value to build upon, if any
|
|
1253
|
+
* @param {ProcessOptions} [options] - options to customize the traversal
|
|
1254
|
+
* @returns {any|Promise<any>} - returns the output value
|
|
1255
|
+
* @internal
|
|
1256
|
+
*/
|
|
1257
|
+
_process(input, target, options = {}) {
|
|
1258
|
+
const location = options?.location ?? new SchemaLocation(this);
|
|
1259
|
+
const context = (options.context instanceof TraversalContext) ? options.context : new TraversalContext(location, {strict: options?.strict, deep: options?.deep, debug: options?.debug});
|
|
1260
|
+
|
|
1261
|
+
const executors = [];
|
|
1262
|
+
|
|
1263
|
+
if (target !== undefined) {
|
|
1264
|
+
executors.push(() => {
|
|
1265
|
+
context.setAssignedInput(target, '');
|
|
1266
|
+
return context.traverse(PRELOAD_EXECUTOR)
|
|
1267
|
+
})
|
|
1268
|
+
}
|
|
1269
|
+
executors.push(() => {
|
|
1270
|
+
context.setAssignedInput(input, options?.inputPath);
|
|
1271
|
+
if (options?.assignments) {
|
|
1272
|
+
for (const [assignmentPath, assignmentInput] of options.assignments) {
|
|
1273
|
+
context.setAssignedInput(assignmentInput, assignmentPath);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return context.traverse(PROCESS_EXECUTOR)
|
|
1277
|
+
});
|
|
1278
|
+
executors.push(() => {
|
|
1279
|
+
return context.getValue()
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
return new PipelineExecutor(executors).execute(context);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* Process an input value to an output value based on this schema.
|
|
1287
|
+
*
|
|
1288
|
+
* Errors are thrown if:
|
|
1289
|
+
* - the input value doesn't match the schema
|
|
1290
|
+
* - a value processor throws an error
|
|
1291
|
+
* - a value cannot be processed (value processors return undefined) after repeated attempts
|
|
1292
|
+
* - a union cannot be resolved
|
|
1293
|
+
*
|
|
1294
|
+
* If an output target is provided, it is assumed to already be valid.
|
|
1295
|
+
* A few traversal options are supported; pass in a TraversalContext instance for full customization.
|
|
1296
|
+
*
|
|
1297
|
+
* (This is an async wrapper around the internal `_process` executor function.)
|
|
1298
|
+
*
|
|
1299
|
+
* Note: this method makes `CompiledSchema` look somewhat like a `FunctionValueProcessor`, but it has a
|
|
1300
|
+
* slightly different signature.
|
|
1301
|
+
*
|
|
1302
|
+
* @param {any} input - the value to process
|
|
1303
|
+
* @param {any} [target] - preexisting output value to build upon, if any
|
|
1304
|
+
* @param {ProcessOptions} [options] - options to customize the traversal
|
|
1305
|
+
* @returns {Promise<any>} - returns the output value
|
|
1306
|
+
*/
|
|
1307
|
+
async process(input, target, options = {}) {
|
|
1308
|
+
return this._process(input, target, options);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Process input path/value assignments into an output value based on the schema.
|
|
1313
|
+
*
|
|
1314
|
+
* Paths are dotted references into the schema hierarchy. Assignment values are normalized, transformed,
|
|
1315
|
+
* validated, and then used to build the output value.
|
|
1316
|
+
*
|
|
1317
|
+
* If a value for the output target is provided, it is assumed to already be valid.
|
|
1318
|
+
*
|
|
1319
|
+
* Errors are thrown if:
|
|
1320
|
+
* - unexpected paths are provided that don't match the schema
|
|
1321
|
+
* - a value is incompatible with the schema referenced by the path
|
|
1322
|
+
* - a value processor throws an error
|
|
1323
|
+
* - a value cannot be processed (value processors return undefined) after repeated attempts
|
|
1324
|
+
* - a union cannot be resolved
|
|
1325
|
+
*
|
|
1326
|
+
* (This an async convenience wrapper around the internal `_process` executor function.)
|
|
1327
|
+
*
|
|
1328
|
+
* @param {Map<string,any>} assignments - path/value associations
|
|
1329
|
+
* @param {any} [target] - preexisting output value to build upon, if any
|
|
1330
|
+
* @param {ProcessAssignmentsOptions} [options] - options to customize the traversal
|
|
1331
|
+
* @returns {Promise<any>} - returns the output value
|
|
1332
|
+
*/
|
|
1333
|
+
async processAssignments(assignments, target, options = {}) {
|
|
1334
|
+
return this._process(undefined, target, {...options, assignments})
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Serialize the config data as if you were going to use the result for a config file.
|
|
1340
|
+
*
|
|
1341
|
+
* Runs all serialization processors in a pipeline. By default, if any processor returns undefined or throws an error,
|
|
1342
|
+
* serialization of that value returns undefined and will be omitted from the output. If you set the "strict"
|
|
1343
|
+
* option, errors are re-thrown.
|
|
1344
|
+
*
|
|
1345
|
+
* Serializers attempt to convert resolved values back to an input-friendly value, first via the "serialize"
|
|
1346
|
+
* schema option, or alternatively by trusting that each value is either already compatible, or implements toJSON().
|
|
1347
|
+
*
|
|
1348
|
+
* @param {any} value
|
|
1349
|
+
* @param {SerializeOptions} [options]
|
|
1350
|
+
* @returns {Promise<NonNullable<any>>}
|
|
1351
|
+
*/
|
|
1352
|
+
async serialize(value, options = {}) {
|
|
1353
|
+
const location = options?.location ?? new SchemaLocation(this);
|
|
1354
|
+
const context = (options.context instanceof TraversalContext) ? options.context : new TraversalContext(location, {strict: options?.strict, deep: options?.deep, debug: options?.debug});
|
|
1355
|
+
|
|
1356
|
+
// should be able to single-shot serialization
|
|
1357
|
+
|
|
1358
|
+
const executors = [
|
|
1359
|
+
() => {
|
|
1360
|
+
context.setAssignedInput(value, '');
|
|
1361
|
+
return context.traverse(SERIALIZE_EXECUTOR)
|
|
1362
|
+
},
|
|
1363
|
+
() => deepPrune(context.getValue())
|
|
1364
|
+
]
|
|
1365
|
+
return new PipelineExecutor(executors).execute(context);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* Given a reference to a union schema member, return the matching key, or undefined if it cannot be found.
|
|
1370
|
+
*
|
|
1371
|
+
* @param {CompiledSchema} unionSchema
|
|
1372
|
+
* @returns {string|undefined}
|
|
1373
|
+
*/
|
|
1374
|
+
findUnionKey(unionSchema) {
|
|
1375
|
+
if (!this.isUnion) {
|
|
1376
|
+
throw new SchemaError('Cannot find union key because schema is not a union');
|
|
1377
|
+
}
|
|
1378
|
+
for (const [k, s] of this.unionSchemaEntries) {
|
|
1379
|
+
if (s === unionSchema) {
|
|
1380
|
+
return k;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return undefined;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* Given a union key or schema, retrieve the matching union schema, or undefined if it cannot be found or is invalid
|
|
1388
|
+
*
|
|
1389
|
+
* @param {string|CompiledSchema} lookup
|
|
1390
|
+
* @returns {CompiledSchema|undefined}
|
|
1391
|
+
*/
|
|
1392
|
+
getUnionSchema(lookup) {
|
|
1393
|
+
if (!lookup) {
|
|
1394
|
+
return undefined
|
|
1395
|
+
}
|
|
1396
|
+
if (typeof lookup === 'string') {
|
|
1397
|
+
if (!this.isUnion) {
|
|
1398
|
+
throw new SchemaError(`Cannot get union schema with key "${lookup}" because schema is not a union`);
|
|
1399
|
+
}
|
|
1400
|
+
if (this.#unionSchemasMap.has(lookup)) {
|
|
1401
|
+
return this.#unionSchemasMap.get(lookup);
|
|
1402
|
+
}
|
|
1403
|
+
throw new SchemaError(`Unknown union schema key "${lookup}"`)
|
|
1404
|
+
}
|
|
1405
|
+
if (lookup instanceof CompiledSchema) {
|
|
1406
|
+
if (!this.isUnion) {
|
|
1407
|
+
throw new SchemaError('Schema is not a union');
|
|
1408
|
+
}
|
|
1409
|
+
if (this.findUnionKey(lookup)) {
|
|
1410
|
+
return lookup;
|
|
1411
|
+
}
|
|
1412
|
+
throw new SchemaError('Schema is not a union member');
|
|
1413
|
+
}
|
|
1414
|
+
throw new SchemaError('Unable to get union schema with provided data');
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* @param {string} key
|
|
1419
|
+
* @param {CompiledSchema} unionSchema
|
|
1420
|
+
* @returns {CompiledSchema}
|
|
1421
|
+
* @internal
|
|
1422
|
+
*/
|
|
1423
|
+
_setUnionSchema(key, unionSchema) {
|
|
1424
|
+
if (!key) {
|
|
1425
|
+
throw new SchemaError('Unable to set a union schema without a valid key');
|
|
1426
|
+
}
|
|
1427
|
+
if (!(unionSchema instanceof CompiledSchema)) {
|
|
1428
|
+
throw new SchemaError('Union schema must be a CompiledSchema instance');
|
|
1429
|
+
}
|
|
1430
|
+
if (this.#frozen) {
|
|
1431
|
+
throw new SchemaError(`Cannot add union schema "${key}" to a frozen CompiledSchema`);
|
|
1432
|
+
}
|
|
1433
|
+
// clear cache, if set
|
|
1434
|
+
this.#unionSchemas = undefined;
|
|
1435
|
+
|
|
1436
|
+
this.#unionSchemasMap.set(key, unionSchema);
|
|
1437
|
+
|
|
1438
|
+
return unionSchema;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Find the schema at a given path, falling back to the wildcard schema if one exists.
|
|
1444
|
+
*
|
|
1445
|
+
* The root schema may be found at '', the empty string. Array members are referenced with dotted integer indexes.
|
|
1446
|
+
* Note that find() does not check union members; use SchemaLocation for finding schemas resolved during traversal.
|
|
1447
|
+
*
|
|
1448
|
+
* @param {string} path
|
|
1449
|
+
* @returns {undefined|CompiledSchema}
|
|
1450
|
+
*/
|
|
1451
|
+
find(path) {
|
|
1452
|
+
if (!path || path === '' || path === '.') {
|
|
1453
|
+
return this;
|
|
1454
|
+
}
|
|
1455
|
+
const pathComponents = path.split('.');
|
|
1456
|
+
|
|
1457
|
+
/** @type {CompiledSchema|undefined} */
|
|
1458
|
+
let s = this;
|
|
1459
|
+
|
|
1460
|
+
for (const pathComponent of pathComponents) {
|
|
1461
|
+
s = s?.getPropertySchema(pathComponent);
|
|
1462
|
+
|
|
1463
|
+
if (!s) {
|
|
1464
|
+
return undefined;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return s;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* toAssignments - attempt to convert input data to a map of assignments.
|
|
1472
|
+
*
|
|
1473
|
+
* @param {any} object - input
|
|
1474
|
+
* @param {string} prefix - prefix to add to any path generated
|
|
1475
|
+
* @returns {Map<string, any>} - output map of path-to-value associations
|
|
1476
|
+
*/
|
|
1477
|
+
toAssignments(object, prefix = '') {
|
|
1478
|
+
if (typeof object !== 'object') {
|
|
1479
|
+
return new Map([['', object]])
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const assignments = new Map();
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
*
|
|
1486
|
+
* @param {CompiledSchema|undefined} schema
|
|
1487
|
+
* @param {any} current
|
|
1488
|
+
* @param {string} path
|
|
1489
|
+
*/
|
|
1490
|
+
function walk(schema, current, path) {
|
|
1491
|
+
const isContainer = Array.isArray(current) || isPlainObject(current);
|
|
1492
|
+
|
|
1493
|
+
const allowIncremental = schema?.allowIncremental ?? true;
|
|
1494
|
+
//const hasChildren = Boolean(schema?.hasChildren || schema?.isUnion && Object.values(schema.unionSchemas).find(s => s.hasChildren))
|
|
1495
|
+
|
|
1496
|
+
if (isContainer && schema?.hasChildren) {//} && allowIncremental) {
|
|
1497
|
+
const entries = Array.isArray(current)? current.entries() : Object.entries(current);
|
|
1498
|
+
|
|
1499
|
+
for (const [key, value] of entries) {
|
|
1500
|
+
const propertySchema = schema?.getPropertySchema(`${key}`);
|
|
1501
|
+
walk(propertySchema, value, path ? `${path}.${key}` : `${key}`)
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
else {
|
|
1505
|
+
assignments.set(path, current);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
walk(this, object, prefix);
|
|
1509
|
+
return assignments;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
/**
|
|
1513
|
+
* Invoke a provided visitor function on every schema node; if visitor returns false (explicitly), abort early.
|
|
1514
|
+
*
|
|
1515
|
+
* @param {(schema:CompiledSchema, path:string) => any} visitor - visitor function
|
|
1516
|
+
* @param {{onlySerializable?:boolean}} [options]
|
|
1517
|
+
* @returns {boolean} - returns true if visitors all returned true, false if any exited early
|
|
1518
|
+
*/
|
|
1519
|
+
visitSchema(visitor, options) {
|
|
1520
|
+
const walked = new Set();
|
|
1521
|
+
const onlySerializable = options?.onlySerializable ?? false;
|
|
1522
|
+
/**
|
|
1523
|
+
* @param {CompiledSchema} schema
|
|
1524
|
+
* @param {string} path
|
|
1525
|
+
* @returns {boolean|undefined}
|
|
1526
|
+
*/
|
|
1527
|
+
function walk(schema, path) {
|
|
1528
|
+
if (walked.has(schema)) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
walked.add(schema);
|
|
1532
|
+
if (schema.metadata.omitFromSerialize && onlySerializable) {
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (schema.hasChildren) {
|
|
1536
|
+
for (const [propName, propSchema] of schema.propertyEntries) {
|
|
1537
|
+
const childPath = path ? `${path}.${propName}` : `${propName}`;
|
|
1538
|
+
if (walk(propSchema, childPath) === false) {
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (schema.isUnion) {
|
|
1544
|
+
for (const [unionSchemaKey, unionSchema] of schema.unionSchemaEntries) {
|
|
1545
|
+
if (walk(unionSchema, path) === false) {
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return visitor(schema, path);
|
|
1551
|
+
}
|
|
1552
|
+
return walk(this, '') ?? true;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* Compute all possible schema paths (including union schema properties)
|
|
1559
|
+
*
|
|
1560
|
+
* @returns {Set<string>}
|
|
1561
|
+
*/
|
|
1562
|
+
getPropertyPaths() {
|
|
1563
|
+
const propertyPaths = new Set();
|
|
1564
|
+
|
|
1565
|
+
this.visitSchema((_schema, path) => {
|
|
1566
|
+
if (path) {
|
|
1567
|
+
propertyPaths.add(path);
|
|
1568
|
+
}
|
|
1569
|
+
})
|
|
1570
|
+
return propertyPaths;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
/**
|
|
1574
|
+
* Return a named property schema (possibly via wildcard)
|
|
1575
|
+
*
|
|
1576
|
+
* @param {string} propertyName
|
|
1577
|
+
* @returns {CompiledSchema|undefined}
|
|
1578
|
+
*/
|
|
1579
|
+
getPropertySchema(propertyName) {
|
|
1580
|
+
if (!propertyName) {
|
|
1581
|
+
throw new SchemaError('Unable to retrieve an unnamed property');
|
|
1582
|
+
}
|
|
1583
|
+
return this.#propertiesMap.get(propertyName) ?? this.#propertiesMap.get('*');
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Associate a schema with a property name. Only for use during compilation.
|
|
1588
|
+
*
|
|
1589
|
+
* @param {string} propertyName
|
|
1590
|
+
* @param {CompiledSchema} propertySchema
|
|
1591
|
+
* @returns {CompiledSchema}
|
|
1592
|
+
*
|
|
1593
|
+
* @internal
|
|
1594
|
+
*/
|
|
1595
|
+
_setPropertySchema(propertyName, propertySchema) {
|
|
1596
|
+
if (!propertyName) {
|
|
1597
|
+
throw new SchemaError('Unable to set an unnamed property');
|
|
1598
|
+
}
|
|
1599
|
+
if (!(propertySchema instanceof CompiledSchema)) {
|
|
1600
|
+
throw new SchemaError('Property schema must be a CompiledSchema instance');
|
|
1601
|
+
}
|
|
1602
|
+
if (this.#frozen) {
|
|
1603
|
+
throw new SchemaError(`Cannot add property ${propertyName} to a frozen CompiledSchema`);
|
|
1604
|
+
}
|
|
1605
|
+
// clear cache, if set
|
|
1606
|
+
this.#properties = undefined;
|
|
1607
|
+
|
|
1608
|
+
this.#propertiesMap.set(propertyName, propertySchema);
|
|
1609
|
+
this.#options.container ??= true;
|
|
1610
|
+
|
|
1611
|
+
return propertySchema;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
/**
|
|
1615
|
+
* Return all child schemas that have a particular option tag
|
|
1616
|
+
*
|
|
1617
|
+
* @param {string} tag
|
|
1618
|
+
* @returns {CompiledSchema[]}
|
|
1619
|
+
* @deprecated
|
|
1620
|
+
*/
|
|
1621
|
+
getTagged(tag) {
|
|
1622
|
+
const schemas = [];
|
|
1623
|
+
for (const propSchema of this.#propertiesMap.values()) {
|
|
1624
|
+
if (propSchema.options[tag]) {
|
|
1625
|
+
schemas.push(propSchema);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return schemas;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Get the first child schema that has a particular option tag
|
|
1633
|
+
*
|
|
1634
|
+
* @param {string} tag
|
|
1635
|
+
* @returns {CompiledSchema|undefined}
|
|
1636
|
+
* @deprecated
|
|
1637
|
+
*/
|
|
1638
|
+
getFirstTagged(tag) {
|
|
1639
|
+
for (const propSchema of this.#propertiesMap.values()) {
|
|
1640
|
+
if (propSchema.options[tag]) {
|
|
1641
|
+
return propSchema;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
return undefined;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/**
|
|
1648
|
+
* Return true if the path is legal within the schema (including all union schemas)
|
|
1649
|
+
*
|
|
1650
|
+
* @param {string} path
|
|
1651
|
+
* @returns {boolean}
|
|
1652
|
+
*/
|
|
1653
|
+
isValidPath(path) {
|
|
1654
|
+
if (path === '') {
|
|
1655
|
+
return true; // means the current schema!
|
|
1656
|
+
}
|
|
1657
|
+
const parts = path.split('.');
|
|
1658
|
+
|
|
1659
|
+
/**
|
|
1660
|
+
* @param {CompiledSchema} schema
|
|
1661
|
+
* @param {number} index
|
|
1662
|
+
* @returns {boolean}
|
|
1663
|
+
*/
|
|
1664
|
+
function check(schema, index = 0) {
|
|
1665
|
+
if (index >= parts.length) {
|
|
1666
|
+
return schema !== undefined;
|
|
1667
|
+
}
|
|
1668
|
+
const propertyName = parts[index];
|
|
1669
|
+
|
|
1670
|
+
const propertySchema = schema.getPropertySchema(propertyName);
|
|
1671
|
+
if (propertySchema) {
|
|
1672
|
+
return check(propertySchema, index + 1);
|
|
1673
|
+
}
|
|
1674
|
+
else if (schema.isUnion) {
|
|
1675
|
+
for (const unionSchema of schema.#unionSchemasMap.values()) {
|
|
1676
|
+
if (check(unionSchema, index)) {
|
|
1677
|
+
return true;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1683
|
+
return check(this);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* Write-protect this schema at the end of compilation.
|
|
1688
|
+
* @param {Set<CompiledSchema>} [seen]
|
|
1689
|
+
* @internal
|
|
1690
|
+
*/
|
|
1691
|
+
_freeze(seen = new Set()) {
|
|
1692
|
+
if (this.#frozen || seen.has(this)) {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
this.#frozen = true;
|
|
1696
|
+
seen.add(this);
|
|
1697
|
+
for (const childSchema of this.#propertiesMap.values()) {
|
|
1698
|
+
childSchema._freeze(seen);
|
|
1699
|
+
}
|
|
1700
|
+
Object.freeze(this.#propertiesMap);
|
|
1701
|
+
for (const unionSchema of this.#unionSchemasMap.values()) {
|
|
1702
|
+
unionSchema._freeze(seen);
|
|
1703
|
+
}
|
|
1704
|
+
Object.freeze(this.#unionSchemasMap);
|
|
1705
|
+
Object.freeze(this.#options);
|
|
1706
|
+
Object.freeze(this.#metadata);
|
|
1707
|
+
Object.freeze(this);
|
|
1708
|
+
}
|
|
1709
|
+
}
|