diff --git a/packages/askar/package.json b/packages/askar/package.json index 655b9196ea..24313172ec 100644 --- a/packages/askar/package.json +++ b/packages/askar/package.json @@ -26,14 +26,14 @@ }, "dependencies": { "@aries-framework/core": "0.3.2", - "aries-askar-test-shared": "^0.0.1", + "aries-askar-test-shared": "^0.1.0-dev.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "rxjs": "^7.2.0", "tsyringe": "^4.7.0" }, "devDependencies": { - "aries-askar-test-nodejs": "^0.0.1", + "aries-askar-test-nodejs": "^0.1.0-dev.2", "@aries-framework/node": "0.3.2", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/askar/src/AskarModule.ts b/packages/askar/src/AskarModule.ts index 578871412a..e3c59c1f29 100644 --- a/packages/askar/src/AskarModule.ts +++ b/packages/askar/src/AskarModule.ts @@ -1,7 +1,7 @@ import type { AskarModuleConfigOptions } from './AskarModuleConfig' import type { DependencyManager, Module } from '@aries-framework/core' -import { registerAriesAskar } from 'aries-askar-shared' +import { registerAriesAskar } from 'aries-askar-test-shared' import { AskarModuleConfig } from './AskarModuleConfig' import { AskarSymbol } from './types' diff --git a/packages/askar/src/storage/AskarStorageService.ts b/packages/askar/src/storage/AskarStorageService.ts index 18fb71e36e..993c1b7290 100644 --- a/packages/askar/src/storage/AskarStorageService.ts +++ b/packages/askar/src/storage/AskarStorageService.ts @@ -9,9 +9,16 @@ import type { } from '@aries-framework/core' import type { EntryObject } from 'aries-askar-test-shared' -import { WalletError, RecordNotFoundError, injectable, JsonTransformer } from '@aries-framework/core' +import { + RecordDuplicateError, + WalletError, + RecordNotFoundError, + injectable, + JsonTransformer, +} from '@aries-framework/core' import { Scan } from 'aries-askar-test-shared' +import { askarErrors, isAskarError } from '../utils/askarError' import { assertAskarWallet } from '../utils/assertAskarWallet' @injectable() @@ -95,13 +102,13 @@ export class AskarStorageService implements StorageService * method. */ // TODO: Transform to Askar format - private indyQueryFromSearchQuery(query: Query): Record { + private askarQueryFromSearchQuery(query: Query): Record { // eslint-disable-next-line prefer-const let { $and, $or, $not, ...tags } = query - $and = ($and as Query[] | undefined)?.map((q) => this.indyQueryFromSearchQuery(q)) - $or = ($or as Query[] | undefined)?.map((q) => this.indyQueryFromSearchQuery(q)) - $not = $not ? this.indyQueryFromSearchQuery($not as Query) : undefined + $and = ($and as Query[] | undefined)?.map((q) => this.askarQueryFromSearchQuery(q)) + $or = ($or as Query[] | undefined)?.map((q) => this.askarQueryFromSearchQuery(q)) + $not = $not ? this.askarQueryFromSearchQuery($not as Query) : undefined const indyQuery = { ...this.transformFromRecordTagValues(tags as unknown as TagsBase), @@ -134,6 +141,10 @@ export class AskarStorageService implements StorageService try { await session.insert({ category: record.type, name: record.id, value, tags }) } catch (error) { + if (isAskarError(error) && error.code === askarErrors.Duplicate) { + throw new RecordDuplicateError(`Record with id ${record.id} already exists`, { recordType: record.type }) + } + throw new WalletError('Error saving record', { cause: error }) } } @@ -149,6 +160,13 @@ export class AskarStorageService implements StorageService try { await session.replace({ category: record.type, name: record.id, value, tags }) } catch (error) { + if (isAskarError(error) && error.code === askarErrors.NotFound) { + throw new RecordNotFoundError(`record with id ${record.id} not found.`, { + recordType: record.type, + cause: error, + }) + } + throw new WalletError('Error updating record', { cause: error }) } } @@ -161,6 +179,12 @@ export class AskarStorageService implements StorageService try { await session.remove({ category: record.type, name: record.id }) } catch (error) { + if (isAskarError(error) && error.code === askarErrors.NotFound) { + throw new RecordNotFoundError(`record with id ${record.id} not found.`, { + recordType: record.type, + cause: error, + }) + } throw new WalletError('Error deleting record', { cause: error }) } } @@ -177,6 +201,12 @@ export class AskarStorageService implements StorageService try { await session.remove({ category: recordClass.type, name: id }) } catch (error) { + if (isAskarError(error) && error.code === askarErrors.NotFound) { + throw new RecordNotFoundError(`record with id ${id} not found.`, { + recordType: recordClass.type, + cause: error, + }) + } throw new WalletError('Error deleting record', { cause: error }) } } @@ -186,15 +216,28 @@ export class AskarStorageService implements StorageService assertAskarWallet(agentContext.wallet) const session = (agentContext.wallet as AskarWallet).session - const record = await session.fetch({ category: recordClass.type, name: id }) - - if (!record) { - throw new RecordNotFoundError(`record with id ${id} not found.`, { - recordType: recordClass.type, - }) + try { + const record = await session.fetch({ category: recordClass.type, name: id }) + if (!record) { + throw new RecordNotFoundError(`record with id ${id} not found.`, { + recordType: recordClass.type, + }) + } + return this.recordToInstance(record, recordClass) + } catch (error) { + if ( + isAskarError(error) && + (error.code === askarErrors.NotFound || + // FIXME: this is current output from askar wrapper but does not describe specifically a not found scenario + error.message === 'Received null pointer. The native library could not find the value.') + ) { + throw new RecordNotFoundError(`record with id ${id} not found.`, { + recordType: recordClass.type, + cause: error, + }) + } + throw new WalletError(`Error getting record`, { cause: error }) } - - return this.recordToInstance(record, recordClass) } /** @inheritDoc */ @@ -220,12 +263,12 @@ export class AskarStorageService implements StorageService assertAskarWallet(agentContext.wallet) const store = agentContext.wallet.handle - const indyQuery = this.indyQueryFromSearchQuery(query) + const askarQuery = this.askarQueryFromSearchQuery(query) const scan = new Scan({ category: recordClass.type, store, - tagFilter: indyQuery, + tagFilter: askarQuery, }) const records = await scan.fetchAll() diff --git a/packages/askar/src/storage/__tests__/AskarStorageService.test.ts b/packages/askar/src/storage/__tests__/AskarStorageService.test.ts index db6b6c5fe3..dfbc55205c 100644 --- a/packages/askar/src/storage/__tests__/AskarStorageService.test.ts +++ b/packages/askar/src/storage/__tests__/AskarStorageService.test.ts @@ -3,6 +3,7 @@ import type { AgentContext, TagsBase } from '@aries-framework/core' import { SigningProviderRegistry, RecordDuplicateError, RecordNotFoundError } from '@aries-framework/core' import { NodeJSAriesAskar } from 'aries-askar-test-nodejs' import { registerAriesAskar } from 'aries-askar-test-shared' +import { TextEncoder } from 'util' import { TestRecord } from '../../../../core/src/storage/__tests__/TestRecord' import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' @@ -13,10 +14,11 @@ describe('AskarStorageService', () => { let wallet: AskarWallet let storageService: AskarStorageService let agentContext: AgentContext + const askar = new NodeJSAriesAskar() beforeEach(async () => { const agentConfig = getAgentConfig('AskarStorageServiceTest') - registerAriesAskar({ askar: new NodeJSAriesAskar() }) + registerAriesAskar({ askar }) wallet = new AskarWallet(agentConfig.logger, new agentDependencies.FileSystem(), new SigningProviderRegistry([])) agentContext = getAgentContext({ @@ -43,7 +45,7 @@ describe('AskarStorageService', () => { return record } - describe.skip('tag transformation', () => { + describe('tag transformation', () => { it('should correctly transform tag values to string before storing', async () => { const record = await insertRecord({ id: 'test-id', @@ -58,12 +60,15 @@ describe('AskarStorageService', () => { }, }) - const retrieveRecord = await indy.getWalletRecord(wallet.handle, record.type, record.id, { - retrieveType: true, - retrieveTags: true, + const retrieveRecord = await askar.sessionFetch({ + category: record.type, + name: record.id, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + sessionHandle: wallet.session.handle!, + forUpdate: false, }) - expect(retrieveRecord.tags).toEqual({ + expect(JSON.parse(retrieveRecord.getTags(0))).toEqual({ someBoolean: '1', someOtherBoolean: '0', someStringValue: 'string', @@ -75,15 +80,23 @@ describe('AskarStorageService', () => { }) it('should correctly transform tag values from string after retrieving', async () => { - await indy.addWalletRecord(wallet.handle, TestRecord.type, 'some-id', '{}', { - someBoolean: '1', - someOtherBoolean: '0', - someStringValue: 'string', - 'anArrayValue:foo': '1', - 'anArrayValue:bar': '1', - // booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0' - someStringNumberValue: 'n__1', - anotherStringNumberValue: 'n__0', + await askar.sessionUpdate({ + category: TestRecord.type, + name: 'some-id', + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + sessionHandle: wallet.session.handle!, + value: new Uint8Array(new TextEncoder().encode('{}')), + tags: { + someBoolean: '1', + someOtherBoolean: '0', + someStringValue: 'string', + 'anArrayValue:foo': '1', + 'anArrayValue:bar': '1', + // booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0' + someStringNumberValue: 'n__1', + anotherStringNumberValue: 'n__0', + }, + operation: 0, // EntryOperation.Insert }) const record = await storageService.getById(agentContext, TestRecord, 'some-id') @@ -187,15 +200,16 @@ describe('AskarStorageService', () => { }) }) - describe.skip('findByQuery()', () => { + describe('findByQuery()', () => { it('should retrieve all records that match the query', async () => { const expectedRecord = await insertRecord({ tags: { myTag: 'foobar' } }) + const expectedRecord2 = await insertRecord({ tags: { myTag: 'foobar' } }) await insertRecord({ tags: { myTag: 'notfoobar' } }) const records = await storageService.findByQuery(agentContext, TestRecord, { myTag: 'foobar' }) - expect(records.length).toBe(1) - expect(records[0]).toEqual(expectedRecord) + expect(records.length).toBe(2) + expect(records).toEqual(expect.arrayContaining([expectedRecord, expectedRecord2])) }) it('finds records using $and statements', async () => { diff --git a/packages/askar/src/types.ts b/packages/askar/src/types.ts index f7ff949df9..d3e230adbf 100644 --- a/packages/askar/src/types.ts +++ b/packages/askar/src/types.ts @@ -1,4 +1,4 @@ -import type { AriesAskar } from 'aries-askar-shared' +import type { AriesAskar } from 'aries-askar-test-shared' export const AskarSymbol = Symbol('Askar') export type { AriesAskar } diff --git a/packages/askar/src/utils/askarError.ts b/packages/askar/src/utils/askarError.ts new file mode 100644 index 0000000000..7d946830f2 --- /dev/null +++ b/packages/askar/src/utils/askarError.ts @@ -0,0 +1,16 @@ +import { AriesAskarError } from 'aries-askar-test-shared' + +export enum askarErrors { + Success = 0, + Backend = 1, + Busy = 2, + Duplicate = 3, + Encryption = 4, + Input = 5, + NotFound = 6, + Unexpected = 7, + Unsupported = 8, + Custom = 100, +} + +export const isAskarError = (error: Error) => error instanceof AriesAskarError diff --git a/packages/askar/src/wallet/AskarWallet.ts b/packages/askar/src/wallet/AskarWallet.ts index 2fd0c4ef31..9da90a2c43 100644 --- a/packages/askar/src/wallet/AskarWallet.ts +++ b/packages/askar/src/wallet/AskarWallet.ts @@ -25,6 +25,7 @@ import { TypedArrayEncoder, FileSystem, } from '@aries-framework/core' +// eslint-disable-next-line import/order import { CryptoBox, Store, StoreKeyMethod, Key as AskarKey, keyAlgFromString } from 'aries-askar-test-shared' const isError = (error: unknown): error is Error => error instanceof Error diff --git a/yarn.lock b/yarn.lock index af1b6b04f8..33bcd83436 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1791,6 +1791,21 @@ semver "^7.3.5" tar "^6.1.11" +"@mapbox/node-pre-gyp@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@mattrglobal/bbs-signatures@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@mattrglobal/bbs-signatures/-/bbs-signatures-1.0.0.tgz#8ff272c6d201aadab7e08bd84dbfd6e0d48ba12d" @@ -3007,22 +3022,23 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -aries-askar-test-nodejs@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/aries-askar-test-nodejs/-/aries-askar-test-nodejs-0.0.1.tgz#bddb73e2bc2bdb5ed12f839282879e059c768076" - integrity sha512-I0SBjisZiyFxXQtVVmQzYAKaXneFhVm5W/ZK8Hn44FFkI++IYTzT5/iT1g1H+Jsj8LSY+Boex+CL1o4B6qGfGw== +aries-askar-test-nodejs@^0.1.0-dev.2: + version "0.1.0-dev.2" + resolved "https://registry.yarnpkg.com/aries-askar-test-nodejs/-/aries-askar-test-nodejs-0.1.0-dev.2.tgz#c7d92de5cd3afa2522c772491ad3be2766e603ee" + integrity sha512-CDYMpNn3rIjZvb+LAGMmEnH9Sx7f2vws/zZWl6TW6yaMJ5QfZgVGgCLDRlpFBuX5nXpt+mDuR91N0ppD/nhSoA== dependencies: - aries-askar-test-shared "*" + "@mapbox/node-pre-gyp" "^1.0.10" + aries-askar-test-shared "0.1.0-dev.2" ffi-napi "^4.0.3" node-cache "^5.1.2" ref-array-di "^1.2.2" ref-napi "^3.0.3" ref-struct-di "^1.1.1" -aries-askar-test-shared@*, aries-askar-test-shared@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/aries-askar-test-shared/-/aries-askar-test-shared-0.0.1.tgz#3f7492139ef902aa1371cc532f887b5eea2dd445" - integrity sha512-EhQW2j94cV7l3fgwDxr9mdS7JTAtOrgF6Ze0vA+i2dypOJrWzQpMdmLQKWyE90vEXoEP0yxjpSTKh6qPniySsA== +aries-askar-test-shared@0.1.0-dev.2, aries-askar-test-shared@^0.1.0-dev.2: + version "0.1.0-dev.2" + resolved "https://registry.yarnpkg.com/aries-askar-test-shared/-/aries-askar-test-shared-0.1.0-dev.2.tgz#052b5ef695609ad5c4dacafcb4e411651f97302e" + integrity sha512-P6lIHupmW42UaqUnjutDEJgbat8w/0nkndrwMYsDVk5Z4uzGSLKVnF0pwO72l8s6SH87V8wWnhNJ1DS2tBBGlA== dependencies: fast-text-encoding "^1.0.3"