Uecko_ERP/packages/rdx-ddd/src/helpers/normalizers.ts
2026-05-12 20:56:22 +02:00

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)));
}