@pagopa/io-react-native-wallet 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +42 -128
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/commonjs/sd-jwt/__test__/types.test.js +40 -0
- package/lib/commonjs/sd-jwt/__test__/types.test.js.map +1 -1
- package/lib/commonjs/sd-jwt/types.js +2 -1
- package/lib/commonjs/sd-jwt/types.js.map +1 -1
- package/lib/commonjs/utils/nestedProperty.js +142 -0
- package/lib/commonjs/utils/nestedProperty.js.map +1 -0
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js +41 -127
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/module/sd-jwt/__test__/types.test.js +41 -1
- package/lib/module/sd-jwt/__test__/types.test.js.map +1 -1
- package/lib/module/sd-jwt/types.js +2 -1
- package/lib/module/sd-jwt/types.js.map +1 -1
- package/lib/module/utils/nestedProperty.js +136 -0
- package/lib/module/utils/nestedProperty.js.map +1 -0
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/types.d.ts +5 -5
- package/lib/typescript/sd-jwt/types.d.ts.map +1 -1
- package/lib/typescript/utils/nestedProperty.d.ts +23 -0
- package/lib/typescript/utils/nestedProperty.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/credential/issuance/07-verify-and-parse-credential.ts +2 -113
- package/src/sd-jwt/__test__/types.test.ts +49 -1
- package/src/sd-jwt/types.ts +2 -1
- package/src/utils/nestedProperty.ts +198 -0
@@ -450,7 +450,7 @@ export declare const Verification: z.ZodObject<{
|
|
450
450
|
assurance_level: z.ZodString;
|
451
451
|
evidence: z.ZodArray<z.ZodObject<{
|
452
452
|
type: z.ZodLiteral<"vouch">;
|
453
|
-
time: z.ZodString
|
453
|
+
time: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
454
454
|
attestation: z.ZodObject<{
|
455
455
|
type: z.ZodLiteral<"digital_attestation">;
|
456
456
|
reference_number: z.ZodString;
|
@@ -479,7 +479,7 @@ export declare const Verification: z.ZodObject<{
|
|
479
479
|
}>;
|
480
480
|
}, "strip", z.ZodTypeAny, {
|
481
481
|
type: "vouch";
|
482
|
-
time: string;
|
482
|
+
time: string | number;
|
483
483
|
attestation: {
|
484
484
|
type: "digital_attestation";
|
485
485
|
reference_number: string;
|
@@ -490,7 +490,7 @@ export declare const Verification: z.ZodObject<{
|
|
490
490
|
};
|
491
491
|
}, {
|
492
492
|
type: "vouch";
|
493
|
-
time: string;
|
493
|
+
time: string | number;
|
494
494
|
attestation: {
|
495
495
|
type: "digital_attestation";
|
496
496
|
reference_number: string;
|
@@ -505,7 +505,7 @@ export declare const Verification: z.ZodObject<{
|
|
505
505
|
assurance_level: string;
|
506
506
|
evidence: {
|
507
507
|
type: "vouch";
|
508
|
-
time: string;
|
508
|
+
time: string | number;
|
509
509
|
attestation: {
|
510
510
|
type: "digital_attestation";
|
511
511
|
reference_number: string;
|
@@ -520,7 +520,7 @@ export declare const Verification: z.ZodObject<{
|
|
520
520
|
assurance_level: string;
|
521
521
|
evidence: {
|
522
522
|
type: "vouch";
|
523
|
-
time: string;
|
523
|
+
time: string | number;
|
524
524
|
attestation: {
|
525
525
|
type: "digital_attestation";
|
526
526
|
reference_number: string;
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/sd-jwt/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,QAAQ,aAAuC,CAAC;AAC7D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC1E,eAAO,MAAM,qBAAqB;;;;;;EAAyC,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AACpD,eAAO,MAAM,UAAU,4DAIrB,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG,OAAO,aAAa,CAAC;AAC9D,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAMF;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkCnB,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/sd-jwt/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,QAAQ,aAAuC,CAAC;AAC7D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC1E,eAAO,MAAM,qBAAqB;;;;;;EAAyC,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AACpD,eAAO,MAAM,UAAU,4DAIrB,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG,OAAO,aAAa,CAAC;AAC9D,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAMF;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkCnB,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBvB,CAAC;AAEH;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAcvB,CAAC"}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
type DisplayData = {
|
2
|
+
locale: string;
|
3
|
+
name: string;
|
4
|
+
}[];
|
5
|
+
type LocalizedNames = Record<string, string>;
|
6
|
+
type PropertyNode<T> = {
|
7
|
+
value: T;
|
8
|
+
name: LocalizedNames;
|
9
|
+
};
|
10
|
+
type Path = (string | number | null)[];
|
11
|
+
type NodeOrStructure = Partial<PropertyNode<any>> | Record<string, any> | any[];
|
12
|
+
/**
|
13
|
+
* Recursively constructs a nested object with descriptive properties from a path.
|
14
|
+
*
|
15
|
+
* @param currentObject - The object or array being built upon.
|
16
|
+
* @param path - The path segments to follow.
|
17
|
+
* @param sourceValue - The raw value to place at the end of the path.
|
18
|
+
* @param displayData - The data for generating localized names.
|
19
|
+
* @returns The new object or array structure.
|
20
|
+
*/
|
21
|
+
export declare const createNestedProperty: (currentObject: NodeOrStructure, path: Path, sourceValue: unknown, displayData: DisplayData) => NodeOrStructure;
|
22
|
+
export {};
|
23
|
+
//# sourceMappingURL=nestedProperty.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"nestedProperty.d.ts","sourceRoot":"","sources":["../../../src/utils/nestedProperty.ts"],"names":[],"mappings":"AAGA,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC;AAGtD,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAG7C,KAAK,YAAY,CAAC,CAAC,IAAI;IACrB,KAAK,EAAE,CAAC,CAAC;IACT,IAAI,EAAE,cAAc,CAAC;CACtB,CAAC;AAGF,KAAK,IAAI,GAAG,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;AAGvC,KAAK,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC;AA6GhF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,kBAChB,eAAe,2BAEjB,OAAO,+BAEnB,eA4BF,CAAC"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@pagopa/io-react-native-wallet",
|
3
|
-
"version": "2.
|
3
|
+
"version": "2.1.0",
|
4
4
|
"description": "Provide data structures, helpers and API for IO Wallet",
|
5
5
|
"main": "lib/commonjs/index",
|
6
6
|
"module": "lib/module/index",
|
@@ -57,7 +57,7 @@
|
|
57
57
|
"@pagopa/io-react-native-iso18013": "^0.3.0",
|
58
58
|
"@pagopa/io-react-native-jwt": "^2.1.0",
|
59
59
|
"@react-native/babel-preset": "0.78.3",
|
60
|
-
"@react-native/eslint-config": "
|
60
|
+
"@react-native/eslint-config": "0.78.3",
|
61
61
|
"@rushstack/eslint-patch": "^1.3.2",
|
62
62
|
"@types/jest": "^29.5.13",
|
63
63
|
"@types/jsrsasign": "^10.5.15",
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import type { CryptoContext } from "@pagopa/io-react-native-jwt";
|
2
|
-
import {
|
2
|
+
import { type Out } from "../../utils/misc";
|
3
3
|
import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
|
4
4
|
import { IoWalletError } from "../../utils/errors";
|
5
5
|
import { SdJwt4VC, verify as verifySdJwt } from "../../sd-jwt";
|
@@ -13,6 +13,7 @@ import { LogLevel, Logger } from "../../utils/logging";
|
|
13
13
|
import { extractElementValueAsDate } from "../../mdoc/converter";
|
14
14
|
import type { CBOR } from "@pagopa/io-react-native-iso18013";
|
15
15
|
import type { PublicKey } from "@pagopa/io-react-native-crypto";
|
16
|
+
import { createNestedProperty } from "../../utils/nestedProperty";
|
16
17
|
|
17
18
|
type IssuerConf = Out<EvaluateIssuerTrust>["issuerConf"];
|
18
19
|
type CredentialConf =
|
@@ -64,118 +65,6 @@ type DecodedSdJwtCredential = Out<typeof verifySdJwt> & {
|
|
64
65
|
sdJwt: SdJwt4VC;
|
65
66
|
};
|
66
67
|
|
67
|
-
// The data used to create localized names
|
68
|
-
type DisplayData = { locale: string; name: string }[];
|
69
|
-
|
70
|
-
// The resulting object of localized names { en: "Name", it: "Nome" }
|
71
|
-
type LocalizedNames = Record<string, string>;
|
72
|
-
|
73
|
-
// The core structure being built: a node containing the actual value and its localized names
|
74
|
-
type PropertyNode<T> = {
|
75
|
-
value: T;
|
76
|
-
name: LocalizedNames;
|
77
|
-
};
|
78
|
-
|
79
|
-
// A path can consist of object keys, array indices, or null for mapping
|
80
|
-
type Path = (string | number | null)[];
|
81
|
-
|
82
|
-
// A union of all possible shapes. It can be a custom PropertyNode or a standard object/array structure
|
83
|
-
type NodeOrStructure = Partial<PropertyNode<any>> | Record<string, any> | any[];
|
84
|
-
|
85
|
-
// Helper to build localized names from the display data.
|
86
|
-
const buildName = (display: DisplayData): LocalizedNames =>
|
87
|
-
display.reduce(
|
88
|
-
(names, { locale, name }) => ({ ...names, [locale]: name }),
|
89
|
-
{}
|
90
|
-
);
|
91
|
-
|
92
|
-
/**
|
93
|
-
* Recursively constructs a nested object with descriptive properties from a path.
|
94
|
-
*
|
95
|
-
* @param currentObject - The object or array being built upon.
|
96
|
-
* @param path - The path segments to follow.
|
97
|
-
* @param sourceValue - The raw value to place at the end of the path.
|
98
|
-
* @param displayData - The data for generating localized names.
|
99
|
-
* @returns The new object or array structure.
|
100
|
-
*/
|
101
|
-
const createNestedProperty = (
|
102
|
-
currentObject: NodeOrStructure,
|
103
|
-
path: Path,
|
104
|
-
sourceValue: unknown, // Use `unknown` for type-safe input
|
105
|
-
displayData: DisplayData
|
106
|
-
): NodeOrStructure => {
|
107
|
-
const [key, ...rest] = path;
|
108
|
-
|
109
|
-
// Case 1: Map over an array (key is null)
|
110
|
-
if (key === null) {
|
111
|
-
if (!Array.isArray(sourceValue)) return currentObject;
|
112
|
-
|
113
|
-
// We assert the type here because we know this branch handles PropertyNodes
|
114
|
-
const node = currentObject as Partial<PropertyNode<unknown[]>>;
|
115
|
-
const existingValue = Array.isArray(node.value) ? node.value : [];
|
116
|
-
|
117
|
-
const mappedArray = sourceValue.map((item, idx) =>
|
118
|
-
createNestedProperty(existingValue[idx] || {}, rest, item, displayData)
|
119
|
-
);
|
120
|
-
|
121
|
-
return {
|
122
|
-
...node,
|
123
|
-
value: mappedArray,
|
124
|
-
name: node.name ?? buildName(displayData),
|
125
|
-
};
|
126
|
-
}
|
127
|
-
|
128
|
-
// Case 2: Handle an object key (key is a string)
|
129
|
-
if (typeof key === "string") {
|
130
|
-
let nextSourceValue = sourceValue;
|
131
|
-
|
132
|
-
if (isObject(sourceValue)) {
|
133
|
-
// Skip processing when the key is not found within the claim object
|
134
|
-
if (!(key in sourceValue)) return currentObject;
|
135
|
-
|
136
|
-
nextSourceValue = sourceValue[key];
|
137
|
-
}
|
138
|
-
|
139
|
-
// base case
|
140
|
-
if (rest.length === 0) {
|
141
|
-
return {
|
142
|
-
...currentObject,
|
143
|
-
[key]: { value: nextSourceValue, name: buildName(displayData) },
|
144
|
-
};
|
145
|
-
}
|
146
|
-
|
147
|
-
// recursive step
|
148
|
-
const nextObject =
|
149
|
-
(currentObject as Record<string, NodeOrStructure>)[key] || {};
|
150
|
-
|
151
|
-
return {
|
152
|
-
...currentObject,
|
153
|
-
[key]: createNestedProperty(
|
154
|
-
nextObject,
|
155
|
-
rest,
|
156
|
-
nextSourceValue,
|
157
|
-
displayData
|
158
|
-
),
|
159
|
-
};
|
160
|
-
}
|
161
|
-
|
162
|
-
// Case 3: Handle a specific array index (key is a number)
|
163
|
-
if (typeof key === "number") {
|
164
|
-
const newArray = Array.isArray(currentObject) ? [...currentObject] : [];
|
165
|
-
const nextValue = Array.isArray(sourceValue) ? sourceValue[key] : undefined;
|
166
|
-
|
167
|
-
newArray[key] = createNestedProperty(
|
168
|
-
newArray[key] || {},
|
169
|
-
rest,
|
170
|
-
nextValue,
|
171
|
-
displayData
|
172
|
-
);
|
173
|
-
return newArray;
|
174
|
-
}
|
175
|
-
|
176
|
-
return currentObject;
|
177
|
-
};
|
178
|
-
|
179
68
|
const parseCredentialSdJwt = (
|
180
69
|
// The credential configuration to use to parse the provided credential
|
181
70
|
credentialConfig: CredentialConf,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Disclosure, SdJwt4VC } from "../types";
|
1
|
+
import { Disclosure, SdJwt4VC, Verification } from "../types";
|
2
2
|
|
3
3
|
describe("SdJwt4VC", () => {
|
4
4
|
it("should accept a valid token", () => {
|
@@ -75,3 +75,51 @@ describe("Disclosure", () => {
|
|
75
75
|
expect(success).toBe(true);
|
76
76
|
});
|
77
77
|
});
|
78
|
+
|
79
|
+
describe("Verification.time", () => {
|
80
|
+
test.each([
|
81
|
+
["ISO string", "2025-09-09T10:00:00Z"],
|
82
|
+
["unix seconds", 1752122400],
|
83
|
+
["unix milliseconds", 1752122400000],
|
84
|
+
])("accepts %s", (_label, time) => {
|
85
|
+
const value = {
|
86
|
+
trust_framework: "eidas",
|
87
|
+
assurance_level: "high",
|
88
|
+
evidence: [
|
89
|
+
{
|
90
|
+
type: "vouch",
|
91
|
+
time,
|
92
|
+
attestation: {
|
93
|
+
type: "digital_attestation",
|
94
|
+
reference_number: "abc",
|
95
|
+
date_of_issuance: "2025-09-02",
|
96
|
+
voucher: { organization: "IPZS" },
|
97
|
+
},
|
98
|
+
},
|
99
|
+
],
|
100
|
+
};
|
101
|
+
|
102
|
+
expect(Verification.safeParse(value).success).toBe(true);
|
103
|
+
});
|
104
|
+
|
105
|
+
it("rejects invalid type", () => {
|
106
|
+
const value = {
|
107
|
+
trust_framework: "eidas",
|
108
|
+
assurance_level: "high",
|
109
|
+
evidence: [
|
110
|
+
{
|
111
|
+
type: "vouch",
|
112
|
+
time: null,
|
113
|
+
attestation: {
|
114
|
+
type: "digital_attestation",
|
115
|
+
reference_number: "abc",
|
116
|
+
date_of_issuance: "2025-09-02",
|
117
|
+
voucher: { organization: "IPZS" },
|
118
|
+
},
|
119
|
+
},
|
120
|
+
],
|
121
|
+
};
|
122
|
+
|
123
|
+
expect(Verification.safeParse(value).success).toBe(false);
|
124
|
+
});
|
125
|
+
});
|
package/src/sd-jwt/types.ts
CHANGED
@@ -96,7 +96,8 @@ export const Verification = z.object({
|
|
96
96
|
evidence: z.array(
|
97
97
|
z.object({
|
98
98
|
type: z.literal("vouch"),
|
99
|
-
|
99
|
+
// Support both string and UNIX timestamp for backward compatibility
|
100
|
+
time: z.union([z.string(), z.number()]),
|
100
101
|
attestation: z.object({
|
101
102
|
type: z.literal("digital_attestation"),
|
102
103
|
reference_number: z.string(),
|
@@ -0,0 +1,198 @@
|
|
1
|
+
import { isObject } from "./misc";
|
2
|
+
|
3
|
+
// The data used to create localized names
|
4
|
+
type DisplayData = { locale: string; name: string }[];
|
5
|
+
|
6
|
+
// The resulting object of localized names { en: "Name", it: "Nome" }
|
7
|
+
type LocalizedNames = Record<string, string>;
|
8
|
+
|
9
|
+
// The core structure being built: a node containing the actual value and its localized names
|
10
|
+
type PropertyNode<T> = {
|
11
|
+
value: T;
|
12
|
+
name: LocalizedNames;
|
13
|
+
};
|
14
|
+
|
15
|
+
// A path can consist of object keys, array indices, or null for mapping
|
16
|
+
type Path = (string | number | null)[];
|
17
|
+
|
18
|
+
// A union of all possible shapes. It can be a custom PropertyNode or a standard object/array structure
|
19
|
+
type NodeOrStructure = Partial<PropertyNode<any>> | Record<string, any> | any[];
|
20
|
+
|
21
|
+
// Helper to build localized names from the display data.
|
22
|
+
const buildName = (display: DisplayData): LocalizedNames =>
|
23
|
+
display.reduce(
|
24
|
+
(names, { locale, name }) => ({ ...names, [locale]: name }),
|
25
|
+
{}
|
26
|
+
);
|
27
|
+
|
28
|
+
// Handles the case where the path key is `null`
|
29
|
+
const handleNullKeyCase = (
|
30
|
+
currentObject: NodeOrStructure,
|
31
|
+
rest: Path,
|
32
|
+
sourceValue: unknown,
|
33
|
+
displayData: DisplayData
|
34
|
+
): NodeOrStructure => {
|
35
|
+
if (!Array.isArray(sourceValue)) return currentObject;
|
36
|
+
|
37
|
+
// We assert the type here because we know this branch handles PropertyNodes
|
38
|
+
const node = currentObject as Partial<PropertyNode<unknown[]>>;
|
39
|
+
const existingValue = Array.isArray(node.value) ? node.value : [];
|
40
|
+
|
41
|
+
const mappedArray = sourceValue.map((item, idx) =>
|
42
|
+
createNestedProperty(existingValue[idx] || {}, rest, item, displayData)
|
43
|
+
);
|
44
|
+
|
45
|
+
return {
|
46
|
+
...node,
|
47
|
+
value: mappedArray,
|
48
|
+
name: node.name ?? buildName(displayData),
|
49
|
+
};
|
50
|
+
};
|
51
|
+
|
52
|
+
// Handles the case where the path key is a string
|
53
|
+
const handleStringKeyCase = (
|
54
|
+
currentObject: NodeOrStructure,
|
55
|
+
key: string,
|
56
|
+
rest: Path,
|
57
|
+
sourceValue: unknown,
|
58
|
+
displayData: DisplayData
|
59
|
+
): NodeOrStructure => {
|
60
|
+
let nextSourceValue = sourceValue;
|
61
|
+
const isLeaf = rest.length === 0;
|
62
|
+
|
63
|
+
if (isObject(sourceValue)) {
|
64
|
+
// Check if any remaining string keys in the path exist in current sourceValue
|
65
|
+
// This handles nested object paths (unlike arrays which use null in the path)
|
66
|
+
const hasRestKey = rest.some(
|
67
|
+
(r) => typeof r === "string" && r in sourceValue
|
68
|
+
);
|
69
|
+
|
70
|
+
if (hasRestKey) {
|
71
|
+
return handleRestKey(currentObject, key, rest, sourceValue, displayData);
|
72
|
+
}
|
73
|
+
|
74
|
+
// Skip processing when the key is not found within the claim object
|
75
|
+
if (!(key in sourceValue)) {
|
76
|
+
// Leaf node: create a node with an empty value and display name
|
77
|
+
if (isLeaf) {
|
78
|
+
return {
|
79
|
+
...currentObject,
|
80
|
+
[key]: { value: {}, name: buildName(displayData) },
|
81
|
+
};
|
82
|
+
}
|
83
|
+
// Skip processing when the key is not found within the claim object
|
84
|
+
return currentObject;
|
85
|
+
}
|
86
|
+
|
87
|
+
nextSourceValue = sourceValue[key];
|
88
|
+
}
|
89
|
+
|
90
|
+
// base case
|
91
|
+
if (isLeaf) {
|
92
|
+
return {
|
93
|
+
...currentObject,
|
94
|
+
[key]: { value: nextSourceValue, name: buildName(displayData) },
|
95
|
+
};
|
96
|
+
}
|
97
|
+
|
98
|
+
// recursive step
|
99
|
+
const nextObject =
|
100
|
+
(currentObject as Record<string, NodeOrStructure>)[key] || {};
|
101
|
+
|
102
|
+
return {
|
103
|
+
...currentObject,
|
104
|
+
[key]: createNestedProperty(nextObject, rest, nextSourceValue, displayData),
|
105
|
+
};
|
106
|
+
};
|
107
|
+
|
108
|
+
// Handles the case where the path key is a number
|
109
|
+
const handleNumberKeyCase = (
|
110
|
+
currentObject: NodeOrStructure,
|
111
|
+
key: number,
|
112
|
+
rest: Path,
|
113
|
+
sourceValue: unknown,
|
114
|
+
displayData: DisplayData
|
115
|
+
): NodeOrStructure => {
|
116
|
+
const newArray = Array.isArray(currentObject) ? [...currentObject] : [];
|
117
|
+
const nextValue = Array.isArray(sourceValue) ? sourceValue[key] : undefined;
|
118
|
+
|
119
|
+
newArray[key] = createNestedProperty(
|
120
|
+
newArray[key] || {},
|
121
|
+
rest,
|
122
|
+
nextValue,
|
123
|
+
displayData
|
124
|
+
);
|
125
|
+
return newArray;
|
126
|
+
};
|
127
|
+
|
128
|
+
/**
|
129
|
+
* Recursively constructs a nested object with descriptive properties from a path.
|
130
|
+
*
|
131
|
+
* @param currentObject - The object or array being built upon.
|
132
|
+
* @param path - The path segments to follow.
|
133
|
+
* @param sourceValue - The raw value to place at the end of the path.
|
134
|
+
* @param displayData - The data for generating localized names.
|
135
|
+
* @returns The new object or array structure.
|
136
|
+
*/
|
137
|
+
export const createNestedProperty = (
|
138
|
+
currentObject: NodeOrStructure,
|
139
|
+
path: Path,
|
140
|
+
sourceValue: unknown, // Use `unknown` for type-safe input
|
141
|
+
displayData: DisplayData
|
142
|
+
): NodeOrStructure => {
|
143
|
+
const [key, ...rest] = path;
|
144
|
+
|
145
|
+
switch (true) {
|
146
|
+
case key === null:
|
147
|
+
return handleNullKeyCase(currentObject, rest, sourceValue, displayData);
|
148
|
+
|
149
|
+
case typeof key === "string":
|
150
|
+
return handleStringKeyCase(
|
151
|
+
currentObject,
|
152
|
+
key as string,
|
153
|
+
rest,
|
154
|
+
sourceValue,
|
155
|
+
displayData
|
156
|
+
);
|
157
|
+
|
158
|
+
case typeof key === "number":
|
159
|
+
return handleNumberKeyCase(
|
160
|
+
currentObject,
|
161
|
+
key as number,
|
162
|
+
rest,
|
163
|
+
sourceValue,
|
164
|
+
displayData
|
165
|
+
);
|
166
|
+
|
167
|
+
default:
|
168
|
+
return currentObject;
|
169
|
+
}
|
170
|
+
};
|
171
|
+
|
172
|
+
// Handles the case where the next key in the path exists in the source object
|
173
|
+
const handleRestKey = (
|
174
|
+
currentObject: Record<string, any>,
|
175
|
+
key: string,
|
176
|
+
rest: Path,
|
177
|
+
sourceValue: Record<string, unknown>,
|
178
|
+
displayData: DisplayData
|
179
|
+
): NodeOrStructure => {
|
180
|
+
const currentNode = currentObject[key] ?? {};
|
181
|
+
// Take the first key in the remaining path
|
182
|
+
const restKey = rest[0] as string;
|
183
|
+
const nextSourceValue = sourceValue[restKey];
|
184
|
+
|
185
|
+
// Merge the current node with the updated nested property for the remaining path.
|
186
|
+
return {
|
187
|
+
...currentObject,
|
188
|
+
[key]: {
|
189
|
+
...currentNode,
|
190
|
+
value: createNestedProperty(
|
191
|
+
currentNode.value ?? {},
|
192
|
+
rest,
|
193
|
+
nextSourceValue,
|
194
|
+
displayData
|
195
|
+
),
|
196
|
+
},
|
197
|
+
};
|
198
|
+
};
|