this repo has no description
at main 289 lines 10 kB view raw
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