Skip to content

Commit

Permalink
fix: improve error message when no wrapped value
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Mar 4, 2024
1 parent 48ee661 commit 054bc89
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/AeSdkBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 2 additions & 1 deletion src/AeSdkMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 2 additions & 1 deletion src/chain.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
24 changes: 0 additions & 24 deletions src/utils/other.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,6 @@ export const concatBuffers = isWebpack4Buffer
)
: Buffer.concat;

export function wrapWithProxy<Value extends object | undefined>(
valueCb: () => Value,
): NonNullable<Value> {
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<Value>;
}

export function unwrapProxy<Value extends object>(value: Value): Value {
return (value as { _wrappedValue?: Value })._wrappedValue ?? value;
}

/**
* Object key type guard
* @param key - Maybe object key
Expand Down
24 changes: 24 additions & 0 deletions src/utils/wrap-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ArgumentError } from './errors';

export function wrapWithProxy<Value extends object | undefined>(
valueCb: () => Value,
): NonNullable<Value> {
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<Value>;
}

export function unwrapProxy<Value extends object>(value: Value): Value {
return (value as { _wrappedValue?: Value })._wrappedValue ?? value;
}
56 changes: 56 additions & 0 deletions test/unit/utils.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});

0 comments on commit 054bc89

Please sign in to comment.