387 lines
12 KiB
TypeScript
387 lines
12 KiB
TypeScript
// Normalizadores y adaptadores DTO -> Maybe/VO
|
|
|
|
import { Maybe, Result, isMaybe, isNullishOrEmpty } from "@repo/rdx-utils";
|
|
|
|
import { MoneyValue, Percentage, Quantity } from "../value-objects";
|
|
|
|
/**
|
|
* Aplica una transformación a un valor potencialmente nulo/indefinido.
|
|
*
|
|
* Casos:
|
|
* - `null` → `null`
|
|
* - `undefined` → `null`
|
|
* - valor no nulo → `map(value)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* mapNullable(null, String); // null
|
|
* mapNullable(undefined, String); // null
|
|
* mapNullable(12, String); // "12"
|
|
* mapNullable("abc", (value) => value.toUpperCase()); // "ABC"
|
|
* ```
|
|
*
|
|
* @typeParam T - Tipo de entrada no nulo.
|
|
* @typeParam R - Tipo de salida.
|
|
* @param input - Valor potencialmente nulo o indefinido.
|
|
* @param map - Función de transformación aplicada solo si `input` tiene valor.
|
|
* @returns Resultado de `map` o `null` si el input es nulo.
|
|
*/
|
|
function mapNullable<T, R>(input: T | null | undefined, map: (value: T) => R): R | null {
|
|
return input == null ? null : map(input);
|
|
}
|
|
|
|
/**
|
|
* Serializa un `Maybe<T>` a un valor de salida, aplicando una política explícita para `None`.
|
|
*
|
|
* Casos:
|
|
* - `Maybe.none()` → `onNone`
|
|
* - `Maybe.some(value)` → `map(value)`
|
|
* - valor falsy inesperado → `onNone`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* serializeMaybe(Maybe.none<string>(), null, (value) => value); // null
|
|
* serializeMaybe(Maybe.none<string>(), "", (value) => value); // ""
|
|
* serializeMaybe(Maybe.some("abc"), null, (value) => value.toUpperCase()); // "ABC"
|
|
* ```
|
|
*
|
|
* @typeParam T - Tipo interno del Maybe.
|
|
* @typeParam R - Tipo de salida.
|
|
* @param maybe - Instancia de `Maybe`.
|
|
* @param onNone - Valor a devolver cuando el Maybe es `None`.
|
|
* @param map - Función de transformación para el caso `Some`.
|
|
* @returns Valor serializado.
|
|
*/
|
|
function serializeMaybe<T, R>(maybe: Maybe<T>, onNone: R, map: (value: T) => R): R {
|
|
return !maybe || maybe.isNone() ? onNone : map(maybe.unwrap());
|
|
}
|
|
|
|
/**
|
|
* Convierte un valor normal, nullable o `Maybe` en un valor nullable (`R | null`).
|
|
*
|
|
* Casos:
|
|
* - `null` → `null`
|
|
* - `undefined` → `null`
|
|
* - valor directo `T` → `map(value)`
|
|
* - `Maybe.none()` → `null`
|
|
* - `Maybe.some(value)` → `map(value)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* toNullable(null, String); // null
|
|
* toNullable(undefined, String); // null
|
|
* toNullable(12, String); // "12"
|
|
* toNullable(Maybe.none<number>(), String); // null
|
|
* toNullable(Maybe.some(12), String); // "12"
|
|
* ```
|
|
*
|
|
* @typeParam V - Tipo de entrada.
|
|
* @typeParam R - Tipo de salida.
|
|
* @param input - Valor directo, nullable o `Maybe` a transformar.
|
|
* @param map - Función de transformación aplicada solo cuando hay valor.
|
|
* @returns Valor transformado o `null`.
|
|
*/
|
|
export function toNullable<V, R>(input: null | undefined, map: (t: V) => R): null;
|
|
export function toNullable<V, R>(input: Maybe<V>, map: (t: V) => R): R | null;
|
|
export function toNullable<V, R>(input: V | null, map: (t: V) => R): R | null;
|
|
export function toNullable<V, R>(input: V, map: (t: V) => R): R;
|
|
export function toNullable<V, R>(
|
|
input: V | Maybe<V> | null | undefined,
|
|
map: (t: V) => R
|
|
): R | null {
|
|
if (isMaybe<V>(input)) {
|
|
return maybeToNullable<V, R>(input, map);
|
|
}
|
|
|
|
return mapNullable<V, R>(input, map);
|
|
}
|
|
|
|
/**
|
|
* Convierte un valor nullable o vacío en un `Maybe<T>` aplicando validación.
|
|
*
|
|
* Casos:
|
|
* - `null` → `Result.ok(Maybe.none())`
|
|
* - `undefined` → `Result.ok(Maybe.none())`
|
|
* - `""` → `Result.ok(Maybe.none())`
|
|
* - whitespace, si `isNullishOrEmpty` lo considera vacío → `Result.ok(Maybe.none())`
|
|
* - valor con contenido + validación correcta → `Result.ok(Maybe.some(value))`
|
|
* - valor con contenido + validación fallida → `Result.fail(error)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeFromNullableResult(null, TextValue.create); // Ok(None)
|
|
* maybeFromNullableResult("", TextValue.create); // Ok(None)
|
|
* maybeFromNullableResult("ACME", TextValue.create); // Ok(Some(TextValue))
|
|
* maybeFromNullableResult("x", TextValue.create); // Fail(error), si el VO lo rechaza
|
|
* ```
|
|
*
|
|
* @typeParam T - Tipo validado.
|
|
* @typeParam S - Tipo de entrada.
|
|
* @param input - Valor a validar.
|
|
* @param validate - Función de validación/construcción del valor cuando `input` tiene contenido.
|
|
* @returns Resultado con `Maybe<T>` o error de validación.
|
|
*/
|
|
export function maybeFromNullableResult<T, S>(
|
|
input: S,
|
|
validate: (raw: NonNullable<S>) => Result<T>
|
|
): Result<Maybe<T>> {
|
|
if (isNullishOrEmpty(input)) {
|
|
return Result.ok(Maybe.none<T>());
|
|
}
|
|
|
|
const value = validate(input as NonNullable<S>);
|
|
return value.isSuccess ? Result.ok(Maybe.some(value.data)) : Result.fail(value.error);
|
|
}
|
|
|
|
/**
|
|
* Serializa un `Maybe<T>` a un objeto aplicando una política de objeto vacío.
|
|
*
|
|
* Casos:
|
|
* - `Maybe.none()` → `emptyObject`
|
|
* - `Maybe.some(value)` sin `map` → `defaultSerializer(value)`
|
|
* - `Maybe.some(value)` con `map` → `map(value)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeToEmptyObjectString(Maybe.none(), emptyMoney, serializeMoney); // emptyMoney
|
|
* maybeToEmptyObjectString(Maybe.some(money), emptyMoney, serializeMoney); // serializeMoney(money)
|
|
* maybeToEmptyObjectString(Maybe.some(money), emptyMoney, serializeMoney, customMap); // customMap(money)
|
|
* ```
|
|
*
|
|
* @internal
|
|
*
|
|
* @typeParam T - Tipo interno del Maybe.
|
|
* @typeParam R - Tipo de salida, normalmente objeto de transporte.
|
|
* @param maybe - Instancia `Maybe`.
|
|
* @param emptyObject - Objeto a devolver si es `None`.
|
|
* @param defaultSerializer - Serializador por defecto para `Some`.
|
|
* @param map - Serializador opcional personalizado; prevalece sobre `defaultSerializer`.
|
|
* @returns Objeto serializado.
|
|
*/
|
|
function maybeToEmptyObjectString<T, R>(
|
|
maybe: Maybe<T>,
|
|
emptyObject: R,
|
|
defaultSerializer: (value: T) => R,
|
|
map?: (value: T) => R
|
|
): R {
|
|
return serializeMaybe(maybe, emptyObject, (value) =>
|
|
map ? map(value) : defaultSerializer(value)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Serializa un `Maybe<MoneyValue>` a un objeto de transporte.
|
|
*
|
|
* Casos:
|
|
* - `Maybe.none()` → `MoneyValue.EMPTY_MONEY_OBJECT`
|
|
* - `Maybe.some(value)` sin `map` → `value.toObjectString()`
|
|
* - `Maybe.some(value)` con `map` → `map(value)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeToEmptyMoneyObjectString(Maybe.none());
|
|
* // { value: "", scale: "", currency_code: "" }
|
|
*
|
|
* maybeToEmptyMoneyObjectString(Maybe.some(money));
|
|
* // { value: "1000", scale: "2", currency_code: "EUR" }
|
|
*
|
|
* maybeToEmptyMoneyObjectString(Maybe.some(money), (value) => ({
|
|
* value: value.toDecimalString(),
|
|
* scale: "2",
|
|
* currency_code: "EUR",
|
|
* }));
|
|
* ```
|
|
*
|
|
* @param maybe - Maybe de `MoneyValue`.
|
|
* @param map - Transformación opcional; si existe, sustituye a `toObjectString()`.
|
|
* @returns Objeto de dinero serializado.
|
|
*/
|
|
export function maybeToEmptyMoneyObjectString(
|
|
maybe: Maybe<MoneyValue>,
|
|
map?: (value: MoneyValue) => { value: string; scale: string; currency_code: string }
|
|
): { value: string; scale: string; currency_code: string } {
|
|
return maybeToEmptyObjectString(
|
|
maybe,
|
|
MoneyValue.EMPTY_MONEY_OBJECT,
|
|
(value) => value.toObjectString(),
|
|
map
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Serializa un `Maybe<Percentage>` a un objeto de transporte.
|
|
*
|
|
* Casos:
|
|
* - `Maybe.none()` → `Percentage.EMPTY_PERCENTAGE_OBJECT`
|
|
* - `Maybe.some(value)` sin `map` → `value.toObjectString()`
|
|
* - `Maybe.some(value)` con `map` → `map(value)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeToEmptyPercentageObjectString(Maybe.none());
|
|
* // { value: "", scale: "" }
|
|
*
|
|
* maybeToEmptyPercentageObjectString(Maybe.some(percentage));
|
|
* // { value: "2100", scale: "2" }
|
|
*
|
|
* maybeToEmptyPercentageObjectString(Maybe.some(percentage), (value) => ({
|
|
* value: value.toDecimalString(),
|
|
* scale: "2",
|
|
* }));
|
|
* ```
|
|
*
|
|
* @param maybe - Maybe de `Percentage`.
|
|
* @param map - Transformación opcional; si existe, sustituye a `toObjectString()`.
|
|
* @returns Objeto de porcentaje serializado.
|
|
*/
|
|
export function maybeToEmptyPercentageObjectString(
|
|
maybe: Maybe<Percentage>,
|
|
map?: (value: Percentage) => { value: string; scale: string }
|
|
): { value: string; scale: string } {
|
|
return maybeToEmptyObjectString(
|
|
maybe,
|
|
Percentage.EMPTY_PERCENTAGE_OBJECT,
|
|
(value) => value.toObjectString(),
|
|
map
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Serializa un `Maybe<Quantity>` a un objeto de transporte.
|
|
*
|
|
* Casos:
|
|
* - `Maybe.none()` → `Quantity.EMPTY_QUANTITY_OBJECT`
|
|
* - `Maybe.some(value)` sin `map` → `value.toObjectString()`
|
|
* - `Maybe.some(value)` con `map` → `map(value)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeToEmptyQuantityObjectString(Maybe.none());
|
|
* // { value: "", scale: "" }
|
|
*
|
|
* maybeToEmptyQuantityObjectString(Maybe.some(quantity));
|
|
* // { value: "1500", scale: "3" }
|
|
*
|
|
* maybeToEmptyQuantityObjectString(Maybe.some(quantity), (value) => ({
|
|
* value: value.toDecimalString(),
|
|
* scale: "3",
|
|
* }));
|
|
* ```
|
|
*
|
|
* @param maybe - Maybe de `Quantity`.
|
|
* @param map - Transformación opcional; si existe, sustituye a `toObjectString()`.
|
|
* @returns Objeto de cantidad serializado.
|
|
*/
|
|
export function maybeToEmptyQuantityObjectString(
|
|
maybe: Maybe<Quantity>,
|
|
map?: (value: Quantity) => { value: string; scale: string }
|
|
): { value: string; scale: string } {
|
|
return maybeToEmptyObjectString(
|
|
maybe,
|
|
Quantity.EMPTY_QUANTITY_OBJECT,
|
|
(value) => value.toObjectString(),
|
|
map
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Convierte un string nullable o vacío en `Maybe<string>`.
|
|
*
|
|
* Casos:
|
|
* - `null` → `Maybe.none()`
|
|
* - `undefined` → `Maybe.none()`
|
|
* - `""` → `Maybe.none()`
|
|
* - `" "` → `Maybe.none()`
|
|
* - `" abc "` → `Maybe.some("abc")`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeFromNullableOrEmptyString(null); // None
|
|
* maybeFromNullableOrEmptyString(undefined); // None
|
|
* maybeFromNullableOrEmptyString(""); // None
|
|
* maybeFromNullableOrEmptyString(" "); // None
|
|
* maybeFromNullableOrEmptyString(" ACME "); // Some("ACME")
|
|
* ```
|
|
*
|
|
* @param input - String de entrada.
|
|
* @returns Maybe del string normalizado con `trim()`.
|
|
*/
|
|
export function maybeFromNullableOrEmptyString(input?: string | null): Maybe<string> {
|
|
if (input == null) return Maybe.none<string>();
|
|
|
|
const trimmed = input.trim();
|
|
return trimmed ? Maybe.some(trimmed) : Maybe.none<string>();
|
|
}
|
|
|
|
/**
|
|
* Convierte un `Maybe<T>` en `R | null`.
|
|
*
|
|
* Casos:
|
|
* - `Maybe.none()` → `null`
|
|
* - `Maybe.some(value)` → `map(value)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeToNullable(Maybe.none<number>(), String); // null
|
|
* maybeToNullable(Maybe.some(12), String); // "12"
|
|
* maybeToNullable(Maybe.some("abc"), (value) => value.toUpperCase()); // "ABC"
|
|
* ```
|
|
*
|
|
* @typeParam T - Tipo interno.
|
|
* @typeParam R - Tipo de salida.
|
|
* @param maybe - Instancia Maybe.
|
|
* @param map - Transformación aplicada solo en caso `Some`.
|
|
* @returns Valor serializado o `null`.
|
|
*/
|
|
export function maybeToNullable<T, R>(maybe: Maybe<T>, map: (t: T) => R): R | null {
|
|
return serializeMaybe(maybe, null, map);
|
|
}
|
|
|
|
/**
|
|
* Convierte un `Maybe<T>` en `string | null`.
|
|
*
|
|
* Casos:
|
|
* - `Maybe.none()` → `null`
|
|
* - `Maybe.some(value)` sin `map` → `String(value)`
|
|
* - `Maybe.some(value)` con `map` → `String(map(value))`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeToNullableString(Maybe.none<number>()); // null
|
|
* maybeToNullableString(Maybe.some(12)); // "12"
|
|
* maybeToNullableString(Maybe.some(user), (value) => value.name); // "John"
|
|
* ```
|
|
*
|
|
* @typeParam T - Tipo interno.
|
|
* @param maybe - Instancia Maybe.
|
|
* @param map - Transformación opcional.
|
|
* @returns String serializado o `null`.
|
|
*/
|
|
export function maybeToNullableString<T>(maybe: Maybe<T>, map?: (t: T) => string): string | null {
|
|
return serializeMaybe(maybe, null, (value) => (map ? String(map(value)) : String(value)));
|
|
}
|
|
|
|
/**
|
|
* Convierte un `Maybe<T>` en `string`.
|
|
*
|
|
* Casos:
|
|
* - `Maybe.none()` → `""`
|
|
* - `Maybe.some(value)` sin `map` → `String(value)`
|
|
* - `Maybe.some(value)` con `map` → `map(value)`
|
|
*
|
|
* Ejemplos:
|
|
* ```ts
|
|
* maybeToEmptyString(Maybe.none<number>()); // ""
|
|
* maybeToEmptyString(Maybe.some(12)); // "12"
|
|
* maybeToEmptyString(Maybe.some(user), (value) => value.name); // "John"
|
|
* ```
|
|
*
|
|
* @typeParam T - Tipo interno.
|
|
* @param maybe - Instancia Maybe.
|
|
* @param map - Transformación opcional.
|
|
* @returns String serializado; nunca `null`.
|
|
*/
|
|
export function maybeToEmptyString<T>(maybe: Maybe<T>, map?: (t: T) => string): string {
|
|
return serializeMaybe(maybe, "", (value) => (map ? map(value) : String(value)));
|
|
}
|