Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve error message when no wrapped value #1957

Merged
merged 1 commit into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});
});
Loading