From b1c63aafa7c420a9af9844959a8aee254b08c5d3 Mon Sep 17 00:00:00 2001 From: J M <2364004+Blu-J@users.noreply.github.com> Date: Thu, 13 Jan 2022 13:42:56 -0700 Subject: [PATCH] fix: Add conversion for old type (#1070) --- .gitignore | 1 + .../app/services/api/embassy-api.service.ts | 2 +- .../services/api/embassy-live-api.service.ts | 2 +- .../services/api/embassy-mock-api.service.ts | 2 +- ui/src/app/util/properties.util.ts | 88 +++++++++++++++++-- 5 files changed, 87 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 50ae4390b..f62faf45b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /*.img.gz /ubuntu.img.xz /product_key.txt +.vscode/settings.json diff --git a/ui/src/app/services/api/embassy-api.service.ts b/ui/src/app/services/api/embassy-api.service.ts index b3cfea8ea..101cc2e4a 100644 --- a/ui/src/app/services/api/embassy-api.service.ts +++ b/ui/src/app/services/api/embassy-api.service.ts @@ -138,7 +138,7 @@ export abstract class ApiService implements Source, Http { // package - abstract getPackageProperties (params: RR.GetPackagePropertiesReq): Promise['data']> + abstract getPackageProperties (params: RR.GetPackagePropertiesReq): Promise['data']> abstract getPackageLogs (params: RR.GetPackageLogsReq): Promise diff --git a/ui/src/app/services/api/embassy-live-api.service.ts b/ui/src/app/services/api/embassy-live-api.service.ts index 292cbd77b..3055b3453 100644 --- a/ui/src/app/services/api/embassy-live-api.service.ts +++ b/ui/src/app/services/api/embassy-live-api.service.ts @@ -214,7 +214,7 @@ export class LiveApiService extends ApiService { // package - async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise ['data'] > { + async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise ['data'] > { return this.http.rpcRequest({ method: 'package.properties', params }) .then(parsePropertiesPermissive) } diff --git a/ui/src/app/services/api/embassy-mock-api.service.ts b/ui/src/app/services/api/embassy-mock-api.service.ts index 67fd4d714..92c5b8ff4 100644 --- a/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/ui/src/app/services/api/embassy-mock-api.service.ts @@ -373,7 +373,7 @@ export class MockApiService extends ApiService { // package - async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise['data']> { + async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise['data']> { await pauseFor(2000) return parsePropertiesPermissive(Mock.PackageProperties) } diff --git a/ui/src/app/util/properties.util.ts b/ui/src/app/util/properties.util.ts index f8e2c85ab..ff4e40e3b 100644 --- a/ui/src/app/util/properties.util.ts +++ b/ui/src/app/util/properties.util.ts @@ -1,8 +1,34 @@ import * as Ajv from 'ajv' import { applyOperation } from 'fast-json-patch' + +export type ValidVersion = 1 | 2 + +function has(obj: Obj, key: K): obj is (Obj & { [P in K]: unknown }) { + return key in obj +} + + const ajv = new Ajv({ jsonPointers: true, allErrors: true, nullable: true }) const ajvWithDefaults = new Ajv({ jsonPointers: true, allErrors: true, useDefaults: true, nullable: true, removeAdditional: 'failing' }) + +const schemaV1 = { + 'type': 'object', + 'properties': { + 'name': { 'type': 'string' }, + 'value': { 'type': 'string' }, + 'description': { 'type': 'string', 'nullable': true, 'default': null }, + 'copyable': { 'type': 'boolean', 'default': false }, + 'qr': { 'type': 'boolean', 'default': false }, + }, + 'required': ['name', 'value', 'copyable', 'qr'], + 'additionalProperties': false, +} +const schemaV1Compiled = ajv.compile(schemaV1) +function isSchemaV1(properties: unknown): properties is PropertiesV1 { + return schemaV1Compiled(properties) as any +} +const schemaV1CompiledWithDefaults = ajvWithDefaults.compile(schemaV1) const schemaV2 = { 'anyOf': [ { @@ -41,13 +67,13 @@ const schemaV2 = { const schemaV2Compiled = ajv.compile(schemaV2) const schemaV2CompiledWithDefaults = ajvWithDefaults.compile(schemaV2) -export function parsePropertiesPermissive (properties: any, errorCallback: (err: Error) => any = console.warn): PackageProperties { +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 { } + return {} } // @TODO still need this conditional? - if (typeof properties.version !== 'number' || !properties.data) { + if (!has(properties, 'version') || !has(properties, 'data') || typeof properties.version !== 'number' || !properties.data) { return Object.entries(properties) .filter(([_, value]) => { if (typeof value === 'string') { @@ -71,8 +97,11 @@ export function parsePropertiesPermissive (properties: any, errorCallback: (err: acc[name] = value return acc }, { }) + } switch (properties.version) { + case 1: + return parsePropertiesV1Permissive(properties.data, errorCallback) case 2: return parsePropertiesV2Permissive(properties.data, errorCallback) default: @@ -81,7 +110,46 @@ export function parsePropertiesPermissive (properties: any, errorCallback: (err: } } -function parsePropertiesV2Permissive (properties: PackagePropertiesV2, errorCallback: (err: Error) => any): PackageProperties { + + +function parsePropertiesV1Permissive (properties: unknown, errorCallback: (err: Error) => any): PackageProperties { + if (!Array.isArray(properties)) { + errorCallback(new TypeError(`${properties} is not an array`)) + return {} + } + return properties.reduce((prev: PackagePropertiesV2, cur: unknown, idx: number) => { + if(isSchemaV1(cur)) { + prev[cur.name] = { + type: 'string', + value: cur.value, + description: cur.description, + copyable: cur.copyable, + qr: cur.qr, + masked: false, + } + } + else if (schemaV1Compiled.errors) { + for (let err of schemaV1Compiled.errors) { + errorCallback(new Error(`/data/${idx}${err.dataPath}: ${err.message}`)) + if (err.dataPath) { + applyOperation(cur, { op: 'replace', path: err.dataPath, value: undefined }) + } + } + if (!schemaV1CompiledWithDefaults(cur)) { + for (let err of schemaV1CompiledWithDefaults.errors) { + errorCallback(new Error(`/data/${idx}${err.dataPath}: ${err.message}`)) + } + return prev + } + } + return prev + }, { }) +} +function parsePropertiesV2Permissive (properties: unknown, errorCallback: (err: Error) => any): PackageProperties { + if (typeof properties !== 'object' || properties === null) { + errorCallback(new TypeError(`${properties} is not an object`)) + return {} + } return Object.entries(properties).reduce((prev, [name, value], idx) => { schemaV2Compiled(value) if (schemaV2Compiled.errors) { @@ -102,8 +170,16 @@ function parsePropertiesV2Permissive (properties: PackagePropertiesV2, errorCall return prev }, { }) } +interface PropertiesV1 { + name: string + value: string + description: string | null + copyable: boolean + qr: boolean +} -export type PackageProperties = PackagePropertiesV2 // change this type when updating versions +type PackagePropertiesV1 = PropertiesV1[] +export type PackageProperties = PackagePropertiesV2 export type PackagePropertiesVersioned = { version: T, @@ -111,9 +187,11 @@ export type PackagePropertiesVersioned = { } export type PackagePropertiesVersionedData = + T extends 1 ? PackagePropertiesV1 : T extends 2 ? PackagePropertiesV2 : never + interface PackagePropertiesV2 { [name: string]: PackagePropertyString | PackagePropertyObject }