Skip to content

Commit

Permalink
fix: improve result nullish type #194
Browse files Browse the repository at this point in the history
  • Loading branch information
4lessandrodev committed Nov 19, 2024
1 parent 0534326 commit b640159
Show file tree
Hide file tree
Showing 11 changed files with 371 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
302 changes: 302 additions & 0 deletions changes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
diff --git a/lib/core/fail.ts b/lib/core/fail.ts
index 8f0739d..239c95e 100644
--- a/lib/core/fail.ts
+++ b/lib/core/fail.ts
@@ -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.
@@ -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.
@@ -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>;


/**
@@ -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.
@@ -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);
}
diff --git a/lib/core/result.ts b/lib/core/result.ts
index 0e26f83..f65011d 100644
--- a/lib/core/result.ts
+++ b/lib/core/result.ts
@@ -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>.
@@ -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.
@@ -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.
@@ -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.
@@ -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>;
@@ -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.
diff --git a/lib/types.ts b/lib/types.ts
index e3af3c0..59989ba 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -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>;
@@ -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> {
diff --git a/tests/core/adapter.spec.ts b/tests/core/adapter.spec.ts
index 1fc9f5d..4d9e470 100644
--- a/tests/core/adapter.spec.ts
+++ b/tests/core/adapter.spec.ts
@@ -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() });
}
diff --git a/tests/core/aggregate.spec.ts b/tests/core/aggregate.spec.ts
index 4fffe18..591f627 100644
--- a/tests/core/aggregate.spec.ts
+++ b/tests/core/aggregate.spec.ts
@@ -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));
}
@@ -142,14 +142,14 @@ describe('aggregate', () => {
super(props);
}

- public static create(props: AggProps): IResult<Aggregate<AggProps>> {
+ public static create(props: AggProps): IResult<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();
@@ -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');

diff --git a/tests/core/entity.spec.ts b/tests/core/entity.spec.ts
index 2b49d7b..7b34e15 100644
--- a/tests/core/entity.spec.ts
+++ b/tests/core/entity.spec.ts
@@ -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", () => {
@@ -16,7 +16,8 @@ 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))
}
}
@@ -24,7 +25,7 @@ describe("entity", () => {
it('should get prototype', () => {
const ent = EntitySample.create({ foo: 'bar' });

- ent.value().change('foo', 'changed');
+ ent.value()?.change('foo', 'changed');
expect(ent.isOk()).toBeTruthy();
});
});
@@ -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))
@@ -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');
});
});

diff --git a/tests/core/fail.spec.ts b/tests/core/fail.spec.ts
index 97f79f3..9adf361 100644
--- a/tests/core/fail.spec.ts
+++ b/tests/core/fail.spec.ts
@@ -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({
@@ -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', () => {
@@ -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());

});

diff --git a/tests/core/value-object.spec.ts b/tests/core/value-object.spec.ts
index 1add011..5530694 100644
--- a/tests/core/value-object.spec.ts
+++ b/tests/core/value-object.spec.ts
@@ -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));
}
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
Loading

0 comments on commit b640159

Please sign in to comment.