Skip to content

Commit 9de3393

Browse files
committed
feat: getQueryParams takes shape parameter
1 parent 9af5d0d commit 9de3393

File tree

6 files changed

+65
-47
lines changed

6 files changed

+65
-47
lines changed

packages/fast-usdc/src/exos/advancer.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { assertAllDefined } from '@agoric/internal';
33
import { ChainAddressShape } from '@agoric/orchestration';
44
import { pickFacet } from '@agoric/vat-data';
55
import { VowShape } from '@agoric/vow';
6-
import { makeError, q } from '@endo/errors';
6+
import { q } from '@endo/errors';
77
import { E } from '@endo/far';
88
import { M } from '@endo/patterns';
9-
import { CctpTxEvidenceShape } from '../typeGuards.js';
9+
import { CctpTxEvidenceShape, QueryParamsShape } from '../typeGuards.js';
1010
import { addressTools } from '../utils/address.js';
1111

1212
const { isGTE } = AmountMath;
@@ -115,13 +115,10 @@ export const prepareAdvancerKit = (
115115
// TODO poolAccount might be a vow we need to unwrap
116116
const { assetManagerFacet, poolAccount } = this.state;
117117
const { recipientAddress } = evidence.aux;
118-
const { EUD } =
119-
addressTools.getQueryParams(recipientAddress).params;
120-
if (!EUD) {
121-
throw makeError(
122-
`recipientAddress does not contain EUD param: ${q(recipientAddress)}`,
123-
);
124-
}
118+
const { EUD } = addressTools.getQueryParams(
119+
recipientAddress,
120+
QueryParamsShape,
121+
);
125122

126123
// this will throw if the bech32 prefix is not found, but is handled by the catch
127124
const destination = chainHub.makeChainAddress(EUD);

packages/fast-usdc/src/exos/settler.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,8 @@ export const prepareSettler = (zone, { statusManager }) => {
5555
return;
5656
}
5757

58-
const { params } = addressTools.getQueryParams(tx.receiver);
59-
// TODO - what's the schema address parameter schema for FUSDC?
60-
if (!params?.EUD) {
58+
const { EUD } = addressTools.getQueryParams(tx.receiver);
59+
if (!EUD) {
6160
// only interested in receivers with EUD parameter
6261
return;
6362
}

packages/fast-usdc/src/typeGuards.js

+5
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ export const PendingTxShape = {
3737
status: M.or(...Object.values(PendingTxStatus)),
3838
};
3939
harden(PendingTxShape);
40+
41+
export const QueryParamsShape = {
42+
EUD: M.string(),
43+
};
44+
harden(QueryParamsShape);
+27-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
import { makeError, q } from '@endo/errors';
2+
import { M, mustMatch } from '@endo/patterns';
3+
4+
/**
5+
* @import {Pattern} from '@endo/patterns';
6+
*/
7+
8+
/**
9+
* Default pattern matcher for `getQueryParams`.
10+
* Does not assert keys exist, but ensures existing keys are strings.
11+
*/
12+
const QueryParamsShape = M.splitRecord(
13+
{},
14+
{},
15+
M.recordOf(M.string(), M.string()),
16+
);
217

318
/**
419
* Very minimal 'URL query string'-like parser that handles:
@@ -22,42 +37,35 @@ export const addressTools = {
2237
*/
2338
hasQueryParams: address => {
2439
try {
25-
const { params } = addressTools.getQueryParams(address);
40+
const params = addressTools.getQueryParams(address);
2641
return Object.keys(params).length > 0;
2742
} catch {
2843
return false;
2944
}
3045
},
3146
/**
3247
* @param {string} address
33-
* @returns {{ address: string, params: Record<string, string>}}
48+
* @param {Pattern} [shape]
49+
* @returns {Record<string, string>}
50+
* @throws {Error} if the address cannot be parsed or params do not match `shape`
3451
*/
35-
getQueryParams: address => {
52+
getQueryParams: (address, shape = QueryParamsShape) => {
3653
const parts = address.split('?');
37-
if (parts.length === 0 || parts.length > 2) {
38-
throw makeError(
39-
`Invalid input. Must be of the form 'address?params': ${q(address)}`,
40-
);
41-
}
42-
const result = {
43-
address: parts[0],
44-
params: {},
45-
};
46-
47-
// no parameters, return early
48-
if (parts.length === 1) {
49-
return result;
54+
if (parts.length !== 2) {
55+
throw makeError(`Unable to parse query params: ${q(address)}`);
5056
}
51-
57+
/** @type {Record<string, string>} */
58+
const result = {};
5259
const paramPairs = parts[1].split('&');
5360
for (const pair of paramPairs) {
5461
const [key, value] = pair.split('=');
5562
if (!key || !value) {
5663
throw makeError(`Invalid parameter format in pair: ${q(pair)}`);
5764
}
58-
result.params[key] = value;
65+
result[key] = value;
5966
}
60-
67+
harden(result);
68+
mustMatch(result, shape);
6169
return result;
6270
},
6371
};

packages/fast-usdc/test/exos/advancer.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ test('updates status to OBSERVED if pre-condition checks fail', async t => {
324324

325325
t.deepEqual(inspectLogs(0), [
326326
'Advancer error:',
327-
'"[Error: recipientAddress does not contain EUD param: \\"agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek\\"]"',
327+
'"[Error: Unable to parse query params: \\"agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek\\"]"',
328328
]);
329329
});
330330

packages/fast-usdc/test/utils/address.test.ts

+24-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
2+
import { M } from '@endo/patterns';
23

34
import { addressTools } from '../../src/utils/address.js';
5+
import { QueryParamsShape } from '../../src/typeGuards.js';
46

57
const FIXTURES = {
68
AGORIC_WITH_DYDX:
@@ -32,31 +34,38 @@ test('hasQueryParams: returns false for invalid parameter formats', t => {
3234

3335
// getQueryParams tests - positive cases
3436
test('getQueryParams: correctly parses address with single EUD parameter', t => {
35-
const result = addressTools.getQueryParams(FIXTURES.AGORIC_WITH_DYDX);
37+
const result = addressTools.getQueryParams(
38+
FIXTURES.AGORIC_WITH_DYDX,
39+
QueryParamsShape,
40+
);
3641
t.deepEqual(result, {
37-
address: 'agoric1bech32addr',
38-
params: {
39-
EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
40-
},
42+
EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
4143
});
4244
});
4345

4446
test('getQueryParams: correctly parses address with multiple parameters', t => {
47+
const pattern = harden({ EUD: M.string(), CID: M.string() });
48+
const result = addressTools.getQueryParams(
49+
FIXTURES.AGORIC_WITH_MULTIPLE,
50+
pattern,
51+
);
52+
t.deepEqual(result, {
53+
EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
54+
CID: 'dydx-mainnet-1',
55+
});
56+
});
57+
58+
test('getQueryParams: returns all parameters when no shape is provided', t => {
4559
const result = addressTools.getQueryParams(FIXTURES.AGORIC_WITH_MULTIPLE);
4660
t.deepEqual(result, {
47-
address: 'agoric1bech32addr',
48-
params: {
49-
EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
50-
CID: 'dydx-mainnet-1',
51-
},
61+
EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
62+
CID: 'dydx-mainnet-1',
5263
});
5364
});
5465

5566
test('getQueryParams: correctly handles address with no parameters', t => {
56-
const result = addressTools.getQueryParams(FIXTURES.AGORIC_NO_PARAMS);
57-
t.deepEqual(result, {
58-
address: 'agoric1bech32addr',
59-
params: {},
67+
t.throws(() => addressTools.getQueryParams(FIXTURES.AGORIC_NO_PARAMS), {
68+
message: 'Unable to parse query params: "agoric1bech32addr"',
6069
});
6170
});
6271

@@ -66,7 +75,7 @@ test('getQueryParams: throws error for multiple question marks', t => {
6675
() => addressTools.getQueryParams(FIXTURES.INVALID_MULTIPLE_QUESTION),
6776
{
6877
message:
69-
'Invalid input. Must be of the form \'address?params\': "agoric1bech32addr?param1=value1?param2=value2"',
78+
'Unable to parse query params: "agoric1bech32addr?param1=value1?param2=value2"',
7079
},
7180
);
7281
});

0 commit comments

Comments
 (0)