Skip to content

Commit 3c2e01a

Browse files
authored
fix search (#306)
* fix search for positive-integer / datetime * 1.27.3
1 parent 74d0fe9 commit 3c2e01a

File tree

4 files changed

+248
-40
lines changed

4 files changed

+248
-40
lines changed

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@0xpolygonid/js-sdk",
3-
"version": "1.27.2",
3+
"version": "1.27.3",
44
"description": "SDK to work with Polygon ID",
55
"main": "dist/node/cjs/index.js",
66
"module": "dist/node/esm/index.js",

src/storage/filters/jsonQuery.ts

+83-37
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@ export enum SearchError {
1010
NotDefinedQueryKey = 'not defined query key',
1111
NotDefinedComparator = 'not defined comparator'
1212
}
13-
13+
/**
14+
* supported data formats
15+
*
16+
* @enum {number}
17+
*/
18+
export enum SupportedDataFormat {
19+
BigInt,
20+
Boolean,
21+
Double,
22+
DateTime,
23+
String
24+
}
1425
/** allowed operators to search */
1526
export type FilterOperatorMethod =
1627
| '$noop'
@@ -84,48 +95,46 @@ const greaterThan = (
8495
a: ComparableType | ComparableType[],
8596
b: ComparableType | ComparableType[]
8697
) => {
87-
if (Array.isArray(a) && Array.isArray(b)) {
88-
return a.every((val, index) => val > (b as ComparableType[])[index]);
89-
}
90-
if (!Array.isArray(a) && Array.isArray(b)) {
91-
return b.every((val) => a > val);
92-
}
93-
if (Array.isArray(a) && !Array.isArray(b)) {
94-
return a.every((val) => val > b);
95-
}
96-
return a > b;
98+
const predicate = (a: ComparableType, b: ComparableType) => {
99+
const dataFormat = detectDataFormat(a.toString());
100+
101+
switch (dataFormat) {
102+
case SupportedDataFormat.BigInt:
103+
case SupportedDataFormat.Boolean:
104+
return BigInt(a) > BigInt(b);
105+
case SupportedDataFormat.DateTime:
106+
return Date.parse(a.toString()) > Date.parse(b.toString()); /// nanoseconds won't be compared.
107+
case SupportedDataFormat.Double:
108+
case SupportedDataFormat.String:
109+
default:
110+
return a > b;
111+
}
112+
};
113+
114+
return operatorIndependentCheck(a, b, predicate);
97115
};
98116

99117
const greaterThanOrEqual = (
100118
a: ComparableType | ComparableType[],
101119
b: ComparableType | ComparableType[]
102120
) => {
103-
if (Array.isArray(a) && Array.isArray(b)) {
104-
return a.every((val, index) => val >= (b as ComparableType[])[index]);
105-
}
106-
if (!Array.isArray(a) && Array.isArray(b)) {
107-
return b.every((val) => a >= val);
108-
}
109-
if (Array.isArray(a) && !Array.isArray(b)) {
110-
return a.every((val) => val >= b);
111-
}
112-
return a >= b;
113-
};
121+
const predicate = (a: ComparableType, b: ComparableType) => {
122+
const dataFormat = detectDataFormat(a.toString());
114123

115-
const lessThanOrEqual = (
116-
a: ComparableType | ComparableType[],
117-
b: ComparableType | ComparableType[]
118-
) => {
119-
if (Array.isArray(a) && Array.isArray(b)) {
120-
return a.every((val, index) => val <= (b as ComparableType[])[index]);
121-
}
122-
if (!Array.isArray(a) && Array.isArray(b)) {
123-
return b.every((val) => a <= val);
124-
}
125-
if (Array.isArray(a) && !Array.isArray(b)) {
126-
return a.every((val) => val <= b);
127-
}
128-
return a <= b;
124+
switch (dataFormat) {
125+
case SupportedDataFormat.BigInt:
126+
case SupportedDataFormat.Boolean:
127+
return BigInt(a) >= BigInt(b);
128+
case SupportedDataFormat.DateTime:
129+
return Date.parse(a.toString()) >= Date.parse(b.toString()); /// nanoseconds won't be compared.
130+
case SupportedDataFormat.Double:
131+
case SupportedDataFormat.String:
132+
default:
133+
return a >= b;
134+
}
135+
};
136+
137+
return operatorIndependentCheck(a, b, predicate);
129138
};
130139

131140
// a - field value
@@ -190,7 +199,7 @@ export const comparatorOptions: { [v in FilterOperatorMethod]: FilterOperatorFun
190199
$gte: (a: ComparableType | ComparableType[], b: ComparableType | ComparableType[]) =>
191200
greaterThanOrEqual(a, b),
192201
$lte: (a: ComparableType | ComparableType[], b: ComparableType | ComparableType[]) =>
193-
lessThanOrEqual(a, b),
202+
!greaterThan(a, b),
194203
$between: (a: ComparableType | ComparableType[], b: ComparableType | ComparableType[]) =>
195204
betweenOperator(a, b),
196205
$nonbetween: (a: ComparableType | ComparableType[], b: ComparableType | ComparableType[]) =>
@@ -320,3 +329,40 @@ export const StandardJSONCredentialsQueryFilter = (query: ProofQuery): FilterQue
320329
}
321330
}, []);
322331
};
332+
333+
const operatorIndependentCheck = (
334+
a: ComparableType | ComparableType[],
335+
b: ComparableType | ComparableType[],
336+
predicate: (a: ComparableType, b: ComparableType) => boolean
337+
) => {
338+
if (Array.isArray(a) && Array.isArray(b)) {
339+
return a.every((val, index) => predicate(val, (b as ComparableType[])[index]));
340+
}
341+
if (!Array.isArray(a) && Array.isArray(b)) {
342+
return b.every((val) => predicate(a, val));
343+
}
344+
if (Array.isArray(a) && !Array.isArray(b)) {
345+
return a.every((val) => predicate(val, b));
346+
}
347+
// in this case a and b are not arrays
348+
return predicate(a as ComparableType, b as ComparableType);
349+
};
350+
351+
const regExBigInt = /^[+-]?\d+$/;
352+
const regExDouble = /^(-?)(0|([1-9][0-9]*))(\\.[0-9]+)?$/;
353+
const regExDateTimeRFC3339Nano =
354+
/* eslint-disable-next-line */
355+
/^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))$/;
356+
const regExBoolean = /^(true)|(false)$/;
357+
const regExDateTimeYYYYMMDD = /^\d{4}-\d{2}-\d{2}$/;
358+
359+
const detectDataFormat = (s: string): SupportedDataFormat =>
360+
regExBigInt.test(s)
361+
? SupportedDataFormat.BigInt
362+
: regExDouble.test(s)
363+
? SupportedDataFormat.Double
364+
: regExDateTimeRFC3339Nano.test(s) || regExDateTimeYYYYMMDD.test(s)
365+
? SupportedDataFormat.DateTime
366+
: regExBoolean.test(s)
367+
? SupportedDataFormat.Boolean
368+
: SupportedDataFormat.String;

tests/credentials/credential-wallet.test.ts

+162
Original file line numberDiff line numberDiff line change
@@ -464,4 +464,166 @@ describe('credential-wallet', () => {
464464
mockedProof.issuerData.state.claimsTreeRoot.bigInt().toString()
465465
);
466466
});
467+
468+
it('Credential search by query', async () => {
469+
const credentialStorage = new CredentialStorage(new InMemoryDataSource<W3CCredential>());
470+
471+
const credToTest = W3CCredential.fromJSON(
472+
JSON.parse(`{
473+
"id": "urn:uuid:9c421fdc-16cc-11f0-9a77-0a58a9feac02",
474+
"@context": [
475+
"https://www.w3.org/2018/credentials/v1",
476+
"https://schema.iden3.io/core/jsonld/iden3proofs.jsonld",
477+
"ipfs://Qmb48rJ5SiQMLXjVkaLQB6fWbT7C8LK75MHsCoHv8GAc15"
478+
],
479+
"type": [
480+
"VerifiableCredential",
481+
"operators"
482+
],
483+
"credentialSubject": {
484+
"boolean1": true,
485+
"date-time1": "2025-04-09T15:48:08.800+02:00",
486+
"id": "did:iden3:privado:main:2SeEkMwamyzKWr3aGUT9AvDjiQK4rMEAZbUZgEzP72",
487+
"integer1": 5342,
488+
"non-negative-integer1": "32423",
489+
"number1": 1234,
490+
"positive-integer1": "12345555",
491+
"string1": "testData",
492+
"type": "operators"
493+
},
494+
"issuer": "did:iden3:polygon:amoy:xJNwv94NrHy1xXU3poKTRL24WJEXmkEggwbBWy6gu",
495+
"credentialSchema": {
496+
"id": "https://ipfs.io/ipfs/QmWDmZQrtvidcNK7d6rJwq7ZSi8SUygJaKepN7NhKtGryc",
497+
"type": "JsonSchema2023"
498+
},
499+
"credentialStatus": {
500+
"id": "https://issuer-node-core-api-testing.privado.id/v2/agent",
501+
"revocationNonce": 978385127,
502+
"type": "Iden3commRevocationStatusV1.0"
503+
},
504+
"issuanceDate": "2025-04-11T12:00:49.921271223Z",
505+
"proof": [
506+
{
507+
"issuerData": {
508+
"id": "did:iden3:polygon:amoy:xJNwv94NrHy1xXU3poKTRL24WJEXmkEggwbBWy6gu",
509+
"state": {
510+
"claimsTreeRoot": "1483789a545832ba69d54ed1691ce758f9b8297542b2749f6fa4786e74fbe80e",
511+
"value": "310ccc3f33fbbfd50da56755e442896065b388e9c4adadd644588eaa6e171b1a",
512+
"rootOfRoots": "0000000000000000000000000000000000000000000000000000000000000000",
513+
"revocationTreeRoot": "0000000000000000000000000000000000000000000000000000000000000000"
514+
},
515+
"mtp": {
516+
"existence": true,
517+
"siblings": []
518+
},
519+
"authCoreClaim": "cca3371a6cb1b715004407e325bd993c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000079adcc5f988e5e6fb7d04adcb5e07afdd53babf34acdb75aa51ac8e1ddc0ee20211c823ff2b1ee62e988fba84247930add22fbbc2fcc4f1b4fd3a50e07f9e6170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
520+
"credentialStatus": {
521+
"id": "https://issuer-node-core-api-testing.privado.id/v2/agent",
522+
"revocationNonce": 0,
523+
"type": "Iden3commRevocationStatusV1.0"
524+
}
525+
},
526+
"type": "BJJSignature2021",
527+
"coreClaim": "637056ad190ec7df5fef1345fda35e1f2200000000000000000000000000000001a16e78ba913717bc15cbe326e236c7eef4f9201582b50e145cb669139c0d0068fbed6c1ddf63a9a4eaa0d4a577b770a5acaf37a542cf86cb6e9e93e9b608020000000000000000000000000000000000000000000000000000000000000000e7f8503a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
528+
"signature": "b17ca01ab223b6281e0b22b4f8945729ffb39eaf4b2a0db59548db7ab0df092e423dfc2472cce2e269a80a4d37bb520eda59ab3a0adfae043caa38a464869505"
529+
}
530+
]
531+
}`)
532+
);
533+
await credentialStorage.saveCredential(credToTest);
534+
535+
// positive-integer
536+
537+
let cred = await credentialStorage.findCredentialsByQuery({
538+
allowedIssuers: ['*'],
539+
context: 'ipfs://Qmb48rJ5SiQMLXjVkaLQB6fWbT7C8LK75MHsCoHv8GAc15',
540+
credentialSubject: {
541+
'positive-integer1': {
542+
$gt: '2'
543+
}
544+
},
545+
groupId: 1745320529,
546+
type: 'operators'
547+
});
548+
expect(1).to.be.equal(cred.length);
549+
expect(cred[0].credentialSubject['positive-integer1']).to.be.equal('12345555');
550+
551+
//number
552+
553+
cred = await credentialStorage.findCredentialsByQuery({
554+
allowedIssuers: ['*'],
555+
context: 'ipfs://Qmb48rJ5SiQMLXjVkaLQB6fWbT7C8LK75MHsCoHv8GAc15',
556+
credentialSubject: {
557+
number1: {
558+
$gt: 1
559+
}
560+
},
561+
groupId: 1745320529,
562+
type: 'operators'
563+
});
564+
expect(1).to.be.equal(cred.length);
565+
expect(cred[0].credentialSubject['number1']).to.be.equal(1234);
566+
567+
//boolean
568+
569+
cred = await credentialStorage.findCredentialsByQuery({
570+
allowedIssuers: ['*'],
571+
context: 'ipfs://Qmb48rJ5SiQMLXjVkaLQB6fWbT7C8LK75MHsCoHv8GAc15',
572+
credentialSubject: {
573+
boolean1: {
574+
$eq: true
575+
}
576+
},
577+
groupId: 1745320529,
578+
type: 'operators'
579+
});
580+
expect(1).to.be.equal(cred.length);
581+
expect(cred[0].credentialSubject['boolean1']).to.be.equal(true);
582+
583+
//datetime lt
584+
585+
cred = await credentialStorage.findCredentialsByQuery({
586+
allowedIssuers: ['*'],
587+
context: 'ipfs://Qmb48rJ5SiQMLXjVkaLQB6fWbT7C8LK75MHsCoHv8GAc15',
588+
credentialSubject: {
589+
'date-time1': {
590+
$lt: '2027-04-09T15:48:08.800+02:00'
591+
}
592+
},
593+
groupId: 1745320529,
594+
type: 'operators'
595+
});
596+
expect(1).to.be.equal(cred.length);
597+
expect(cred[0].credentialSubject['date-time1']).to.be.equal('2025-04-09T15:48:08.800+02:00');
598+
599+
// datetime gt
600+
cred = await credentialStorage.findCredentialsByQuery({
601+
allowedIssuers: ['*'],
602+
context: 'ipfs://Qmb48rJ5SiQMLXjVkaLQB6fWbT7C8LK75MHsCoHv8GAc15',
603+
credentialSubject: {
604+
'date-time1': {
605+
$gt: '2022-04-09T15:48:08.800+02:00'
606+
}
607+
},
608+
groupId: 1745320529,
609+
type: 'operators'
610+
});
611+
expect(1).to.be.equal(cred.length);
612+
expect(cred[0].credentialSubject['date-time1']).to.be.equal('2025-04-09T15:48:08.800+02:00');
613+
614+
// datetime YYYY-MM-DD
615+
cred = await credentialStorage.findCredentialsByQuery({
616+
allowedIssuers: ['*'],
617+
context: 'ipfs://Qmb48rJ5SiQMLXjVkaLQB6fWbT7C8LK75MHsCoHv8GAc15',
618+
credentialSubject: {
619+
'date-time1': {
620+
$gt: '2022-04-01'
621+
}
622+
},
623+
groupId: 1745320529,
624+
type: 'operators'
625+
});
626+
expect(1).to.be.equal(cred.length);
627+
expect(cred[0].credentialSubject['date-time1']).to.be.equal('2025-04-09T15:48:08.800+02:00');
628+
});
467629
});

0 commit comments

Comments
 (0)