Fix/empty properties (#1764)

* refactor properties utilities with updated ts-matches and allow for a null file

* update pacakges
This commit is contained in:
Lucy C
2022-08-29 11:58:49 -04:00
committed by GitHub
parent b2d7f4f606
commit 8cd2fac9b9
3 changed files with 2641 additions and 2697 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,7 @@
"pbkdf2": "^3.1.2",
"rxjs": "^7.5.6",
"swiper": "^8.2.4",
"ts-matches": "^5.1.7",
"ts-matches": "^5.2.0",
"tslib": "^2.3.0",
"uuid": "^8.3.2",
"zone.js": "^0.11.5"

View File

@@ -1,51 +1,59 @@
import { applyOperation } from 'fast-json-patch'
import {
import matches, {
Parser,
shape,
string,
literal,
boolean,
object,
deferred,
dictionary,
anyOf,
number,
arrayOf,
} from 'ts-matches'
export type ValidVersion = 1 | 2
type ValidVersion = 1 | 2
function has<Obj extends {}, K extends string>(
obj: Obj,
key: K,
): obj is Obj & { [P in K]: unknown } {
return key in obj
type PropertiesV1 = typeof matchPropertiesV1._TYPE
type PackagePropertiesV1 = PropertiesV1[]
type PackagePropertiesV2 = {
[name: string]: PackagePropertyString | PackagePropertyObject
}
type PackagePropertiesVersionedData<T extends number> = T extends 1
? PackagePropertiesV1
: T extends 2
? PackagePropertiesV2
: never
type PackagePropertyString = typeof matchPackagePropertyString._TYPE
export type PackagePropertiesVersioned<T extends number> = {
version: T
data: PackagePropertiesVersionedData<T>
}
export type PackageProperties = PackagePropertiesV2
const matchPropertiesV1 = shape(
{
name: string,
value: string,
description: string.optional(),
copyable: boolean.optional(),
qr: boolean.optional(),
description: string,
copyable: boolean,
qr: boolean,
},
['description', 'copyable', 'qr'],
{ copyable: false, qr: false } as const,
)
type PropertiesV1 = typeof matchPropertiesV1._TYPE
type PackagePropertiesV2 = {
[name: string]: PackagePropertyString | PackagePropertyObject
}
const [matchPackagePropertiesV2, setPPV2] = deferred<PackagePropertiesV2>()
const matchPackagePropertyString = shape(
{
type: literal('string'),
description: string.optional(),
description: string,
value: string,
copyable: boolean.optional(),
qr: boolean.optional(),
masked: boolean.optional(),
copyable: boolean,
qr: boolean,
masked: boolean,
},
['description', 'copyable', 'qr', 'masked'],
{
@@ -54,16 +62,15 @@ const matchPackagePropertyString = shape(
masked: false,
} as const,
)
type PackagePropertyString = typeof matchPackagePropertyString._TYPE
const matchPackagePropertyObject = shape(
{
type: literal('object'),
value: matchPackagePropertiesV2,
description: string.optional(),
description: string,
},
['description'],
{ description: null as null },
)
const matchPropertyV2 = anyOf(
matchPackagePropertyString,
matchPackagePropertyObject,
@@ -71,58 +78,29 @@ const matchPropertyV2 = anyOf(
type PackagePropertyObject = typeof matchPackagePropertyObject._TYPE
setPPV2(dictionary([string, matchPropertyV2]))
const matchPackagePropertiesVersionedV1 = shape({
version: number,
data: arrayOf(matchPropertiesV1),
})
const matchPackagePropertiesVersionedV2 = shape({
version: number,
data: dictionary([string, matchPropertyV2]),
})
export function parsePropertiesPermissive(
properties: unknown,
errorCallback: (err: Error) => any = console.warn,
): PackageProperties {
if (typeof properties !== 'object' || properties === null) {
errorCallback(new TypeError(`${properties} is not an object`))
return {}
}
// @TODO still need this conditional?
if (
!has(properties, 'version') ||
!has(properties, 'data') ||
typeof properties.version !== 'number' ||
!properties.data
) {
return Object.entries(properties)
.filter(([_, value]) => {
if (typeof value === 'string') {
return true
} else {
errorCallback(new TypeError(`${value} is not a string`))
return false
}
})
.map(([name, value]) => ({
name,
value: {
value: String(value),
copyable: false,
qr: false,
masked: false,
},
}))
.reduce((acc, { name, value }) => {
// TODO: Fix type
acc[name] = value as any
return acc
}, {} as PackageProperties)
}
switch (properties.version) {
case 1:
return parsePropertiesV1Permissive(properties.data, errorCallback)
case 2:
return parsePropertiesV2Permissive(properties.data, errorCallback)
default:
errorCallback(
new Error(
`unknown properties version ${properties.version}, attempting to parse as v2`,
),
)
return parsePropertiesV2Permissive(properties.data, errorCallback)
}
return matches(properties)
.when(matchPackagePropertiesVersionedV1, prop =>
parsePropertiesV1Permissive(prop.data, errorCallback),
)
.when(matchPackagePropertiesVersionedV2, prop => prop.data)
.when(matches.nill, {})
.defaultToLazy(() => {
errorCallback(new TypeError(`value is not valid`))
return {}
})
}
function parsePropertiesV1Permissive(
@@ -164,37 +142,6 @@ function parsePropertiesV1Permissive(
{},
)
}
function parsePropertiesV2Permissive(
properties: unknown,
errorCallback: (err: Error) => any,
): PackageProperties {
if (!object.test(properties)) {
return {}
}
return Object.entries(properties).reduce(
(prev: PackageProperties, [name, value], idx) => {
const result = matchPropertyV2.enumParsed(value)
if ('value' in result) {
prev[name] = result.value
} else {
const error = result.error
const message = Parser.validatorErrorAsString(error)
const dataPath = error.keys.map(removeQuotes).join('/')
errorCallback(new Error(`/data/${idx}: ${message}`))
if (dataPath) {
applyOperation(properties, {
op: 'replace',
path: `/${dataPath}`,
value: undefined,
})
}
}
return prev
},
{},
)
}
const removeRegex = /('|")/
function removeQuotes(x: string) {
@@ -203,17 +150,3 @@ function removeQuotes(x: string) {
}
return x
}
type PackagePropertiesV1 = PropertiesV1[]
export type PackageProperties = PackagePropertiesV2
export type PackagePropertiesVersioned<T extends number> = {
version: T
data: PackagePropertiesVersionedData<T>
}
export type PackagePropertiesVersionedData<T extends number> = T extends 1
? PackagePropertiesV1
: T extends 2
? PackagePropertiesV2
: never