mirror of
https://github.com/rxliuli/apps.apple.com.git
synced 2026-03-22 15:47:39 +01:00
290 lines
9.7 KiB
TypeScript
290 lines
9.7 KiB
TypeScript
import { Opt, isNothing } from "@jet/environment/types/optional";
|
|
import * as serverData from "./server-data";
|
|
import * as media from "./data-structure";
|
|
import { JSONValue, MapLike, JSONData } from "./json-types";
|
|
import * as errors from "./errors";
|
|
|
|
// region Generic Attribute retrieval
|
|
|
|
// region Attribute retrieval
|
|
|
|
/**
|
|
* Retrieve the specified attribute from the data, coercing it to a JSONData dictionary
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param attributePath The path of the attribute.
|
|
* @param defaultValue The object to return if the path search fails.
|
|
* @returns The dictionary of data
|
|
*/
|
|
export function attributeAsDictionary<Type extends JSONValue>(
|
|
data: media.Data,
|
|
attributePath?: serverData.ObjectPath,
|
|
defaultValue?: MapLike<Type>,
|
|
): MapLike<Type> | null {
|
|
if (serverData.isNull(data)) {
|
|
return null;
|
|
}
|
|
return serverData.asDictionary(data.attributes, attributePath, defaultValue);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the specified attribute from the data, coercing it to an Interface
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param attributePath The path of the attribute.
|
|
* @param defaultValue The object to return if the path search fails.
|
|
* @returns The dictionary of data as an interface
|
|
*/
|
|
export function attributeAsInterface<Interface>(
|
|
data: media.Data,
|
|
attributePath?: serverData.ObjectPath,
|
|
defaultValue?: JSONData,
|
|
): Interface | null {
|
|
return attributeAsDictionary(data, attributePath, defaultValue) as unknown as Interface;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the specified attribute from the data as an array, coercing to an empty array if the object is not an array.
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param attributePath The path of the attribute.
|
|
* @returns {any[]} The attribute value as an array.
|
|
*/
|
|
export function attributeAsArrayOrEmpty<T extends JSONValue>(
|
|
data: media.Data,
|
|
attributePath?: serverData.ObjectPath,
|
|
): T[] {
|
|
if (serverData.isNull(data)) {
|
|
return [];
|
|
}
|
|
return serverData.asArrayOrEmpty(data.attributes, attributePath);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the specified attribute from the data as a string.
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param attributePath The object path for the attribute.
|
|
* @param policy The validation policy to use when resolving this value.
|
|
* @returns {string} The attribute value as a string.
|
|
*/
|
|
export function attributeAsString(
|
|
data: media.Data,
|
|
attributePath?: serverData.ObjectPath,
|
|
policy: serverData.ValidationPolicy = "coercible",
|
|
): Opt<string> {
|
|
if (serverData.isNull(data)) {
|
|
return null;
|
|
}
|
|
return serverData.asString(data.attributes, attributePath, policy);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the specified meta from the data as a string.
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param metaPath The object path for the meta.
|
|
* @param policy The validation policy to use when resolving this value.
|
|
* @returns {string} The meta value as a string.
|
|
*/
|
|
export function metaAsString(
|
|
data: media.Data,
|
|
metaPath?: serverData.ObjectPath,
|
|
policy: serverData.ValidationPolicy = "coercible",
|
|
): Opt<string> {
|
|
if (serverData.isNull(data)) {
|
|
return null;
|
|
}
|
|
return serverData.asString(data.meta, metaPath, policy);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the specified attribute from the data as a date.
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param attributePath The object path for the attribute.
|
|
* @param policy The validation policy to use when resolving this value.
|
|
* @returns {Date} The attribute value as a date.
|
|
*/
|
|
export function attributeAsDate(
|
|
data: media.Data,
|
|
attributePath?: serverData.ObjectPath,
|
|
policy: serverData.ValidationPolicy = "coercible",
|
|
): Opt<Date> {
|
|
if (serverData.isNull(data)) {
|
|
return null;
|
|
}
|
|
const dateString = serverData.asString(data.attributes, attributePath, policy);
|
|
if (isNothing(dateString)) {
|
|
return null;
|
|
}
|
|
return new Date(dateString);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the specified attribute from the data as a boolean.
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param attributePath The path of the attribute.
|
|
* @param policy The validation policy to use when resolving this value.
|
|
* @returns {boolean} The attribute value as a boolean.
|
|
*/
|
|
export function attributeAsBoolean(
|
|
data: media.Data,
|
|
attributePath?: serverData.ObjectPath,
|
|
policy: serverData.ValidationPolicy = "coercible",
|
|
): boolean | null {
|
|
if (serverData.isNull(data)) {
|
|
return null;
|
|
}
|
|
return serverData.asBoolean(data.attributes, attributePath, policy);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the specified attribute from the data as a boolean, which will be `false` if the attribute does not exist.
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param attributePath The path of the attribute.
|
|
* @returns {boolean} The attribute value as a boolean, coercing to `false` if the value is not present..
|
|
*/
|
|
export function attributeAsBooleanOrFalse(data: media.Data, attributePath?: serverData.ObjectPath): boolean {
|
|
if (serverData.isNull(data)) {
|
|
return false;
|
|
}
|
|
return serverData.asBooleanOrFalse(data.attributes, attributePath);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the specified attribute from the data as a number.
|
|
*
|
|
* @param data The data from which to retrieve the attribute.
|
|
* @param attributePath The path of the attribute.
|
|
* @param policy The validation policy to use when resolving this value.
|
|
* @returns {boolean} The attribute value as a number.
|
|
*/
|
|
export function attributeAsNumber(
|
|
data: media.Data,
|
|
attributePath?: serverData.ObjectPath,
|
|
policy: serverData.ValidationPolicy = "coercible",
|
|
): Opt<number> {
|
|
if (serverData.isNull(data)) {
|
|
return null;
|
|
}
|
|
return serverData.asNumber(data.attributes, attributePath, policy);
|
|
}
|
|
|
|
export function hasAttributes(data: media.Data): boolean {
|
|
return !serverData.isNull(serverData.asDictionary(data, "attributes"));
|
|
}
|
|
|
|
/**
|
|
* The canonical way to detect if an item from Media API is hydrated or not.
|
|
*
|
|
* @param data The data from which to retrieve the attributes.
|
|
*/
|
|
export function isNotHydrated(data: media.Data): boolean {
|
|
return !hasAttributes(data);
|
|
}
|
|
|
|
// region Custom Attributes
|
|
|
|
/**
|
|
* Performs conversion for a custom variant of given attribute, if any are available.
|
|
* @param attribute Attribute to get custom attribute key for, if any.
|
|
*/
|
|
export function attributeKeyAsCustomAttributeKey(attribute: string): string | undefined {
|
|
return customAttributeMapping[attribute];
|
|
}
|
|
|
|
/**
|
|
* Whether or not given custom attributes key allows fallback to default page with AB testing treatment within a nondefault page.
|
|
* This is to allow AB testing to affect only icons within custom product pages.
|
|
*/
|
|
export function attributeAllowsNonDefaultTreatmentInNonDefaultPage(customAttribute: string): boolean {
|
|
return customAttribute === "customArtwork" || customAttribute === "customIconArtwork"; // Only the icon artwork.
|
|
}
|
|
|
|
/**
|
|
* Defines mapping of attribute to custom attribute.
|
|
*/
|
|
const customAttributeMapping: { [key: string]: string } = {
|
|
artwork: "customArtwork",
|
|
iconArtwork: "customIconArtwork",
|
|
screenshotsByType: "customScreenshotsByType",
|
|
promotionalText: "customPromotionalText",
|
|
videoPreviewsByType: "customVideoPreviewsByType",
|
|
customScreenshotsByTypeForAd: "customScreenshotsByTypeForAd",
|
|
customVideoPreviewsByTypeForAd: "customVideoPreviewsByTypeForAd",
|
|
};
|
|
|
|
export function requiredAttributeAsString(data: media.Data, attributePath: serverData.ObjectPath): string {
|
|
const value = attributeAsString(data, attributePath);
|
|
if (isNothing(value)) {
|
|
throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function requiredAttributeAsDate(data: media.Data, attributePath: serverData.ObjectPath): Date {
|
|
const value = attributeAsDate(data, attributePath);
|
|
if (isNothing(value)) {
|
|
throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function requiredAttributeAsDictionary<Type extends JSONValue>(
|
|
data: media.Data,
|
|
attributePath: serverData.ObjectPath,
|
|
): MapLike<Type> {
|
|
const value: MapLike<Type> | null = attributeAsDictionary(data, attributePath);
|
|
if (isNothing(value)) {
|
|
throw new errors.MissingFieldError(data, concatObjectPaths("attributes", attributePath));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function requiredMeta(data: media.Data): MapLike<JSONValue> {
|
|
const value = serverData.asDictionary(data, "meta");
|
|
if (isNothing(value)) {
|
|
throw new errors.MissingFieldError(data, "meta");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function requiredMetaAttributeAsString(data: media.Data, attributePath: serverData.ObjectPath): string {
|
|
const meta = requiredMeta(data);
|
|
const value = serverData.asString(meta, attributePath);
|
|
if (isNothing(value)) {
|
|
throw new errors.MissingFieldError(data, concatObjectPaths("meta", attributePath));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function requiredMetaAttributeAsNumber(data: media.Data, attributePath: serverData.ObjectPath): number {
|
|
const meta = requiredMeta(data);
|
|
const value = serverData.asNumber(meta, attributePath);
|
|
if (isNothing(value)) {
|
|
throw new errors.MissingFieldError(data, concatObjectPaths("meta", attributePath));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function concatObjectPaths(prefix: serverData.ObjectPath, suffix: serverData.ObjectPath): serverData.ObjectPath {
|
|
let finalPath: string[];
|
|
if (Array.isArray(prefix)) {
|
|
finalPath = prefix;
|
|
} else {
|
|
finalPath = [prefix];
|
|
}
|
|
|
|
if (Array.isArray(suffix)) {
|
|
finalPath.push(...suffix);
|
|
} else {
|
|
finalPath.push(suffix);
|
|
}
|
|
return finalPath;
|
|
}
|
|
|
|
// endregion
|