this repo has no description
1import { Opt, isNothing } from "@jet/environment/types/optional";
2import * as serverData from "./server-data";
3import * as media from "./data-structure";
4import { JSONValue, MapLike, JSONData } from "./json-types";
5import * as errors from "./errors";
6
7// region Generic Attribute retrieval
8
9// region Attribute retrieval
10
11/**
12 * Retrieve the specified attribute from the data, coercing it to a JSONData dictionary
13 *
14 * @param data The data from which to retrieve the attribute.
15 * @param attributePath The path of the attribute.
16 * @param defaultValue The object to return if the path search fails.
17 * @returns The dictionary of data
18 */
19export function attributeAsDictionary<Type extends JSONValue>(
20 data: media.Data,
21 attributePath?: serverData.ObjectPath,
22 defaultValue?: MapLike<Type>,
23): MapLike<Type> | null {
24 if (serverData.isNull(data)) {
25 return null;
26 }
27 return serverData.asDictionary(data.attributes, attributePath, defaultValue);
28}
29
30/**
31 * Retrieve the specified attribute from the data, coercing it to an Interface
32 *
33 * @param data The data from which to retrieve the attribute.
34 * @param attributePath The path of the attribute.
35 * @param defaultValue The object to return if the path search fails.
36 * @returns The dictionary of data as an interface
37 */
38export function attributeAsInterface<Interface>(
39 data: media.Data,
40 attributePath?: serverData.ObjectPath,
41 defaultValue?: JSONData,
42): Interface | null {
43 return attributeAsDictionary(data, attributePath, defaultValue) as unknown as Interface;
44}
45
46/**
47 * Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array.
48 *
49 * @param data The data from which to retrieve the attribute.
50 * @param attributePath The path of the attribute.
51 * @returns {any[]} The attribute value as an array.
52 */
53export function attributeAsArrayOrEmpty<T extends JSONValue>(
54 data: media.Data,
55 attributePath?: serverData.ObjectPath,
56): T[] {
57 if (serverData.isNull(data)) {
58 return [];
59 }
60 return serverData.asArrayOrEmpty(data.attributes, attributePath);
61}
62
63/**
64 * Retrieve the specified attribute from the data as a string.
65 *
66 * @param data The data from which to retrieve the attribute.
67 * @param attributePath The object path for the attribute.
68 * @param policy The validation policy to use when resolving this value.
69 * @returns {string} The attribute value as a string.
70 */
71export function attributeAsString(
72 data: media.Data,
73 attributePath?: serverData.ObjectPath,
74 policy: serverData.ValidationPolicy = "coercible",
75): Opt<string> {
76 if (serverData.isNull(data)) {
77 return null;
78 }
79 return serverData.asString(data.attributes, attributePath, policy);
80}
81
82/**
83 * Retrieve the specified meta from the data as a string.
84 *
85 * @param data The data from which to retrieve the attribute.
86 * @param metaPath The object path for the meta.
87 * @param policy The validation policy to use when resolving this value.
88 * @returns {string} The meta value as a string.
89 */
90export function metaAsString(
91 data: media.Data,
92 metaPath?: serverData.ObjectPath,
93 policy: serverData.ValidationPolicy = "coercible",
94): Opt<string> {
95 if (serverData.isNull(data)) {
96 return null;
97 }
98 return serverData.asString(data.meta, metaPath, policy);
99}
100
101/**
102 * Retrieve the specified attribute from the data as a date.
103 *
104 * @param data The data from which to retrieve the attribute.
105 * @param attributePath The object path for the attribute.
106 * @param policy The validation policy to use when resolving this value.
107 * @returns {Date} The attribute value as a date.
108 */
109export function attributeAsDate(
110 data: media.Data,
111 attributePath?: serverData.ObjectPath,
112 policy: serverData.ValidationPolicy = "coercible",
113): Opt<Date> {
114 if (serverData.isNull(data)) {
115 return null;
116 }
117 const dateString = serverData.asString(data.attributes, attributePath, policy);
118 if (isNothing(dateString)) {
119 return null;
120 }
121 return new Date(dateString);
122}
123
124/**
125 * Retrieve the specified attribute from the data as a boolean.
126 *
127 * @param data The data from which to retrieve the attribute.
128 * @param attributePath The path of the attribute.
129 * @param policy The validation policy to use when resolving this value.
130 * @returns {boolean} The attribute value as a boolean.
131 */
132export function attributeAsBoolean(
133 data: media.Data,
134 attributePath?: serverData.ObjectPath,
135 policy: serverData.ValidationPolicy = "coercible",
136): boolean | null {
137 if (serverData.isNull(data)) {
138 return null;
139 }
140 return serverData.asBoolean(data.attributes, attributePath, policy);
141}
142
143/**
144 * Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist.
145 *
146 * @param data The data from which to retrieve the attribute.
147 * @param attributePath The path of the attribute.
148 * @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present..
149 */
150export function attributeAsBooleanOrFalse(data: media.Data, attributePath?: serverData.ObjectPath): boolean {
151 if (serverData.isNull(data)) {
152 return false;
153 }
154 return serverData.asBooleanOrFalse(data.attributes, attributePath);
155}
156
157/**
158 * Retrieve the specified attribute from the data as a number.
159 *
160 * @param data The data from which to retrieve the attribute.
161 * @param attributePath The path of the attribute.
162 * @param policy The validation policy to use when resolving this value.
163 * @returns {boolean} The attribute value as a number.
164 */
165export function attributeAsNumber(
166 data: media.Data,
167 attributePath?: serverData.ObjectPath,
168 policy: serverData.ValidationPolicy = "coercible",
169): Opt<number> {
170 if (serverData.isNull(data)) {
171 return null;
172 }
173 return serverData.asNumber(data.attributes, attributePath, policy);
174}
175
176export function hasAttributes(data: media.Data): boolean {
177 return !serverData.isNull(serverData.asDictionary(data, "attributes"));
178}
179
180/**
181 * The canonical way to detect if an item from Media API is hydrated or not.
182 *
183 * @param data The data from which to retrieve the attributes.
184 */
185export function isNotHydrated(data: media.Data): boolean {
186 return !hasAttributes(data);
187}
188
189// region Custom Attributes
190
191/**
192 * Performs conversion for a custom variant of given attribute, if any are available.
193 * @param attribute Attribute to get custom attribute key for, if any.
194 */
195export function attributeKeyAsCustomAttributeKey(attribute: string): string | undefined {
196 return customAttributeMapping[attribute];
197}
198
199/**
200 * Whether or not given custom attributes key allows fallback to default page with AB testing treatment within a nondefault page.
201 * This is to allow AB testing to affect only icons within custom product pages.
202 */
203export function attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttribute: string): boolean {
204 return customAttribute === "customArtwork" || customAttribute === "customIconArtwork"; // Only the icon artwork.
205}
206
207/**
208 * Defines mapping of attribute to custom attribute.
209 */
210const customAttributeMapping: { [key: string]: string } = {
211 artwork: "customArtwork",
212 iconArtwork: "customIconArtwork",
213 screenshotsByType: "customScreenshotsByType",
214 promotionalText: "customPromotionalText",
215 videoPreviewsByType: "customVideoPreviewsByType",
216 customScreenshotsByTypeForAd: "customScreenshotsByTypeForAd",
217 customVideoPreviewsByTypeForAd: "customVideoPreviewsByTypeForAd",
218};
219
220export function requiredAttributeAsString(data: media.Data, attributePath: serverData.ObjectPath): string {
221 const value = attributeAsString(data, attributePath);
222 if (isNothing(value)) {
223 throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
224 }
225 return value;
226}
227
228export function requiredAttributeAsDate(data: media.Data, attributePath: serverData.ObjectPath): Date {
229 const value = attributeAsDate(data, attributePath);
230 if (isNothing(value)) {
231 throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
232 }
233 return value;
234}
235
236export function requiredAttributeAsDictionary<Type extends JSONValue>(
237 data: media.Data,
238 attributePath: serverData.ObjectPath,
239): MapLike<Type> {
240 const value: MapLike<Type> | null = attributeAsDictionary(data, attributePath);
241 if (isNothing(value)) {
242 throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
243 }
244 return value;
245}
246
247export function requiredMeta(data: media.Data): MapLike<JSONValue> {
248 const value = serverData.asDictionary(data, "meta");
249 if (isNothing(value)) {
250 throw new errors.MissingFieldError(data, "meta");
251 }
252 return value;
253}
254
255export function requiredMetaAttributeAsString(data: media.Data, attributePath: serverData.ObjectPath): string {
256 const meta = requiredMeta(data);
257 const value = serverData.asString(meta, attributePath);
258 if (isNothing(value)) {
259 throw new errors.MissingFieldError(data, concatObjectPaths("meta", attributePath));
260 }
261 return value;
262}
263
264export function requiredMetaAttributeAsNumber(data: media.Data, attributePath: serverData.ObjectPath): number {
265 const meta = requiredMeta(data);
266 const value = serverData.asNumber(meta, attributePath);
267 if (isNothing(value)) {
268 throw new errors.MissingFieldError(data, concatObjectPaths("meta", attributePath));
269 }
270 return value;
271}
272
273export function concatObjectPaths(prefix: serverData.ObjectPath, suffix: serverData.ObjectPath): serverData.ObjectPath {
274 let finalPath: string[];
275 if (Array.isArray(prefix)) {
276 finalPath = prefix;
277 } else {
278 finalPath = [prefix];
279 }
280
281 if (Array.isArray(suffix)) {
282 finalPath.push(...suffix);
283 } else {
284 finalPath.push(suffix);
285 }
286 return finalPath;
287}
288
289// endregion