Skip to content

Commit

Permalink
Merge pull request #195 from 4lessandrodev/fix/194/nullish-result-value
Browse files Browse the repository at this point in the history
Fix/194/nullish result value
  • Loading branch information
4lessandrodev authored Nov 19, 2024
2 parents 0534326 + 7c8688a commit 6a0889b
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 33 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ All notable changes to this project will be documented in this file.

---

### [1.23.5] - 2024-10-18

#### Fix

## Changelog for `1.23.5-beta.0`

### **Changes**
- **Explicit Typing for Failures**: `Result.fail` now explicitly returns `Result<null, ...>`, ensuring that values are always `null` in failure states.
- **New `isNull` Method**: Added to simplify validation of `null` values or failure states, improving readability and type safety.
- **Adjusted Creation Methods**: Methods like `create` and adapters now return `Result<T | null>` where applicable for better consistency and error handling.

### **Impact**
These changes improve type safety, make failure handling more explicit, and encourage clearer checks in code. The updates may require minor adjustments in existing codebases to accommodate the explicit `null` typing in failures. This release is marked as beta for testing purposes.

Feedback is welcome! 🚀

[issue](https://github.com/4lessandrodev/rich-domain/issues/194)

## Released

---
Expand Down
10 changes: 5 additions & 5 deletions lib/core/fail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Result from "./result";
* @argument P generic type for payload.
* @default void as no state.
*/
function Fail(): Result<string, string, {}>;
function Fail(): Result<null, string, {}>;

/**
* @description Create an instance of Result as failure state.
Expand All @@ -33,7 +33,7 @@ import Result from "./result";
* @argument P generic type for payload.
* @default void as no state.
*/
function Fail(): IResult<string, string, {}>;
function Fail(): IResult<null, string, {}>;

/**
* @description Create an instance of Result as failure state.
Expand All @@ -50,7 +50,7 @@ function Fail(): IResult<string, string, {}>;
* @argument P generic type for payload.
* @default void as no state.
*/
function Fail<E, M extends {} = {}, P = void>(error: E extends void ? null : E, metaData?: M): Result<P, E extends void ? string : E, M>;
function Fail<E, M extends {} = {}>(error: E extends void ? null : E, metaData?: M): Result<null, E extends void ? string : E, M>;


/**
Expand All @@ -68,7 +68,7 @@ function Fail(): IResult<string, string, {}>;
* @argument P generic type for payload.
* @default void as no state.
*/
function Fail<E, M extends {} = {}, P = void>(error: E extends void ? null : E, metaData?: M): IResult<P, E extends void ? string : E, M>;
function Fail<E, M extends {} = {}>(error: E extends void ? null : E, metaData?: M): IResult<null, E extends void ? string : E, M>;

/**
* @description Create an instance of Result as failure state.
Expand All @@ -85,7 +85,7 @@ function Fail<E, M extends {} = {}, P = void>(error: E extends void ? null : E,
* @argument P generic type for payload.
* @default void as no state.
*/
function Fail<E = string, M extends {} = {}, P = void>(error?: E extends void ? null : E, metaData?: M): IResult<P, E extends void ? string : E, M> {
function Fail<E = string, M extends {} = {}>(error?: E extends void ? null : E, metaData?: M): IResult<null, E extends void ? string : E, M> {
const _error = (typeof error !== 'undefined' && error !== null) ? error : 'void error. no message!';
return Result.fail(_error as any, metaData);
}
Expand Down
31 changes: 26 additions & 5 deletions lib/core/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class Result<T = void, D = string, M = {}> implements IResult<T, D, M> {
* @returns instance of Result<void>.
*/
public static Ok(): Result<void>;

/**
* @description Create an instance of Result as success state.
* @returns instance of Result<void>.
Expand All @@ -42,7 +42,7 @@ export class Result<T = void, D = string, M = {}> implements IResult<T, D, M> {
* @param metaData as M to state.
* @returns instance of Result.
*/
public static Ok<T, M = {}, D = string>(data: T, metaData?: M): Result<T, D, M>;
public static Ok<T, M = {}, D = string>(data: T, metaData?: M): Result<T, D, M>;

/**
* @description Create an instance of Result as success state with data and metadata to payload.
Expand All @@ -51,7 +51,7 @@ export class Result<T = void, D = string, M = {}> implements IResult<T, D, M> {
* @returns instance of Result.
*/
public static Ok<T, M = {}, D = string>(data: T, metaData?: M): IResult<T, D, M>;

/**
* @description Create an instance of Result as success state with data and metadata to payload.
* @param data as T to payload.
Expand All @@ -70,7 +70,7 @@ export class Result<T = void, D = string, M = {}> implements IResult<T, D, M> {
* @param metaData as M to state.
* @returns instance of Result.
*/
public static fail<D = string, M = {}, T = void>(error?: D, metaData?: M): Result<T, D, M>;
public static fail<D = string, M = {}>(error?: D, metaData?: M): Result<null, D, M>;

/**
* @description Create an instance of Result as failure state with error and metadata to payload.
Expand Down Expand Up @@ -100,7 +100,7 @@ export class Result<T = void, D = string, M = {}> implements IResult<T, D, M> {
*/
public static combine<A = any, B = any, M = any>(results: Array<IResult<any, any, any>>): IResult<A, B, M> {
const iterator = Result.iterate(results);
if (iterator.isEmpty()) return Result.fail('No results provided on combine param' as B) as IResult<A, B, M>;
if (iterator.isEmpty()) return Result.fail('No results provided on combine param' as B) as unknown as IResult<A, B, M>;
while (iterator.hasNext()) {
const currentResult = iterator.next();
if (currentResult.isFail()) return currentResult as IResult<A, B, M>;
Expand Down Expand Up @@ -167,6 +167,27 @@ export class Result<T = void, D = string, M = {}> implements IResult<T, D, M> {
isFail(): boolean {
return this.#isFail;
}
/**
* @description Determines if the result instance contains a `null` value.
* This method is particularly useful when dealing with dynamically typed results,
* allowing developers to validate and refine types based on the state of the result.
*
* @returns {boolean}
* - `true` if the result instance holds a `null` value.
* - `false` otherwise.
*
* @example
* const result = Result.Ok(null);
*
* if (result.isNull()) {
* console.log("The result value is null");
* } else {
* console.log("The result value is not null:", result.value());
* }
*/
isNull(): boolean {
return this.#data === null || this.#isFail;
}

/**
* @description Check if result instance is success.
Expand Down
3 changes: 2 additions & 1 deletion lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface IResult<T, D = string, M = {}> {
value(): T;
error(): D;
isFail(): boolean;
isNull(): boolean;
isOk(): boolean;
metaData(): M;
toObject(): IResultObject<T, D, M>;
Expand Down Expand Up @@ -196,7 +197,7 @@ export interface IPublicHistory<Props> {
export type IPropsValidation<T> = { [P in keyof Required<T>]: (value: T[P]) => boolean };

export interface IAdapter<F, T, E = any, M = any> {
build(target: F): IResult<T, E, M>;
build(target: F): IResult<T | null, E, M>;
}

export interface Adapter<A = any, B = any> {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rich-domain",
"version": "1.23.4",
"version": "1.23.5-beta-0",
"description": "This package provide utils file and interfaces to assistant build a complex application with domain driving design",
"main": "index.js",
"types": "index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion tests/core/adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ describe('adapter v1', () => {
type Err = { err: string; stack?: string };

class CustomAdapter implements IAdapter<In, Out, Err> {
build(target: In): IResult<Out, Err> {
build(target: In): IResult<Out | null, Err> {
if (typeof target.a !== 'number') return Fail({ err: 'target.a is not a number' });
return Ok({ b: target.a.toString() });
}
Expand Down
10 changes: 5 additions & 5 deletions tests/core/aggregate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ describe('aggregate', () => {
return this.validator.number(value).isBetween(0, 130);
}

public static create(props: Props): IResult<ValueObject<Props>> {
public static create(props: Props): IResult<ValueObject<Props> | null> {
if (!this.isValidValue(props.value)) return Result.fail('Invalid value');
return Result.Ok(new AgeVo(props));
}
Expand Down Expand Up @@ -142,14 +142,14 @@ describe('aggregate', () => {
super(props);
}

public static create(props: AggProps): IResult<Aggregate<AggProps>> {
public static create(props: AggProps): Result<Aggregate<AggProps> | null> {
return Result.Ok(new UserAgg(props));
}
}

it('should create a user with success', () => {

const age = AgeVo.create({ value: 21 }).value();
const age = AgeVo.create({ value: 21 }).value() as AgeVo;
const user = UserAgg.create({ age });

expect(user.isOk()).toBeTruthy();
Expand All @@ -158,10 +158,10 @@ describe('aggregate', () => {

it('should get value from age with success', () => {

const age = AgeVo.create({ value: 21 }).value();
const age = AgeVo.create({ value: 21 }).value() as AgeVo;
const user = UserAgg.create({ age }).value();

const result = user
const result = (user as Aggregate<AggProps>)
.get('age')
.get('value');

Expand Down
11 changes: 6 additions & 5 deletions tests/core/entity.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Entity, Id, Ok, Result, ValueObject } from "../../lib/core";
import { Entity, Fail, Id, Ok, Result, ValueObject } from "../../lib/core";
import { Adapter, IResult, UID } from "../../lib/types";

describe("entity", () => {
Expand All @@ -16,15 +16,16 @@ describe("entity", () => {
return value !== undefined;
}

public static create(props: Props): IResult<EntitySample> {
public static create(props: Props): IResult<EntitySample | null> {
if(!props) return Fail('props is required')
return Result.Ok(new EntitySample(props))
}
}

it('should get prototype', () => {
const ent = EntitySample.create({ foo: 'bar' });

ent.value().change('foo', 'changed');
ent.value()?.change('foo', 'changed');
expect(ent.isOk()).toBeTruthy();
});
});
Expand Down Expand Up @@ -630,7 +631,7 @@ describe("entity", () => {
return this.util.string(this.props.foo).removeSpaces();
}

public static create(props: Props): IResult<ValSamp> {
public static create(props: Props): IResult<ValSamp | null> {
const isValid = this.isValidProps(props.foo);
if (!isValid) return Result.fail('Erro');
return Result.Ok(new ValSamp(props))
Expand All @@ -645,7 +646,7 @@ describe("entity", () => {
it('should remove space from value', () => {
const ent = ValSamp.create({ foo: ' Some Value With Spaces ' });
expect(ent.isOk()).toBeTruthy();
expect(ent.value().RemoveSpace()).toBe('SomeValueWithSpaces');
expect(ent.value()?.RemoveSpace()).toBe('SomeValueWithSpaces');
});
});

Expand Down
13 changes: 4 additions & 9 deletions tests/core/fail.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,7 @@ describe('fail', () => {
arg: string;
}

interface Payload {
user: any;
}

const result = Fail<Generic, MetaData, Payload>({ message: 'invalid email' }, { arg: '[email protected]' });
const result = Fail<Generic, MetaData>({ message: 'invalid email' }, { arg: '[email protected]' });
expect(result.isOk()).toBeFalsy();
expect(result.isFail()).toBeTruthy();
expect(result.toObject()).toEqual({
Expand All @@ -116,7 +112,6 @@ describe('fail', () => {
describe('generic types', () => {

type Error = { message: string };
type Payload = { data: { status: number } };
type MetaData = { args: number };

it('should fail generate the same payload as result', () => {
Expand All @@ -125,10 +120,10 @@ describe('fail', () => {
const metaData: MetaData = { args: status };
const error: Error = { message: 'something went wrong!' };

const resultInstance = Result.fail<Error, MetaData, Payload>(error, metaData);
const okInstance = Fail<Error, MetaData, Payload>(error, metaData);
const resultInstance = Result.fail<Error, MetaData>(error, metaData);
const failInstance = Fail<Error, MetaData>(error, metaData);

expect(resultInstance.toObject()).toEqual(okInstance.toObject());
expect(resultInstance.toObject()).toEqual(failInstance.toObject());

});

Expand Down
2 changes: 1 addition & 1 deletion tests/core/value-object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ describe('value-object', () => {
return isValidAge && isValidDate;
}

public static create(props: Props1): IResult<HumanAge> {
public static create(props: Props1): IResult<HumanAge | null> {
if (!HumanAge.isValidProps(props)) return Result.fail('Invalid props');
return Result.Ok(new HumanAge(props));
}
Expand Down

0 comments on commit 6a0889b

Please sign in to comment.