Add progressive web app companion for cross-platform access
Vite + TypeScript PWA that mirrors the Android app's core features: - Pre-processed shelter data (build-time UTM33N→WGS84 conversion) - Leaflet map with shelter markers, user location, and offline tiles - Canvas compass arrow (ported from DirectionArrowView.kt) - IndexedDB shelter cache with 7-day staleness check - Service worker with CacheFirst tiles and precached app shell - i18n for en, nb, nn (ported from Android strings.xml) - iOS/Android compass handling with low-pass filter - Respects user map interaction (no auto-snap on pan/zoom) - Build revision cache-breaker for reliable SW updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
46365b713b
commit
e8428de775
12051 changed files with 1799735 additions and 0 deletions
21
pwa/node_modules/@apideck/better-ajv-errors/LICENSE
generated
vendored
Normal file
21
pwa/node_modules/@apideck/better-ajv-errors/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Apideck
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
71
pwa/node_modules/@apideck/better-ajv-errors/README.md
generated
vendored
Normal file
71
pwa/node_modules/@apideck/better-ajv-errors/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
[](https://npmjs.com/@apideck/better-ajv-errors) [](https://npmjs.com/@apideck/better-ajv-errors) [](https://github.com/apideck-libraries/better-ajv-errors/actions/workflows/main.yml?query=branch%3Amain++)
|
||||
|
||||
# @apideck/better-ajv-errors 👮♀️
|
||||
|
||||
> Human-friendly JSON Schema validation for APIs
|
||||
|
||||
|
||||
- Readable and helpful [ajv](https://github.com/ajv-validator/ajv) errors
|
||||
- API-friendly format
|
||||
- Suggestions for spelling mistakes
|
||||
- Minimal footprint: 1.56 kB (gzip + minified)
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
$ yarn add @apideck/better-ajv-errors
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
$ npm i @apideck/better-ajv-errors
|
||||
```
|
||||
|
||||
Also make sure that you've installed [ajv](https://www.npmjs.com/package/ajv) at version 8 or higher.
|
||||
|
||||
## Usage
|
||||
|
||||
After validating some data with ajv, pass the errors to `betterAjvErrors`
|
||||
|
||||
```ts
|
||||
import Ajv from 'ajv';
|
||||
import { betterAjvErrors } from '@apideck/better-ajv-errors';
|
||||
|
||||
// Without allErrors: true, ajv will only return the first error
|
||||
const ajv = new Ajv({ allErrors: true });
|
||||
|
||||
const valid = ajv.validate(schema, data);
|
||||
|
||||
if (!valid) {
|
||||
const betterErrors = betterAjvErrors({ schema, data, errors: ajv.errors });
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### betterAjvErrors
|
||||
|
||||
Function that formats ajv validation errors in a human-friendly format.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `options: BetterAjvErrorsOptions`
|
||||
- `errors: ErrorObject[] | null | undefined` Your ajv errors, you will find these in the `errors` property of your ajv instance (`ErrorObject` is a type from the ajv package).
|
||||
- `data: Object` The data you passed to ajv to be validated.
|
||||
- `schema: JSONSchema` The schema you passed to ajv to validate against.
|
||||
- `basePath?: string` An optional base path to prefix paths returned by `betterAjvErrors`. For example, in APIs, it could be useful to use `'{requestBody}'` or `'{queryParemeters}'` as a basePath. This will make it clear to users where exactly the error occurred.
|
||||
|
||||
#### Return Value
|
||||
|
||||
- `ValidationError[]` Array of formatted errors (properties of `ValidationError` below)
|
||||
- `message: string` Formatted error message
|
||||
- `suggestion?: string` Optional suggestion based on provided data and schema
|
||||
- `path: string` Object path where the error occurred (example: `.foo.bar.0.quz`)
|
||||
- `context: { errorType: DefinedError['keyword']; [additionalContext: string]: unknown }` `errorType` is `error.keyword` proxied from `ajv`. `errorType` can be used as a key for i18n if needed. There might be additional properties on context, based on the type of error.
|
||||
|
||||
## Related
|
||||
|
||||
- [atlassian/better-ajv-errors](https://github.com/atlassian/better-ajv-errors) was the inspiration for this library. Atlassian's library is more focused on CLI errors, this library is focused on developer-friendly API error messages.
|
||||
246
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.development.js
generated
vendored
Normal file
246
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.development.js
generated
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
||||
|
||||
var leven = _interopDefault(require('leven'));
|
||||
var pointer = _interopDefault(require('jsonpointer'));
|
||||
|
||||
function _extends() {
|
||||
_extends = Object.assign || function (target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i];
|
||||
|
||||
for (var key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
return _extends.apply(this, arguments);
|
||||
}
|
||||
|
||||
var AJV_ERROR_KEYWORD_WEIGHT_MAP = {
|
||||
"enum": 1,
|
||||
type: 0
|
||||
};
|
||||
var QUOTES_REGEX = /"/g;
|
||||
var NOT_REGEX = /NOT/g;
|
||||
var SLASH_REGEX = /\//g;
|
||||
|
||||
var filterSingleErrorPerProperty = function filterSingleErrorPerProperty(errors) {
|
||||
var errorsPerProperty = errors.reduce(function (acc, error) {
|
||||
var _ref, _error$params$additio, _error$params, _error$params2, _AJV_ERROR_KEYWORD_WE, _AJV_ERROR_KEYWORD_WE2;
|
||||
|
||||
var prop = error.instancePath + ((_ref = (_error$params$additio = (_error$params = error.params) == null ? void 0 : _error$params.additionalProperty) != null ? _error$params$additio : (_error$params2 = error.params) == null ? void 0 : _error$params2.missingProperty) != null ? _ref : '');
|
||||
var existingError = acc[prop];
|
||||
|
||||
if (!existingError) {
|
||||
acc[prop] = error;
|
||||
return acc;
|
||||
}
|
||||
|
||||
var weight = (_AJV_ERROR_KEYWORD_WE = AJV_ERROR_KEYWORD_WEIGHT_MAP[error.keyword]) != null ? _AJV_ERROR_KEYWORD_WE : 0;
|
||||
var existingWeight = (_AJV_ERROR_KEYWORD_WE2 = AJV_ERROR_KEYWORD_WEIGHT_MAP[existingError.keyword]) != null ? _AJV_ERROR_KEYWORD_WE2 : 0;
|
||||
|
||||
if (weight > existingWeight) {
|
||||
acc[prop] = error;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
return Object.values(errorsPerProperty);
|
||||
};
|
||||
|
||||
var getSuggestion = function getSuggestion(_ref) {
|
||||
var value = _ref.value,
|
||||
suggestions = _ref.suggestions,
|
||||
_ref$format = _ref.format,
|
||||
format = _ref$format === void 0 ? function (suggestion) {
|
||||
return "Did you mean '" + suggestion + "'?";
|
||||
} : _ref$format;
|
||||
if (!value) return '';
|
||||
var bestSuggestion = suggestions.reduce(function (best, current) {
|
||||
var distance = leven(value, current);
|
||||
|
||||
if (best.distance > distance) {
|
||||
return {
|
||||
value: current,
|
||||
distance: distance
|
||||
};
|
||||
}
|
||||
|
||||
return best;
|
||||
}, {
|
||||
distance: Infinity,
|
||||
value: ''
|
||||
});
|
||||
return bestSuggestion.distance < value.length ? format(bestSuggestion.value) : '';
|
||||
};
|
||||
|
||||
var pointerToDotNotation = function pointerToDotNotation(pointer) {
|
||||
return pointer.replace(SLASH_REGEX, '.');
|
||||
};
|
||||
var cleanAjvMessage = function cleanAjvMessage(message) {
|
||||
return message.replace(QUOTES_REGEX, "'").replace(NOT_REGEX, 'not');
|
||||
};
|
||||
var getLastSegment = function getLastSegment(path) {
|
||||
var segments = path.split('/');
|
||||
return segments.pop();
|
||||
};
|
||||
var safeJsonPointer = function safeJsonPointer(_ref) {
|
||||
var object = _ref.object,
|
||||
pnter = _ref.pnter,
|
||||
fallback = _ref.fallback;
|
||||
|
||||
try {
|
||||
return pointer.get(object, pnter);
|
||||
} catch (err) {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
var betterAjvErrors = function betterAjvErrors(_ref) {
|
||||
var errors = _ref.errors,
|
||||
data = _ref.data,
|
||||
schema = _ref.schema,
|
||||
_ref$basePath = _ref.basePath,
|
||||
basePath = _ref$basePath === void 0 ? '{base}' : _ref$basePath;
|
||||
|
||||
if (!Array.isArray(errors) || errors.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var definedErrors = filterSingleErrorPerProperty(errors);
|
||||
return definedErrors.map(function (error) {
|
||||
var path = pointerToDotNotation(basePath + error.instancePath);
|
||||
var prop = getLastSegment(error.instancePath);
|
||||
var defaultContext = {
|
||||
errorType: error.keyword
|
||||
};
|
||||
var defaultMessage = (prop ? "property '" + prop + "'" : path) + " " + cleanAjvMessage(error.message);
|
||||
var validationError;
|
||||
|
||||
switch (error.keyword) {
|
||||
case 'additionalProperties':
|
||||
{
|
||||
var additionalProp = error.params.additionalProperty;
|
||||
var suggestionPointer = error.schemaPath.replace('#', '').replace('/additionalProperties', '');
|
||||
|
||||
var _safeJsonPointer = safeJsonPointer({
|
||||
object: schema,
|
||||
pnter: suggestionPointer,
|
||||
fallback: {
|
||||
properties: {}
|
||||
}
|
||||
}),
|
||||
properties = _safeJsonPointer.properties;
|
||||
|
||||
validationError = {
|
||||
message: "'" + additionalProp + "' property is not expected to be here",
|
||||
suggestion: getSuggestion({
|
||||
value: additionalProp,
|
||||
suggestions: Object.keys(properties != null ? properties : {}),
|
||||
format: function format(suggestion) {
|
||||
return "Did you mean property '" + suggestion + "'?";
|
||||
}
|
||||
}),
|
||||
path: path,
|
||||
context: defaultContext
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'enum':
|
||||
{
|
||||
var suggestions = error.params.allowedValues.map(function (value) {
|
||||
return value.toString();
|
||||
});
|
||||
|
||||
var _prop = getLastSegment(error.instancePath);
|
||||
|
||||
var value = safeJsonPointer({
|
||||
object: data,
|
||||
pnter: error.instancePath,
|
||||
fallback: ''
|
||||
});
|
||||
validationError = {
|
||||
message: "'" + _prop + "' property must be equal to one of the allowed values",
|
||||
suggestion: getSuggestion({
|
||||
value: value,
|
||||
suggestions: suggestions
|
||||
}),
|
||||
path: path,
|
||||
context: _extends({}, defaultContext, {
|
||||
allowedValues: error.params.allowedValues
|
||||
})
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'type':
|
||||
{
|
||||
var _prop2 = getLastSegment(error.instancePath);
|
||||
|
||||
var type = error.params.type;
|
||||
validationError = {
|
||||
message: "'" + _prop2 + "' property type must be " + type,
|
||||
path: path,
|
||||
context: defaultContext
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'required':
|
||||
{
|
||||
validationError = {
|
||||
message: path + " must have required property '" + error.params.missingProperty + "'",
|
||||
path: path,
|
||||
context: defaultContext
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'const':
|
||||
{
|
||||
return {
|
||||
message: "'" + prop + "' property must be equal to the allowed value",
|
||||
path: path,
|
||||
context: _extends({}, defaultContext, {
|
||||
allowedValue: error.params.allowedValue
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
message: defaultMessage,
|
||||
path: path,
|
||||
context: defaultContext
|
||||
};
|
||||
} // Remove empty properties
|
||||
|
||||
|
||||
var errorEntries = Object.entries(validationError);
|
||||
|
||||
for (var _i = 0, _errorEntries = errorEntries; _i < _errorEntries.length; _i++) {
|
||||
var _errorEntries$_i = _errorEntries[_i],
|
||||
key = _errorEntries$_i[0],
|
||||
_value = _errorEntries$_i[1];
|
||||
|
||||
if (_value === null || _value === undefined || _value === '') {
|
||||
delete validationError[key];
|
||||
}
|
||||
}
|
||||
|
||||
return validationError;
|
||||
});
|
||||
};
|
||||
|
||||
exports.betterAjvErrors = betterAjvErrors;
|
||||
//# sourceMappingURL=better-ajv-errors.cjs.development.js.map
|
||||
1
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.development.js.map
generated
vendored
Normal file
1
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.development.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.production.min.js
generated
vendored
Normal file
2
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.production.min.js
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var t=e(require("leven")),r=e(require("jsonpointer"));function a(){return(a=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e}).apply(this,arguments)}var n={enum:1,type:0},o=/"/g,s=/NOT/g,u=/\//g,l=function(e){var r=e.value,a=e.format,n=void 0===a?function(e){return"Did you mean '"+e+"'?"}:a;if(!r)return"";var o=e.suggestions.reduce((function(e,a){var n=t(r,a);return e.distance>n?{value:a,distance:n}:e}),{distance:Infinity,value:""});return o.distance<r.length?n(o.value):""},i=function(e){return e.split("/").pop()},p=function(e){var t=e.object,a=e.pnter,n=e.fallback;try{return r.get(t,a)}catch(e){return n}};exports.betterAjvErrors=function(e){var t=e.errors,r=e.data,c=e.schema,d=e.basePath,v=void 0===d?"{base}":d;return Array.isArray(t)&&0!==t.length?function(e){var t=e.reduce((function(e,t){var r,a,o,s,u,l,i=t.instancePath+(null!=(r=null!=(a=null==(o=t.params)?void 0:o.additionalProperty)?a:null==(s=t.params)?void 0:s.missingProperty)?r:""),p=e[i];return p?((null!=(u=n[t.keyword])?u:0)>(null!=(l=n[p.keyword])?l:0)&&(e[i]=t),e):(e[i]=t,e)}),{});return Object.values(t)}(t).map((function(e){var t,n=function(e){return e.replace(u,".")}(v+e.instancePath),d=i(e.instancePath),y={errorType:e.keyword},f=(d?"property '"+d+"'":n)+" "+e.message.replace(o,"'").replace(s,"not");switch(e.keyword){case"additionalProperties":var m=e.params.additionalProperty,g=e.schemaPath.replace("#","").replace("/additionalProperties",""),h=p({object:c,pnter:g,fallback:{properties:{}}}).properties;t={message:"'"+m+"' property is not expected to be here",suggestion:l({value:m,suggestions:Object.keys(null!=h?h:{}),format:function(e){return"Did you mean property '"+e+"'?"}}),path:n,context:y};break;case"enum":var b=e.params.allowedValues.map((function(e){return e.toString()})),P=i(e.instancePath),w=p({object:r,pnter:e.instancePath,fallback:""});t={message:"'"+P+"' property must be equal to one of the allowed values",suggestion:l({value:w,suggestions:b}),path:n,context:a({},y,{allowedValues:e.params.allowedValues})};break;case"type":t={message:"'"+i(e.instancePath)+"' property type must be "+e.params.type,path:n,context:y};break;case"required":t={message:n+" must have required property '"+e.params.missingProperty+"'",path:n,context:y};break;case"const":return{message:"'"+d+"' property must be equal to the allowed value",path:n,context:a({},y,{allowedValue:e.params.allowedValue})};default:return{message:f,path:n,context:y}}for(var j=0,k=Object.entries(t);j<k.length;j++){var x=k[j],O=x[1];null!=O&&""!==O||delete t[x[0]]}return t})):[]};
|
||||
//# sourceMappingURL=better-ajv-errors.cjs.production.min.js.map
|
||||
1
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.production.min.js.map
generated
vendored
Normal file
1
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.production.min.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
240
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.esm.js
generated
vendored
Normal file
240
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.esm.js
generated
vendored
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
import leven from 'leven';
|
||||
import pointer from 'jsonpointer';
|
||||
|
||||
function _extends() {
|
||||
_extends = Object.assign || function (target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i];
|
||||
|
||||
for (var key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
return _extends.apply(this, arguments);
|
||||
}
|
||||
|
||||
var AJV_ERROR_KEYWORD_WEIGHT_MAP = {
|
||||
"enum": 1,
|
||||
type: 0
|
||||
};
|
||||
var QUOTES_REGEX = /"/g;
|
||||
var NOT_REGEX = /NOT/g;
|
||||
var SLASH_REGEX = /\//g;
|
||||
|
||||
var filterSingleErrorPerProperty = function filterSingleErrorPerProperty(errors) {
|
||||
var errorsPerProperty = errors.reduce(function (acc, error) {
|
||||
var _ref, _error$params$additio, _error$params, _error$params2, _AJV_ERROR_KEYWORD_WE, _AJV_ERROR_KEYWORD_WE2;
|
||||
|
||||
var prop = error.instancePath + ((_ref = (_error$params$additio = (_error$params = error.params) == null ? void 0 : _error$params.additionalProperty) != null ? _error$params$additio : (_error$params2 = error.params) == null ? void 0 : _error$params2.missingProperty) != null ? _ref : '');
|
||||
var existingError = acc[prop];
|
||||
|
||||
if (!existingError) {
|
||||
acc[prop] = error;
|
||||
return acc;
|
||||
}
|
||||
|
||||
var weight = (_AJV_ERROR_KEYWORD_WE = AJV_ERROR_KEYWORD_WEIGHT_MAP[error.keyword]) != null ? _AJV_ERROR_KEYWORD_WE : 0;
|
||||
var existingWeight = (_AJV_ERROR_KEYWORD_WE2 = AJV_ERROR_KEYWORD_WEIGHT_MAP[existingError.keyword]) != null ? _AJV_ERROR_KEYWORD_WE2 : 0;
|
||||
|
||||
if (weight > existingWeight) {
|
||||
acc[prop] = error;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
return Object.values(errorsPerProperty);
|
||||
};
|
||||
|
||||
var getSuggestion = function getSuggestion(_ref) {
|
||||
var value = _ref.value,
|
||||
suggestions = _ref.suggestions,
|
||||
_ref$format = _ref.format,
|
||||
format = _ref$format === void 0 ? function (suggestion) {
|
||||
return "Did you mean '" + suggestion + "'?";
|
||||
} : _ref$format;
|
||||
if (!value) return '';
|
||||
var bestSuggestion = suggestions.reduce(function (best, current) {
|
||||
var distance = leven(value, current);
|
||||
|
||||
if (best.distance > distance) {
|
||||
return {
|
||||
value: current,
|
||||
distance: distance
|
||||
};
|
||||
}
|
||||
|
||||
return best;
|
||||
}, {
|
||||
distance: Infinity,
|
||||
value: ''
|
||||
});
|
||||
return bestSuggestion.distance < value.length ? format(bestSuggestion.value) : '';
|
||||
};
|
||||
|
||||
var pointerToDotNotation = function pointerToDotNotation(pointer) {
|
||||
return pointer.replace(SLASH_REGEX, '.');
|
||||
};
|
||||
var cleanAjvMessage = function cleanAjvMessage(message) {
|
||||
return message.replace(QUOTES_REGEX, "'").replace(NOT_REGEX, 'not');
|
||||
};
|
||||
var getLastSegment = function getLastSegment(path) {
|
||||
var segments = path.split('/');
|
||||
return segments.pop();
|
||||
};
|
||||
var safeJsonPointer = function safeJsonPointer(_ref) {
|
||||
var object = _ref.object,
|
||||
pnter = _ref.pnter,
|
||||
fallback = _ref.fallback;
|
||||
|
||||
try {
|
||||
return pointer.get(object, pnter);
|
||||
} catch (err) {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
var betterAjvErrors = function betterAjvErrors(_ref) {
|
||||
var errors = _ref.errors,
|
||||
data = _ref.data,
|
||||
schema = _ref.schema,
|
||||
_ref$basePath = _ref.basePath,
|
||||
basePath = _ref$basePath === void 0 ? '{base}' : _ref$basePath;
|
||||
|
||||
if (!Array.isArray(errors) || errors.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var definedErrors = filterSingleErrorPerProperty(errors);
|
||||
return definedErrors.map(function (error) {
|
||||
var path = pointerToDotNotation(basePath + error.instancePath);
|
||||
var prop = getLastSegment(error.instancePath);
|
||||
var defaultContext = {
|
||||
errorType: error.keyword
|
||||
};
|
||||
var defaultMessage = (prop ? "property '" + prop + "'" : path) + " " + cleanAjvMessage(error.message);
|
||||
var validationError;
|
||||
|
||||
switch (error.keyword) {
|
||||
case 'additionalProperties':
|
||||
{
|
||||
var additionalProp = error.params.additionalProperty;
|
||||
var suggestionPointer = error.schemaPath.replace('#', '').replace('/additionalProperties', '');
|
||||
|
||||
var _safeJsonPointer = safeJsonPointer({
|
||||
object: schema,
|
||||
pnter: suggestionPointer,
|
||||
fallback: {
|
||||
properties: {}
|
||||
}
|
||||
}),
|
||||
properties = _safeJsonPointer.properties;
|
||||
|
||||
validationError = {
|
||||
message: "'" + additionalProp + "' property is not expected to be here",
|
||||
suggestion: getSuggestion({
|
||||
value: additionalProp,
|
||||
suggestions: Object.keys(properties != null ? properties : {}),
|
||||
format: function format(suggestion) {
|
||||
return "Did you mean property '" + suggestion + "'?";
|
||||
}
|
||||
}),
|
||||
path: path,
|
||||
context: defaultContext
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'enum':
|
||||
{
|
||||
var suggestions = error.params.allowedValues.map(function (value) {
|
||||
return value.toString();
|
||||
});
|
||||
|
||||
var _prop = getLastSegment(error.instancePath);
|
||||
|
||||
var value = safeJsonPointer({
|
||||
object: data,
|
||||
pnter: error.instancePath,
|
||||
fallback: ''
|
||||
});
|
||||
validationError = {
|
||||
message: "'" + _prop + "' property must be equal to one of the allowed values",
|
||||
suggestion: getSuggestion({
|
||||
value: value,
|
||||
suggestions: suggestions
|
||||
}),
|
||||
path: path,
|
||||
context: _extends({}, defaultContext, {
|
||||
allowedValues: error.params.allowedValues
|
||||
})
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'type':
|
||||
{
|
||||
var _prop2 = getLastSegment(error.instancePath);
|
||||
|
||||
var type = error.params.type;
|
||||
validationError = {
|
||||
message: "'" + _prop2 + "' property type must be " + type,
|
||||
path: path,
|
||||
context: defaultContext
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'required':
|
||||
{
|
||||
validationError = {
|
||||
message: path + " must have required property '" + error.params.missingProperty + "'",
|
||||
path: path,
|
||||
context: defaultContext
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'const':
|
||||
{
|
||||
return {
|
||||
message: "'" + prop + "' property must be equal to the allowed value",
|
||||
path: path,
|
||||
context: _extends({}, defaultContext, {
|
||||
allowedValue: error.params.allowedValue
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
message: defaultMessage,
|
||||
path: path,
|
||||
context: defaultContext
|
||||
};
|
||||
} // Remove empty properties
|
||||
|
||||
|
||||
var errorEntries = Object.entries(validationError);
|
||||
|
||||
for (var _i = 0, _errorEntries = errorEntries; _i < _errorEntries.length; _i++) {
|
||||
var _errorEntries$_i = _errorEntries[_i],
|
||||
key = _errorEntries$_i[0],
|
||||
_value = _errorEntries$_i[1];
|
||||
|
||||
if (_value === null || _value === undefined || _value === '') {
|
||||
delete validationError[key];
|
||||
}
|
||||
}
|
||||
|
||||
return validationError;
|
||||
});
|
||||
};
|
||||
|
||||
export { betterAjvErrors };
|
||||
//# sourceMappingURL=better-ajv-errors.esm.js.map
|
||||
1
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.esm.js.map
generated
vendored
Normal file
1
pwa/node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.esm.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
5
pwa/node_modules/@apideck/better-ajv-errors/dist/constants.d.ts
generated
vendored
Normal file
5
pwa/node_modules/@apideck/better-ajv-errors/dist/constants.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { DefinedError } from 'ajv';
|
||||
export declare const AJV_ERROR_KEYWORD_WEIGHT_MAP: Partial<Record<DefinedError['keyword'], number>>;
|
||||
export declare const QUOTES_REGEX: RegExp;
|
||||
export declare const NOT_REGEX: RegExp;
|
||||
export declare const SLASH_REGEX: RegExp;
|
||||
11
pwa/node_modules/@apideck/better-ajv-errors/dist/index.d.ts
generated
vendored
Normal file
11
pwa/node_modules/@apideck/better-ajv-errors/dist/index.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { ErrorObject } from 'ajv';
|
||||
import type { JSONSchema6 } from 'json-schema';
|
||||
import { ValidationError } from './types/ValidationError';
|
||||
export interface BetterAjvErrorsOptions {
|
||||
errors: ErrorObject[] | null | undefined;
|
||||
data: any;
|
||||
schema: JSONSchema6;
|
||||
basePath?: string;
|
||||
}
|
||||
export declare const betterAjvErrors: ({ errors, data, schema, basePath, }: BetterAjvErrorsOptions) => ValidationError[];
|
||||
export { ValidationError };
|
||||
8
pwa/node_modules/@apideck/better-ajv-errors/dist/index.js
generated
vendored
Normal file
8
pwa/node_modules/@apideck/better-ajv-errors/dist/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
'use strict'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./better-ajv-errors.cjs.production.min.js')
|
||||
} else {
|
||||
module.exports = require('./better-ajv-errors.cjs.development.js')
|
||||
}
|
||||
2
pwa/node_modules/@apideck/better-ajv-errors/dist/lib/filter.d.ts
generated
vendored
Normal file
2
pwa/node_modules/@apideck/better-ajv-errors/dist/lib/filter.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import { DefinedError } from 'ajv';
|
||||
export declare const filterSingleErrorPerProperty: (errors: DefinedError[]) => DefinedError[];
|
||||
5
pwa/node_modules/@apideck/better-ajv-errors/dist/lib/suggestions.d.ts
generated
vendored
Normal file
5
pwa/node_modules/@apideck/better-ajv-errors/dist/lib/suggestions.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export declare const getSuggestion: ({ value, suggestions, format, }: {
|
||||
value: string | null;
|
||||
suggestions: string[];
|
||||
format?: ((suggestion: string) => string) | undefined;
|
||||
}) => string;
|
||||
8
pwa/node_modules/@apideck/better-ajv-errors/dist/lib/utils.d.ts
generated
vendored
Normal file
8
pwa/node_modules/@apideck/better-ajv-errors/dist/lib/utils.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export declare const pointerToDotNotation: (pointer: string) => string;
|
||||
export declare const cleanAjvMessage: (message: string) => string;
|
||||
export declare const getLastSegment: (path: string) => string;
|
||||
export declare const safeJsonPointer: <T>({ object, pnter, fallback }: {
|
||||
object: any;
|
||||
pnter: string;
|
||||
fallback: T;
|
||||
}) => T;
|
||||
10
pwa/node_modules/@apideck/better-ajv-errors/dist/types/ValidationError.d.ts
generated
vendored
Normal file
10
pwa/node_modules/@apideck/better-ajv-errors/dist/types/ValidationError.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { DefinedError } from 'ajv';
|
||||
export interface ValidationError {
|
||||
message: string;
|
||||
path: string;
|
||||
suggestion?: string;
|
||||
context: {
|
||||
errorType: DefinedError['keyword'];
|
||||
[additionalContext: string]: unknown;
|
||||
};
|
||||
}
|
||||
88
pwa/node_modules/@apideck/better-ajv-errors/package.json
generated
vendored
Normal file
88
pwa/node_modules/@apideck/better-ajv-errors/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"name": "@apideck/better-ajv-errors",
|
||||
"description": "Human-friendly JSON Schema validation for APIs",
|
||||
"version": "0.3.6",
|
||||
"author": "Apideck <support@apideck.com> (https://apideck.com/)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apideck-libraries/better-ajv-errors"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/apideck-libraries/better-ajv-errors/issues"
|
||||
},
|
||||
"contributors": [
|
||||
"Elias Meire <elias@apideck.com>"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/better-ajv-errors.esm.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "tsdx watch",
|
||||
"build": "tsdx build",
|
||||
"test": "tsdx test",
|
||||
"lint": "tsdx lint",
|
||||
"prepare": "tsdx build",
|
||||
"size": "size-limit",
|
||||
"release": "np --no-publish && npm publish --access public --registry https://registry.npmjs.org",
|
||||
"analyze": "size-limit --why"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"size-limit": [
|
||||
{
|
||||
"path": "dist/better-ajv-errors.cjs.production.min.js",
|
||||
"limit": "2 KB"
|
||||
},
|
||||
{
|
||||
"path": "dist/better-ajv-errors.esm.js",
|
||||
"limit": "2.5 KB"
|
||||
}
|
||||
],
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "^7.0.8",
|
||||
"ajv": "^8.11.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"husky": "^8.0.1",
|
||||
"np": "^7.6.1",
|
||||
"size-limit": "^7.0.8",
|
||||
"tsdx": "^0.14.1",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": ">=8"
|
||||
},
|
||||
"dependencies": {
|
||||
"json-schema": "^0.4.0",
|
||||
"jsonpointer": "^5.0.0",
|
||||
"leven": "^3.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"prettier": "^2.3.0"
|
||||
},
|
||||
"keywords": [
|
||||
"apideck",
|
||||
"ajv",
|
||||
"json",
|
||||
"schema",
|
||||
"json-schema",
|
||||
"errors",
|
||||
"human"
|
||||
]
|
||||
}
|
||||
10
pwa/node_modules/@apideck/better-ajv-errors/src/constants.ts
generated
vendored
Normal file
10
pwa/node_modules/@apideck/better-ajv-errors/src/constants.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { DefinedError } from 'ajv';
|
||||
|
||||
export const AJV_ERROR_KEYWORD_WEIGHT_MAP: Partial<Record<DefinedError['keyword'], number>> = {
|
||||
enum: 1,
|
||||
type: 0,
|
||||
};
|
||||
|
||||
export const QUOTES_REGEX = /"/g;
|
||||
export const NOT_REGEX = /NOT/g;
|
||||
export const SLASH_REGEX = /\//g;
|
||||
434
pwa/node_modules/@apideck/better-ajv-errors/src/index.test.ts
generated
vendored
Normal file
434
pwa/node_modules/@apideck/better-ajv-errors/src/index.test.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
import Ajv from 'ajv';
|
||||
import { JSONSchema6 } from 'json-schema';
|
||||
import { betterAjvErrors } from './index';
|
||||
|
||||
describe('betterAjvErrors', () => {
|
||||
let ajv: Ajv;
|
||||
let schema: JSONSchema6;
|
||||
let data: Record<string, unknown>;
|
||||
|
||||
beforeEach(() => {
|
||||
ajv = new Ajv({ allErrors: true });
|
||||
schema = {
|
||||
type: 'object',
|
||||
required: ['str'],
|
||||
properties: {
|
||||
str: {
|
||||
type: 'string',
|
||||
},
|
||||
enum: {
|
||||
type: 'string',
|
||||
enum: ['one', 'two'],
|
||||
},
|
||||
bounds: {
|
||||
type: 'number',
|
||||
minimum: 2,
|
||||
maximum: 4,
|
||||
},
|
||||
nested: {
|
||||
type: 'object',
|
||||
required: ['deepReq'],
|
||||
properties: {
|
||||
deepReq: {
|
||||
type: 'boolean',
|
||||
},
|
||||
deep: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
};
|
||||
});
|
||||
|
||||
describe('additionalProperties', () => {
|
||||
it('should handle additionalProperties=false', () => {
|
||||
data = {
|
||||
str: 'str',
|
||||
foo: 'bar',
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'additionalProperties',
|
||||
},
|
||||
message: "'foo' property is not expected to be here",
|
||||
path: '{base}',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle additionalProperties=true', () => {
|
||||
data = {
|
||||
str: 'str',
|
||||
foo: 'bar',
|
||||
};
|
||||
schema.additionalProperties = true;
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should give suggestions when relevant', () => {
|
||||
data = {
|
||||
str: 'str',
|
||||
bonds: 'bar',
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'additionalProperties',
|
||||
},
|
||||
message: "'bonds' property is not expected to be here",
|
||||
path: '{base}',
|
||||
suggestion: "Did you mean property 'bounds'?",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle object schemas without properties', () => {
|
||||
data = {
|
||||
empty: { foo: 1 },
|
||||
};
|
||||
schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
empty: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'additionalProperties',
|
||||
},
|
||||
message: "'foo' property is not expected to be here",
|
||||
path: '{base}.empty',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('required', () => {
|
||||
it('should handle required properties', () => {
|
||||
data = {
|
||||
nested: {},
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'required',
|
||||
},
|
||||
message: "{base} must have required property 'str'",
|
||||
path: '{base}',
|
||||
},
|
||||
{
|
||||
context: {
|
||||
errorType: 'required',
|
||||
},
|
||||
message: "{base}.nested must have required property 'deepReq'",
|
||||
path: '{base}.nested',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle multiple required properties', () => {
|
||||
schema = {
|
||||
type: 'object',
|
||||
required: ['req1', 'req2'],
|
||||
properties: {
|
||||
req1: {
|
||||
type: 'string',
|
||||
},
|
||||
req2: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
};
|
||||
data = {};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'required',
|
||||
},
|
||||
message: "{base} must have required property 'req1'",
|
||||
path: '{base}',
|
||||
},
|
||||
{
|
||||
context: {
|
||||
errorType: 'required',
|
||||
},
|
||||
message: "{base} must have required property 'req2'",
|
||||
path: '{base}',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('type', () => {
|
||||
it('should handle type errors', () => {
|
||||
data = {
|
||||
str: 123,
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'type',
|
||||
},
|
||||
message: "'str' property type must be string",
|
||||
path: '{base}.str',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('minimum/maximum', () => {
|
||||
it('should handle minimum/maximum errors', () => {
|
||||
data = {
|
||||
str: 'str',
|
||||
bounds: 123,
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'maximum',
|
||||
},
|
||||
message: "property 'bounds' must be <= 4",
|
||||
path: '{base}.bounds',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enum', () => {
|
||||
it('should handle enum errors', () => {
|
||||
data = {
|
||||
str: 'str',
|
||||
enum: 'zzzz',
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'enum',
|
||||
allowedValues: ['one', 'two'],
|
||||
},
|
||||
message: "'enum' property must be equal to one of the allowed values",
|
||||
path: '{base}.enum',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should provide suggestions when relevant', () => {
|
||||
data = {
|
||||
str: 'str',
|
||||
enum: 'pne',
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'enum',
|
||||
allowedValues: ['one', 'two'],
|
||||
},
|
||||
message: "'enum' property must be equal to one of the allowed values",
|
||||
path: '{base}.enum',
|
||||
suggestion: "Did you mean 'one'?",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not crash on null value', () => {
|
||||
data = {
|
||||
type: null,
|
||||
};
|
||||
schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['primary', 'secondary'],
|
||||
},
|
||||
},
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
allowedValues: ['primary', 'secondary'],
|
||||
errorType: 'enum',
|
||||
},
|
||||
message: "'type' property must be equal to one of the allowed values",
|
||||
path: '{base}.type',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle array paths', () => {
|
||||
data = {
|
||||
custom: [{ foo: 'bar' }, { aaa: 'zzz' }],
|
||||
};
|
||||
schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
custom: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'additionalProperties',
|
||||
},
|
||||
message: "'foo' property is not expected to be here",
|
||||
path: '{base}.custom.0',
|
||||
},
|
||||
{
|
||||
context: {
|
||||
errorType: 'additionalProperties',
|
||||
},
|
||||
message: "'aaa' property is not expected to be here",
|
||||
path: '{base}.custom.1',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle file $refs', () => {
|
||||
data = {
|
||||
child: [{ foo: 'bar' }, { aaa: 'zzz' }],
|
||||
};
|
||||
schema = {
|
||||
$id: 'http://example.com/schemas/Main.json',
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: './Child.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
ajv.addSchema({
|
||||
$id: 'http://example.com/schemas/Child.json',
|
||||
additionalProperties: false,
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
});
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
errorType: 'additionalProperties',
|
||||
},
|
||||
message: "'foo' property is not expected to be here",
|
||||
path: '{base}.child.0',
|
||||
},
|
||||
{
|
||||
context: {
|
||||
errorType: 'additionalProperties',
|
||||
},
|
||||
message: "'aaa' property is not expected to be here",
|
||||
path: '{base}.child.1',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle number enums', () => {
|
||||
data = {
|
||||
isLive: 2,
|
||||
};
|
||||
schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
isLive: {
|
||||
type: 'integer',
|
||||
enum: [0, 1],
|
||||
},
|
||||
},
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
allowedValues: [0, 1],
|
||||
errorType: 'enum',
|
||||
},
|
||||
message: "'isLive' property must be equal to one of the allowed values",
|
||||
path: '{base}.isLive',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('const', () => {
|
||||
it('should handle const errors', () => {
|
||||
data = {
|
||||
const: 2,
|
||||
};
|
||||
schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
const: {
|
||||
type: 'integer',
|
||||
const: 42,
|
||||
},
|
||||
},
|
||||
};
|
||||
ajv.validate(schema, data);
|
||||
const errors = betterAjvErrors({ data, schema, errors: ajv.errors });
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
context: {
|
||||
allowedValue: 42,
|
||||
errorType: 'const',
|
||||
},
|
||||
message: "'const' property must be equal to the allowed value",
|
||||
path: '{base}.const',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
121
pwa/node_modules/@apideck/better-ajv-errors/src/index.ts
generated
vendored
Normal file
121
pwa/node_modules/@apideck/better-ajv-errors/src/index.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import { DefinedError, ErrorObject } from 'ajv';
|
||||
import type { JSONSchema6 } from 'json-schema';
|
||||
import { ValidationError } from './types/ValidationError';
|
||||
import { filterSingleErrorPerProperty } from './lib/filter';
|
||||
import { getSuggestion } from './lib/suggestions';
|
||||
import { cleanAjvMessage, getLastSegment, pointerToDotNotation, safeJsonPointer } from './lib/utils';
|
||||
|
||||
export interface BetterAjvErrorsOptions {
|
||||
errors: ErrorObject[] | null | undefined;
|
||||
data: any;
|
||||
schema: JSONSchema6;
|
||||
basePath?: string;
|
||||
}
|
||||
|
||||
export const betterAjvErrors = ({
|
||||
errors,
|
||||
data,
|
||||
schema,
|
||||
basePath = '{base}',
|
||||
}: BetterAjvErrorsOptions): ValidationError[] => {
|
||||
if (!Array.isArray(errors) || errors.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const definedErrors = filterSingleErrorPerProperty(errors as DefinedError[]);
|
||||
|
||||
return definedErrors.map((error) => {
|
||||
const path = pointerToDotNotation(basePath + error.instancePath);
|
||||
const prop = getLastSegment(error.instancePath);
|
||||
const defaultContext = {
|
||||
errorType: error.keyword,
|
||||
};
|
||||
const defaultMessage = `${prop ? `property '${prop}'` : path} ${cleanAjvMessage(error.message as string)}`;
|
||||
|
||||
let validationError: ValidationError;
|
||||
|
||||
switch (error.keyword) {
|
||||
case 'additionalProperties': {
|
||||
const additionalProp = error.params.additionalProperty;
|
||||
const suggestionPointer = error.schemaPath.replace('#', '').replace('/additionalProperties', '');
|
||||
const { properties } = safeJsonPointer({
|
||||
object: schema,
|
||||
pnter: suggestionPointer,
|
||||
fallback: { properties: {} },
|
||||
});
|
||||
validationError = {
|
||||
message: `'${additionalProp}' property is not expected to be here`,
|
||||
suggestion: getSuggestion({
|
||||
value: additionalProp,
|
||||
suggestions: Object.keys(properties ?? {}),
|
||||
format: (suggestion) => `Did you mean property '${suggestion}'?`,
|
||||
}),
|
||||
path,
|
||||
context: defaultContext,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'enum': {
|
||||
const suggestions = error.params.allowedValues.map((value) => value.toString());
|
||||
const prop = getLastSegment(error.instancePath);
|
||||
const value = safeJsonPointer({ object: data, pnter: error.instancePath, fallback: '' });
|
||||
validationError = {
|
||||
message: `'${prop}' property must be equal to one of the allowed values`,
|
||||
suggestion: getSuggestion({
|
||||
value,
|
||||
suggestions,
|
||||
}),
|
||||
path,
|
||||
context: {
|
||||
...defaultContext,
|
||||
allowedValues: error.params.allowedValues,
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'type': {
|
||||
const prop = getLastSegment(error.instancePath);
|
||||
const type = error.params.type;
|
||||
validationError = {
|
||||
message: `'${prop}' property type must be ${type}`,
|
||||
path,
|
||||
context: defaultContext,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'required': {
|
||||
validationError = {
|
||||
message: `${path} must have required property '${error.params.missingProperty}'`,
|
||||
path,
|
||||
context: defaultContext,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'const': {
|
||||
return {
|
||||
message: `'${prop}' property must be equal to the allowed value`,
|
||||
path,
|
||||
context: {
|
||||
...defaultContext,
|
||||
allowedValue: error.params.allowedValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return { message: defaultMessage, path, context: defaultContext };
|
||||
}
|
||||
|
||||
// Remove empty properties
|
||||
const errorEntries = Object.entries(validationError);
|
||||
for (const [key, value] of errorEntries as [keyof ValidationError, unknown][]) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
delete validationError[key];
|
||||
}
|
||||
}
|
||||
|
||||
return validationError;
|
||||
});
|
||||
};
|
||||
|
||||
export { ValidationError };
|
||||
23
pwa/node_modules/@apideck/better-ajv-errors/src/lib/filter.ts
generated
vendored
Normal file
23
pwa/node_modules/@apideck/better-ajv-errors/src/lib/filter.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { DefinedError } from 'ajv';
|
||||
import { AJV_ERROR_KEYWORD_WEIGHT_MAP } from '../constants';
|
||||
|
||||
export const filterSingleErrorPerProperty = (errors: DefinedError[]): DefinedError[] => {
|
||||
const errorsPerProperty = errors.reduce<Record<string, DefinedError>>((acc, error) => {
|
||||
const prop =
|
||||
error.instancePath + ((error.params as any)?.additionalProperty ?? (error.params as any)?.missingProperty ?? '');
|
||||
const existingError = acc[prop];
|
||||
if (!existingError) {
|
||||
acc[prop] = error;
|
||||
return acc;
|
||||
}
|
||||
const weight = AJV_ERROR_KEYWORD_WEIGHT_MAP[error.keyword] ?? 0;
|
||||
const existingWeight = AJV_ERROR_KEYWORD_WEIGHT_MAP[existingError.keyword] ?? 0;
|
||||
|
||||
if (weight > existingWeight) {
|
||||
acc[prop] = error;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return Object.values(errorsPerProperty);
|
||||
};
|
||||
29
pwa/node_modules/@apideck/better-ajv-errors/src/lib/suggestions.ts
generated
vendored
Normal file
29
pwa/node_modules/@apideck/better-ajv-errors/src/lib/suggestions.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import leven from 'leven';
|
||||
|
||||
export const getSuggestion = ({
|
||||
value,
|
||||
suggestions,
|
||||
format = (suggestion) => `Did you mean '${suggestion}'?`,
|
||||
}: {
|
||||
value: string | null;
|
||||
suggestions: string[];
|
||||
format?: (suggestion: string) => string;
|
||||
}): string => {
|
||||
if (!value) return '';
|
||||
const bestSuggestion = suggestions.reduce(
|
||||
(best, current) => {
|
||||
const distance = leven(value, current);
|
||||
if (best.distance > distance) {
|
||||
return { value: current, distance };
|
||||
}
|
||||
|
||||
return best;
|
||||
},
|
||||
{
|
||||
distance: Infinity,
|
||||
value: '',
|
||||
}
|
||||
);
|
||||
|
||||
return bestSuggestion.distance < value.length ? format(bestSuggestion.value) : '';
|
||||
};
|
||||
23
pwa/node_modules/@apideck/better-ajv-errors/src/lib/utils.ts
generated
vendored
Normal file
23
pwa/node_modules/@apideck/better-ajv-errors/src/lib/utils.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { NOT_REGEX, QUOTES_REGEX, SLASH_REGEX } from '../constants';
|
||||
import pointer from 'jsonpointer';
|
||||
|
||||
export const pointerToDotNotation = (pointer: string): string => {
|
||||
return pointer.replace(SLASH_REGEX, '.');
|
||||
};
|
||||
|
||||
export const cleanAjvMessage = (message: string): string => {
|
||||
return message.replace(QUOTES_REGEX, "'").replace(NOT_REGEX, 'not');
|
||||
};
|
||||
|
||||
export const getLastSegment = (path: string): string => {
|
||||
const segments = path.split('/');
|
||||
return segments.pop() as string;
|
||||
};
|
||||
|
||||
export const safeJsonPointer = <T>({ object, pnter, fallback }: { object: any; pnter: string; fallback: T }): T => {
|
||||
try {
|
||||
return pointer.get(object, pnter);
|
||||
} catch (err) {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
11
pwa/node_modules/@apideck/better-ajv-errors/src/types/ValidationError.ts
generated
vendored
Normal file
11
pwa/node_modules/@apideck/better-ajv-errors/src/types/ValidationError.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { DefinedError } from 'ajv';
|
||||
|
||||
export interface ValidationError {
|
||||
message: string;
|
||||
path: string;
|
||||
suggestion?: string;
|
||||
context: {
|
||||
errorType: DefinedError['keyword'];
|
||||
[additionalContext: string]: unknown;
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue