From 054bc89b940cacdeae1f804153ef39c3fa1e0026 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Mon, 4 Mar 2024 20:47:58 +0700 Subject: [PATCH] fix: improve error message when no wrapped value --- src/AeSdkBase.ts | 2 +- src/AeSdkMethods.ts | 3 ++- src/chain.ts | 3 ++- src/utils/other.ts | 24 ------------------ src/utils/wrap-proxy.ts | 24 ++++++++++++++++++ test/unit/utils.ts | 56 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 src/utils/wrap-proxy.ts create mode 100644 test/unit/utils.ts diff --git a/src/AeSdkBase.ts b/src/AeSdkBase.ts index 869c767e1e..17daa34a5d 100644 --- a/src/AeSdkBase.ts +++ b/src/AeSdkBase.ts @@ -4,7 +4,7 @@ import { CompilerError, DuplicateNodeError, NodeNotFoundError, NotImplementedError, TypeError, } from './utils/errors'; import { Encoded } from './utils/encoder'; -import { wrapWithProxy } from './utils/other'; +import { wrapWithProxy } from './utils/wrap-proxy'; import CompilerBase from './contract/compiler/Base'; import AeSdkMethods, { OnAccount, AeSdkMethodsOptions, WrappedOptions } from './AeSdkMethods'; import { AensName } from './tx/builder/constants'; diff --git a/src/AeSdkMethods.ts b/src/AeSdkMethods.ts index 80983fbc41..7c583c49a5 100644 --- a/src/AeSdkMethods.ts +++ b/src/AeSdkMethods.ts @@ -7,7 +7,8 @@ import Contract, { ContractMethodsBase } from './contract/Contract'; import createDelegationSignature from './contract/delegation-signature'; import * as contractGaMethods from './contract/ga'; import { buildTxAsync } from './tx/builder'; -import { mapObject, UnionToIntersection, wrapWithProxy } from './utils/other'; +import { mapObject, UnionToIntersection } from './utils/other'; +import { wrapWithProxy } from './utils/wrap-proxy'; import Node from './Node'; import { TxParamsAsync } from './tx/builder/schema.generated'; import AccountBase from './account/Base'; diff --git a/src/chain.ts b/src/chain.ts index 159783a191..376705cb3e 100644 --- a/src/chain.ts +++ b/src/chain.ts @@ -1,5 +1,6 @@ import { AE_AMOUNT_FORMATS, formatAmount } from './utils/amount-formatter'; -import { isAccountNotFoundError, pause, unwrapProxy } from './utils/other'; +import { isAccountNotFoundError, pause } from './utils/other'; +import { unwrapProxy } from './utils/wrap-proxy'; import { isNameValid, produceNameId } from './tx/builder/helpers'; import { AensName, DRY_RUN_ACCOUNT } from './tx/builder/constants'; import { diff --git a/src/utils/other.ts b/src/utils/other.ts index df68e6691a..2e137e0249 100644 --- a/src/utils/other.ts +++ b/src/utils/other.ts @@ -27,30 +27,6 @@ export const concatBuffers = isWebpack4Buffer ) : Buffer.concat; -export function wrapWithProxy( - valueCb: () => Value, -): NonNullable { - return new Proxy( - {}, - Object.fromEntries(([ - 'apply', 'construct', 'defineProperty', 'deleteProperty', 'getOwnPropertyDescriptor', - 'getPrototypeOf', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf', - 'get', 'has', - ] as const).map((name) => [name, (t: {}, ...args: unknown[]) => { - if (name === 'get' && args[0] === '_wrappedValue') return valueCb(); - const target = valueCb() as object; // to get a native exception in case it missed - const res = (Reflect[name] as any)(target, ...args); - return typeof res === 'function' && name === 'get' - ? res.bind(target) // otherwise it fails with attempted to get private field on non-instance - : res; - }])), - ) as NonNullable; -} - -export function unwrapProxy(value: Value): Value { - return (value as { _wrappedValue?: Value })._wrappedValue ?? value; -} - /** * Object key type guard * @param key - Maybe object key diff --git a/src/utils/wrap-proxy.ts b/src/utils/wrap-proxy.ts new file mode 100644 index 0000000000..0b3433556d --- /dev/null +++ b/src/utils/wrap-proxy.ts @@ -0,0 +1,24 @@ +import { ArgumentError } from './errors'; + +export function wrapWithProxy( + valueCb: () => Value, +): NonNullable { + return new Proxy( + {}, + Object.fromEntries(([ + 'apply', 'construct', 'defineProperty', 'deleteProperty', 'getOwnPropertyDescriptor', + 'getPrototypeOf', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf', + 'get', 'has', + ] as const).map((name) => [name, (t: {}, ...args: unknown[]) => { + const target = valueCb(); + if (target == null) throw new ArgumentError('wrapped value', 'defined', target); + if (name === 'get' && args[0] === '_wrappedValue') return target; + const res = (Reflect[name] as any)(target, ...args); + return typeof res === 'function' && name === 'get' ? res.bind(target) : res; + }])), + ) as NonNullable; +} + +export function unwrapProxy(value: Value): Value { + return (value as { _wrappedValue?: Value })._wrappedValue ?? value; +} diff --git a/test/unit/utils.ts b/test/unit/utils.ts new file mode 100644 index 0000000000..3fd873a384 --- /dev/null +++ b/test/unit/utils.ts @@ -0,0 +1,56 @@ +import { describe, it } from 'mocha'; +import { expect } from 'chai'; +import { wrapWithProxy, unwrapProxy } from '../../src/utils/wrap-proxy'; +import { ArgumentError } from '../../src'; + +describe('Utils', () => { + describe('wrapWithProxy', () => { + it('wraps value', () => { + let t = { test: 'foo' }; + const wrapped = wrapWithProxy(() => t); + expect(wrapped).to.not.be.equal(t); + expect(wrapped.test).to.be.equal('foo'); + t.test = 'bar'; + expect(wrapped.test).to.be.equal('bar'); + t = { test: 'baz' }; + expect(wrapped.test).to.be.equal('baz'); + }); + + it('throws error if value undefined', () => { + const wrapped = wrapWithProxy<{ test: string } | undefined>(() => undefined); + expect(() => wrapped.test) + .to.throw(ArgumentError, 'wrapped value should be defined, got undefined instead'); + }); + + it('can call private method', () => { + class Entity { + readonly t = 5; + + #bar(): number { + return this.t; + } + + foo(): number { + return this.#bar(); + } + } + + const entity = new Entity(); + const wrapped = wrapWithProxy(() => entity); + expect(wrapped.foo()).to.be.equal(5); + }); + }); + + describe('unwrapProxy', () => { + const t = { test: 'foo' }; + + it('unwraps proxy to value', () => { + const wrapped = wrapWithProxy(() => t); + expect(unwrapProxy(wrapped)).to.be.equal(t); + }); + + it('does nothing if not wrapped', () => { + expect(unwrapProxy(t)).to.be.equal(t); + }); + }); +});