diff --git a/examples/Examples.Any/Program.cs b/examples/Examples.Any/Program.cs index b1c02dd..bbbf6c8 100644 --- a/examples/Examples.Any/Program.cs +++ b/examples/Examples.Any/Program.cs @@ -150,6 +150,71 @@ Console.WriteLine($"tripleThird.TryAs(): success={t3Try}, value={t3Bool}"); Console.WriteLine(); +// ============================================================================ +// INDEX PROPERTY +// ============================================================================ +Console.WriteLine("--- Index Property ---"); + +Any anyStringIdx = Any.First("hello"); +Any anyIntIdx = Any.Second(42); +Console.WriteLine($"anyString.Index: {anyStringIdx.Index}"); +Console.WriteLine($"anyInt.Index: {anyIntIdx.Index}"); +Console.WriteLine(); + +// ============================================================================ +// ANYACTIONSTATUS (FROM SWITCH RETURN VALUE) +// ============================================================================ +Console.WriteLine("--- AnyActionStatus ---"); + +Any switchTarget = Any.First("world"); +AnyActionStatus switchStatus = switchTarget.Switch( + first: v => Console.WriteLine($" Switched first: {v}"), + second: v => Console.WriteLine($" Switched second: {v}") +); +Console.WriteLine($"AnyActionStatus.Executed: {switchStatus == AnyActionStatus.Executed}"); + +Any emptyAny = default; +AnyActionStatus emptyStatus = emptyAny.Switch( + first: v => Console.WriteLine($" This won't run"), + second: v => Console.WriteLine($" This won't run") +); +Console.WriteLine($"AnyActionStatus.NotExecuted: {emptyStatus == AnyActionStatus.NotExecuted}"); +Console.WriteLine(); + +// ============================================================================ +// ANY — FOUR-TYPE VARIANT (AnyT4) +// ============================================================================ +Console.WriteLine("--- Any (AnyT4) ---"); + +Any quadThird = Any.Third(true); +Console.WriteLine($"AnyT4.IsThird: {quadThird.IsThird}"); +Console.WriteLine($"AnyT4.IsFourth: {quadThird.IsFourth}"); +Console.WriteLine($"AnyT4.Index: {quadThird.Index}"); +Console.WriteLine($"AnyT4.GetThird(): {quadThird.GetThird()}"); + +Any quadFourth = Any.Fourth(Guid.Empty); +Console.WriteLine($"AnyT4 Fourth: IsFirst={quadFourth.IsFirst}, IsFourth={quadFourth.IsFourth}, Index={quadFourth.Index}"); + +(string? q1, int? q2, bool? q3, Guid? q4) = quadFourth.ToTuple(); +Console.WriteLine($"AnyT4 ToTuple: q4={q4}"); + +AnyActionResult quadMatch = quadThird.Match( + first: s => $"string:{s}", + second: i => $"int:{i}", + third: b => $"bool:{b}", + fourth: g => $"guid:{g}" +); +Console.WriteLine($"AnyT4 Match: Result={quadMatch.Result}, Status={quadMatch.Status}"); + +AnyActionStatus quadSwitch = quadFourth.Switch( + first: s => Console.WriteLine($" quad first: {s}"), + second: i => Console.WriteLine($" quad second: {i}"), + third: b => Console.WriteLine($" quad third: {b}"), + fourth: g => Console.WriteLine($" quad fourth: {g}") +); +Console.WriteLine($"AnyT4 Switch status: {quadSwitch}"); +Console.WriteLine(); + Console.WriteLine("========================================"); Console.WriteLine("Demo complete."); Console.WriteLine("========================================"); diff --git a/examples/Examples.Errors/Program.cs b/examples/Examples.Errors/Program.cs index 5408167..b493ae3 100644 --- a/examples/Examples.Errors/Program.cs +++ b/examples/Examples.Errors/Program.cs @@ -140,6 +140,98 @@ } Console.WriteLine(); +// ============================================================================ +// FAILURE / UNEXPECTED / UNAUTHORIZED / FORBIDDEN / EXCEPTION +// ============================================================================ +Console.WriteLine("--- Failure / Unexpected / Unauthorized / Forbidden / Exception ---"); + +Error failure = Error.Failure("Op.Failed", "The operation failed."); +Console.WriteLine($"Failure: Code={failure.Code}, Type={failure.Type}"); + +Error unexpected = Error.Unexpected("Sys.Crash", "An unexpected error occurred."); +Console.WriteLine($"Unexpected: Code={unexpected.Code}, Type={unexpected.Type}"); + +Error unauthorized = Error.Unauthorized("Auth.NoToken", "Access token is missing."); +Console.WriteLine($"Unauthorized: Code={unauthorized.Code}, Type={unauthorized.Type}"); + +Error forbidden = Error.Forbidden("Perm.Denied", "Insufficient permissions."); +Console.WriteLine($"Forbidden: Code={forbidden.Code}, Type={forbidden.Type}"); + +try { throw new InvalidOperationException("Disk full"); } +catch (Exception ex) +{ + Error fromEx = Error.Exception(ex); + Console.WriteLine($"Exception: Code={fromEx.Code}, Description={fromEx.Description}"); + + Error fromExWithCode = Error.Exception("Storage.Full", ex); + Console.WriteLine($"Exception with code: Code={fromExWithCode.Code}"); +} +Console.WriteLine(); + +// ============================================================================ +// CREATE MANY +// ============================================================================ +Console.WriteLine("--- CreateMany ---"); + +Error[] many = Error.CreateMany( + Error.Validation("Name.Empty", "Name is required"), + Error.Validation("Email.Invalid", "Email is invalid"), + Error.Validation("Age.Range", "Age out of range") +); +Console.WriteLine($"CreateMany: {many.Length} errors"); +foreach (Error e in many) + Console.WriteLine($" {e.Code}"); +Console.WriteLine(); + +// ============================================================================ +// SENTINEL VALUES: NOFIRSTERROR / NOERRORS / FALSE +// ============================================================================ +Console.WriteLine("--- Sentinel Values ---"); + +Console.WriteLine($"NoFirstError: Code={Error.NoFirstError.Code}, Type={Error.NoFirstError.Type}"); +Console.WriteLine($"NoErrors: Code={Error.NoErrors.Code}"); +Console.WriteLine($"False: Code={Error.False.Code}, Type={Error.False.Type}"); +Console.WriteLine(); + +// ============================================================================ +// ERRORTYPE ENUM / TOINTTYPE / TOERRORTYPE / TOHTTPSTATUSCODE +// ============================================================================ +Console.WriteLine("--- ErrorType Enum / ToIntType / ToErrorType ---"); + +ErrorType[] types = +{ + ErrorType.Failure, ErrorType.Validation, ErrorType.NotFound, + ErrorType.Unauthorized, ErrorType.Forbidden, ErrorType.Conflict, ErrorType.Unexpected +}; + +foreach (ErrorType t in types) +{ + int intType = t.ToIntType(); + int httpCode = t.ToHttpStatusCode(); + Console.WriteLine($" {t}: numeric={intType}, http={httpCode}"); +} + +ErrorType fromHttp404 = 404.ToErrorType(); +Console.WriteLine($"404.ToErrorType(): {fromHttp404}"); + +ErrorType fromHttp400 = 400.ToErrorType(); +Console.WriteLine($"400.ToErrorType(): {fromHttp400}"); +Console.WriteLine(); + +// ============================================================================ +// IERROR INTERFACE +// ============================================================================ +Console.WriteLine("--- IError Interface ---"); + +Error sample = Error.NotFound("Sample.Missing", "Sample not found"); +IError asInterface = sample; +Console.WriteLine($"IError.Code={asInterface.Code}"); +Console.WriteLine($"IError.Description={asInterface.Description}"); +Console.WriteLine($"IError.Type={asInterface.Type}"); +Console.WriteLine($"IError.NumericType={asInterface.NumericType}"); +Console.WriteLine($"IError.Metadata={asInterface.Metadata}"); +Console.WriteLine(); + Console.WriteLine("========================================"); Console.WriteLine("Demo complete."); Console.WriteLine("========================================"); diff --git a/examples/Examples.Maybe/Program.cs b/examples/Examples.Maybe/Program.cs index 1b17166..72a5157 100644 --- a/examples/Examples.Maybe/Program.cs +++ b/examples/Examples.Maybe/Program.cs @@ -164,6 +164,154 @@ from b in 3.AsMaybe() Console.WriteLine($"Choose: [{string.Join(", ", chosen)}]"); Console.WriteLine(); +// ============================================================================ +// BIND +// ============================================================================ +Console.WriteLine("--- Bind ---"); + +Maybe bindName = "alice".AsMaybe(); +Maybe nameLength = bindName.Bind(n => n.Length > 0 ? Maybe.From(n.Length) : Maybe.None); +Console.WriteLine($"Bind Some: {nameLength.Value}"); + +Maybe emptyBound = Maybe.None.Bind(n => Maybe.From(n.Length)); +Console.WriteLine($"Bind None: HasValue={emptyBound.HasValue}"); +Console.WriteLine(); + +// ============================================================================ +// GET VALUE OR DEFAULT / GET VALUE OR THROW +// ============================================================================ +Console.WriteLine("--- GetValueOrDefault / GetValueOrThrow ---"); + +Maybe someInt = 42.AsMaybe(); +Maybe noneInt = Maybe.None; + +int gvod = noneInt.GetValueOrDefault(-1); +Console.WriteLine($"GetValueOrDefault (None): {gvod}"); + +int gvod2 = noneInt.GetValueOrDefault(() => 99); +Console.WriteLine($"GetValueOrDefault factory (None): {gvod2}"); + +int gvod3 = someInt.GetValueOrDefault(-1); +Console.WriteLine($"GetValueOrDefault (Some): {gvod3}"); + +int gvot = someInt.GetValueOrThrow(); +Console.WriteLine($"GetValueOrThrow (Some): {gvot}"); + +try { noneInt.GetValueOrThrow("No value!"); } +catch (InvalidOperationException ex) { Console.WriteLine($"GetValueOrThrow (None) threw: {ex.Message}"); } +Console.WriteLine(); + +// ============================================================================ +// TRY FIRST / TRY LAST / TRY FIND +// ============================================================================ +Console.WriteLine("--- TryFirst / TryLast / TryFind ---"); + +int[] nums = { 3, 7, 2, 9, 1 }; +Maybe firstVal = nums.TryFirst(); +Console.WriteLine($"TryFirst: {firstVal.Value}"); + +Maybe firstMatch = nums.TryFirst(x => x > 5); +Console.WriteLine($"TryFirst (predicate): {firstMatch.Value}"); + +Maybe noMatch = nums.TryFirst(x => x > 100); +Console.WriteLine($"TryFirst (no match): HasValue={noMatch.HasValue}"); + +Maybe lastVal = nums.TryLast(); +Console.WriteLine($"TryLast: {lastVal.Value}"); + +Maybe lastMatch = nums.TryLast(x => x > 5); +Console.WriteLine($"TryLast (predicate): {lastMatch.Value}"); + +IReadOnlyDictionary dict = new Dictionary { { "a", 1 }, { "b", 2 } }; +Maybe found = dict.TryFind("b"); +Console.WriteLine($"TryFind found: {found.Value}"); +Maybe notFoundVal = dict.TryFind("z"); +Console.WriteLine($"TryFind not found: HasValue={notFoundVal.HasValue}"); +Console.WriteLine(); + +// ============================================================================ +// AS NULLABLE +// ============================================================================ +Console.WriteLine("--- AsNullable ---"); + +int? nullable = someInt.AsNullable(); +Console.WriteLine($"AsNullable (Some): {nullable}"); + +int? nullableNone = noneInt.AsNullable(); +Console.WriteLine($"AsNullable (None): {nullableNone.HasValue}"); +Console.WriteLine(); + +// ============================================================================ +// EXECUTE / EXECUTE NO VALUE +// ============================================================================ +Console.WriteLine("--- Execute / ExecuteNoValue ---"); + +someInt.Execute(v => Console.WriteLine($" Execute (Some): {v}")); +noneInt.Execute(v => Console.WriteLine(" This won't print")); + +noneInt.ExecuteNoValue(() => Console.WriteLine(" ExecuteNoValue (None) fired")); +someInt.ExecuteNoValue(() => Console.WriteLine(" This won't print")); +Console.WriteLine(); + +// ============================================================================ +// FLATTEN +// ============================================================================ +Console.WriteLine("--- Flatten ---"); + +Maybe> nested = Maybe>.From(42.AsMaybe()); +Maybe flat = nested.Flatten(); +Console.WriteLine($"Flatten: {flat.Value}"); + +Maybe> nestedNone = Maybe>.None; +Maybe flatNone = nestedNone.Flatten(); +Console.WriteLine($"Flatten None: HasValue={flatNone.HasValue}"); +Console.WriteLine(); + +// ============================================================================ +// DECONSTRUCT +// ============================================================================ +Console.WriteLine("--- Deconstruct ---"); + +(bool hasValue, int? val) = someInt; +Console.WriteLine($"Deconstruct Some: hasValue={hasValue}, value={val}"); + +(bool hasValueNone, int? valNone) = noneInt; +Console.WriteLine($"Deconstruct None: hasValue={hasValueNone}, value={valNone}"); +Console.WriteLine(); + +// ============================================================================ +// TO MAYBE UNIT RESULT +// ============================================================================ +Console.WriteLine("--- ToMaybeUnitResult ---"); + +Result unitOk = someInt.ToMaybeUnitResult(); +Console.WriteLine($"ToMaybeUnitResult (Some): IsSuccess={unitOk.IsSuccess}"); + +Result unitNone = noneInt.ToMaybeUnitResult(Error.NotFound("Val", "No value")); +Console.WriteLine($"ToMaybeUnitResult (None): IsFailure={unitNone.IsFailure}, Code={unitNone.FirstError.Code}"); +Console.WriteLine(); + +// ============================================================================ +// COLLECTION: SEQUENCE / TRAVERSE / PARTITION +// ============================================================================ +Console.WriteLine("--- Collection: Sequence / Traverse / Partition ---"); + +List> allSome = new() { 1.AsMaybe(), 2.AsMaybe(), 3.AsMaybe() }; +Maybe sequenced = allSome.Sequence(); +Console.WriteLine($"Sequence (all Some): [{string.Join(", ", sequenced.Value)}]"); + +List> withNone = new() { 1.AsMaybe(), Maybe.None, 3.AsMaybe() }; +Maybe sequencedNone = withNone.Sequence(); +Console.WriteLine($"Sequence (has None): HasValue={sequencedNone.HasValue}"); + +Maybe traversed = new[] { "1", "2", "3" } + .Traverse(s => int.TryParse(s, out int n) ? Maybe.From(n) : Maybe.None); +Console.WriteLine($"Traverse: [{string.Join(", ", traversed.Value)}]"); + +(int[] values, int noneCount) = withNone.Partition(); +Console.WriteLine($"Partition: {values.Length} values, {noneCount} None(s)"); +Console.WriteLine(); + Console.WriteLine("========================================"); Console.WriteLine("Demo complete."); Console.WriteLine("========================================"); diff --git a/examples/Examples.Results/Program.cs b/examples/Examples.Results/Program.cs index 9891e0b..dc72428 100644 --- a/examples/Examples.Results/Program.cs +++ b/examples/Examples.Results/Program.cs @@ -218,6 +218,229 @@ Result GetUserName(int id) Console.WriteLine(result); Console.WriteLine(); +// ============================================================================ +// MAP (TRANSFORM VALUE) +// ============================================================================ +Console.WriteLine("--- Map ---"); + +Result doubled = Result.Success(21).Map(v => v * 2); +Console.WriteLine($"Map: {doubled.Value}"); + +Result asString = Result.Success(42).Map(v => $"Value is {v}"); +Console.WriteLine($"Map to string: {asString.Value}"); + +Result mapFailed = Result.Failure(Error.Validation("X", "Bad")).Map(v => $"Value is {v}"); +Console.WriteLine($"Map on failure propagates: {mapFailed.FirstError.Code}"); +Console.WriteLine(); + +// ============================================================================ +// BIND (CHAIN WITH DIFFERENT RETURN TYPE) +// ============================================================================ +Console.WriteLine("--- Bind ---"); + +Result parsed = Result.Success("123") + .Bind(s => int.TryParse(s, out int n) ? Result.Success(n) : Result.Failure(Error.Validation("Parse", "Not a number"))); +Console.WriteLine($"Bind parse: {parsed.Value}"); + +Result bindUnit = Result.Success("alice") + .Bind(s => s.Length > 0 ? Result.Success() : Result.Failure(Error.Validation("Name", "Empty"))); +Console.WriteLine($"Bind to unit: {bindUnit.IsSuccess}"); +Console.WriteLine(); + +// ============================================================================ +// TAP (SIDE EFFECT ON SUCCESS) +// ============================================================================ +Console.WriteLine("--- Tap ---"); + +Result.Success(10) + .Tap(v => Console.WriteLine($" Tap value: {v}")) + .Tap(() => Console.WriteLine(" Tap no-arg")) + .Tap(true, v => Console.WriteLine($" Tap conditional true: {v}")) + .Tap(false, v => Console.WriteLine(" This won't print")) + .Tap(() => 5 > 3, v => Console.WriteLine($" Tap predicate: {v}")); + +Result.Failure(Error.NotFound("X", "Missing")) + .Tap(v => Console.WriteLine(" This won't print (failure)")); +Console.WriteLine(); + +// ============================================================================ +// ELSE (FALLBACK ON FAILURE) +// ============================================================================ +Console.WriteLine("--- Else ---"); + +Result elseValue = Result.Failure(Error.NotFound("X", "Missing")).Else(42); +Console.WriteLine($"Else with value: {elseValue.Value}"); + +Result elseFromErrors = Result.Failure(Error.Validation("X", "Bad")).Else(errors => -1); +Console.WriteLine($"Else from errors: {elseFromErrors.Value}"); + +Result elseReplaceError = Result.Failure(Error.Validation("Old", "Old error")) + .Else(Error.Conflict("New", "Replaced error")); +Console.WriteLine($"Else replace error: {elseReplaceError.FirstError.Code}"); + +Result elseNoChange = Result.Success(99).Else(0); +Console.WriteLine($"Else on success (no change): {elseNoChange.Value}"); +Console.WriteLine(); + +// ============================================================================ +// FINALLY (ALWAYS EXECUTES) +// ============================================================================ +Console.WriteLine("--- Finally ---"); + +string finallySuccess = Result.Success(7).Finally(r => r.IsSuccess ? $"Ok: {r.Value}" : "Fail"); +Console.WriteLine($"Finally success: {finallySuccess}"); + +string finallyFail = Result.Failure(Error.NotFound("X", "Missing")) + .Finally(r => r.IsSuccess ? "Ok" : $"Fail: {r.FirstError.Code}"); +Console.WriteLine($"Finally failure: {finallyFail}"); +Console.WriteLine(); + +// ============================================================================ +// GET VALUE OR DEFAULT / GET VALUE OR THROW +// ============================================================================ +Console.WriteLine("--- GetValueOrDefault / GetValueOrThrow ---"); + +int defaulted = Result.Failure(Error.NotFound("X", "Missing")).GetValueOrDefault(-1); +Console.WriteLine($"GetValueOrDefault: {defaulted}"); + +int fromFactory = Result.Failure(Error.NotFound("X", "Missing")).GetValueOrDefault(() => 99); +Console.WriteLine($"GetValueOrDefault factory: {fromFactory}"); + +int found = Result.Success(42).GetValueOrDefault(-1); +Console.WriteLine($"GetValueOrDefault success: {found}"); + +try +{ + Result.Failure(Error.NotFound("X", "Missing")).GetValueOrThrow("Custom message"); +} +catch (InvalidOperationException ex) +{ + Console.WriteLine($"GetValueOrThrow threw: {ex.Message}"); +} +Console.WriteLine(); + +// ============================================================================ +// UNWRAP / UNWRAP OR DEFAULT +// ============================================================================ +Console.WriteLine("--- Unwrap / UnwrapOrDefault ---"); + +int unwrapped = Result.Success(55).Unwrap(); +Console.WriteLine($"Unwrap success: {unwrapped}"); + +int unwrapOr = Result.Failure(Error.NotFound("X", "Missing")).UnwrapOrDefault(-999); +Console.WriteLine($"UnwrapOrDefault: {unwrapOr}"); + +try +{ + Result.Failure(Error.Validation("X", "Bad")).Unwrap(); +} +catch (ResultUnwrapException ex) +{ + Console.WriteLine($"Unwrap threw ResultUnwrapException: {ex.Errors.Length} error(s)"); +} +Console.WriteLine(); + +// ============================================================================ +// RECOVER / RECOVER FIRST +// ============================================================================ +Console.WriteLine("--- Recover / RecoverFirst ---"); + +Result recovered = Result.Failure(Error.NotFound("Cache.Miss", "Not in cache")) + .Recover(ErrorType.NotFound, _ => Result.Success(0)); +Console.WriteLine($"Recover NotFound: {recovered.Value}"); + +Result notRecovered = Result.Failure(Error.Validation("X", "Bad")) + .Recover(ErrorType.NotFound, _ => Result.Success(0)); +Console.WriteLine($"Recover (wrong type, unchanged): {notRecovered.IsFailure}"); + +Result recoveredFirst = Result.Failure(Error.Unauthorized("Auth", "No token")) + .RecoverFirst(ErrorType.Unauthorized, _ => Result.Success(-1)); +Console.WriteLine($"RecoverFirst Unauthorized: {recoveredFirst.Value}"); +Console.WriteLine(); + +// ============================================================================ +// BINDIF (CONDITIONAL BIND) +// ============================================================================ +Console.WriteLine("--- BindIf ---"); + +bool isAdmin = true; +Result role = Result.Success("alice") + .BindIf(isAdmin, name => Result.Success($"{name}:admin")) + .BindIf(!isAdmin, name => Result.Success($"{name}:guest")); +Console.WriteLine($"BindIf (bool): {role.Value}"); + +Result bindIfPredicate = Result.Success(10) + .BindIf(v => v > 5, v => Result.Success(v * 2)); +Console.WriteLine($"BindIf predicate: {bindIfPredicate.Value}"); +Console.WriteLine(); + +// ============================================================================ +// FAILIF (FAIL WHEN CONDITION IS MET) +// ============================================================================ +Console.WriteLine("--- FailIf ---"); + +Result failIfNeg = Result.Success(-5) + .FailIf(v => v < 0, Error.Validation("Range", "Must be non-negative")); +Console.WriteLine($"FailIf (met): {failIfNeg.FirstError.Code}"); + +Result failIfOk = Result.Success(5) + .FailIf(v => v < 0, Error.Validation("Range", "Must be non-negative")); +Console.WriteLine($"FailIf (not met): {failIfOk.Value}"); +Console.WriteLine(); + +// ============================================================================ +// TRYCATCH (EXCEPTION-SAFE BIND) +// ============================================================================ +Console.WriteLine("--- TryCatch ---"); + +Result tryCatchOk = Result.Success("42") + .TryCatch(s => Result.Success(int.Parse(s))); +Console.WriteLine($"TryCatch success: {tryCatchOk.Value}"); + +Result tryCatchFail = Result.Success("not-a-number") + .TryCatch(s => Result.Success(int.Parse(s))); +Console.WriteLine($"TryCatch caught exception: IsFailure={tryCatchFail.IsFailure}"); +Console.WriteLine(); + +// ============================================================================ +// COLLECTION: SEQUENCE / TRAVERSE / PARTITION / FIRSTFAILUREORSUCCESSES +// ============================================================================ +Console.WriteLine("--- Collection Extensions ---"); + +List> allOkResults = new() +{ + Result.Success(1), Result.Success(2), Result.Success(3) +}; +Result sequenced = allOkResults.Sequence(); +Console.WriteLine($"Sequence (all ok): [{string.Join(", ", sequenced.Value)}]"); + +List> withFailure = new() +{ + Result.Success(1), + Result.Failure(Error.Validation("X", "Bad")), + Result.Success(3) +}; +Result sequencedFail = withFailure.Sequence(); +Console.WriteLine($"Sequence (has failure): IsFailure={sequencedFail.IsFailure}, errors={sequencedFail.Errors.Length}"); + +Result traversed = new[] { "1", "2", "3" } + .Traverse(s => int.TryParse(s, out int n) ? Result.Success(n) : Result.Failure(Error.Validation("Parse", "Bad"))); +Console.WriteLine($"Traverse: [{string.Join(", ", traversed.Value)}]"); + +(int[] successes, Error[] errs) = withFailure.Partition(); +Console.WriteLine($"Partition: {successes.Length} successes, {errs.Length} errors"); + +List mixedResults = new() +{ + Result.Success(), Result.Failure(Error.NotFound("X", "Missing")), Result.Success() +}; +Result firstFail = mixedResults.FirstFailureOrSuccesses(); +Console.WriteLine($"FirstFailureOrSuccesses: {firstFail.FirstError.Code}"); + +Result allOk = new List { Result.Success(), Result.Success() }.FirstFailureOrSuccesses(); +Console.WriteLine($"FirstFailureOrSuccesses (all ok): {allOk.IsSuccess}"); +Console.WriteLine(); + Console.WriteLine("========================================"); Console.WriteLine("Demo complete."); Console.WriteLine("========================================"); diff --git a/examples/Examples.Rules/Program.cs b/examples/Examples.Rules/Program.cs index 25f8d53..28ccfc9 100644 --- a/examples/Examples.Rules/Program.cs +++ b/examples/Examples.Rules/Program.cs @@ -170,6 +170,43 @@ ); Console.WriteLine(); +// ============================================================================ +// NEXT COMBINATOR (PIPELINE CHAIN) +// ============================================================================ +Console.WriteLine("--- Next Combinator ---"); + +IRule mustBePositive = ((Func)(x => + x > 0 ? Result.Success() : Result.Failure(Error.Validation("Positive", "Must be positive")))).ToRule(); + +IRule mustBeEven = ((Func)(x => + x % 2 == 0 ? Result.Success() : Result.Failure(Error.Validation("Even", "Must be even")))).ToRule(); + +IRule mustBeLessThan100 = ((Func)(x => + x < 100 ? Result.Success() : Result.Failure(Error.Validation("Range", "Must be less than 100")))).ToRule(); + +IRuleBase chain = mustBePositive.Next(mustBeEven).Next(mustBeLessThan100); + +RuleEngine.Evaluate(chain, 4).Switch( + onSuccess: () => Console.WriteLine("4: passes all chained rules"), + onFailure: errors => Console.WriteLine($"4 failed: {errors[0].Description}") +); + +RuleEngine.Evaluate(chain, -2).Switch( + onSuccess: () => Console.WriteLine("-2 passed"), + onFailure: errors => Console.WriteLine($"-2 failed at first rule: {errors[0].Code}") +); + +RuleEngine.Evaluate(chain, 3).Switch( + onSuccess: () => Console.WriteLine("3 passed"), + onFailure: errors => Console.WriteLine($"3 (odd) failed: {errors[0].Code}") +); + +RuleEngine.Evaluate(chain, 200).Switch( + onSuccess: () => Console.WriteLine("200 passed"), + onFailure: errors => Console.WriteLine($"200 (>=100) failed: {errors[0].Code}") +); +Console.WriteLine(); + Console.WriteLine("========================================"); Console.WriteLine("Demo complete."); Console.WriteLine("========================================"); diff --git a/examples/Examples.Validation/Program.cs b/examples/Examples.Validation/Program.cs index aacf0ae..63602b1 100644 --- a/examples/Examples.Validation/Program.cs +++ b/examples/Examples.Validation/Program.cs @@ -245,6 +245,100 @@ }); Console.WriteLine(); +// ============================================================================ +// 11. ADVANCED COMPARABLE VALIDATORS +// ============================================================================ +Console.WriteLine("--- 11. LessThan / LessThanOrEqualTo / ExclusiveBetween / InclusiveBetween / Equal / NotEqual ---"); + +Result ltResult = await Validator.ValidateAsync( + new ProductModel("SKU-1", 150m, 5, null), + (m, rules) => rules.For(() => m.Price).LessThan(100m)); +Console.WriteLine($"Price 150 LessThan(100): IsFailure={ltResult.IsFailure}"); + +Result lteResult = await Validator.ValidateAsync( + new ProductModel("SKU-1", 100m, 5, null), + (m, rules) => rules.For(() => m.Price).LessThanOrEqualTo(100m)); +Console.WriteLine($"Price 100 LessThanOrEqualTo(100): IsSuccess={lteResult.IsSuccess}"); + +Result excResult = await Validator.ValidateAsync( + new ProductModel("SKU-1", 10m, 5, null), + (m, rules) => rules.For(() => m.Price).ExclusiveBetween(0m, 100m)); +Console.WriteLine($"Price 10 ExclusiveBetween(0,100): IsSuccess={excResult.IsSuccess}"); + +Result incResult = await Validator.ValidateAsync( + new ProductModel("SKU-1", 100m, 5, null), + (m, rules) => rules.For(() => m.Price).InclusiveBetween(0m, 100m)); +Console.WriteLine($"Price 100 InclusiveBetween(0,100): IsSuccess={incResult.IsSuccess}"); + +Result eqResult = await Validator.ValidateAsync( + new ProductModel("EXPECTED-SKU", 10m, 5, null), + (m, rules) => rules.For(() => m.Sku).Equal("EXPECTED-SKU")); +Console.WriteLine($"Sku Equal('EXPECTED-SKU'): IsSuccess={eqResult.IsSuccess}"); + +Result neResult = await Validator.ValidateAsync( + new ProductModel("RESERVED", 10m, 5, null), + (m, rules) => rules.For(() => m.Sku).NotEqual("RESERVED")); +Console.WriteLine($"Sku NotEqual('RESERVED'): IsFailure={neResult.IsFailure}"); + +Console.WriteLine(); + +// ============================================================================ +// 12. STRING VALIDATORS (StartsWith, EndsWith, NotNull) +// ============================================================================ +Console.WriteLine("--- 12. StartsWith / EndsWith / NotNull ---"); + +Result swResult = await Validator.ValidateAsync( + new CreateUserCommand("user@example.com", "Dr. Smith", 30, "P@ssw0rd!"), + (m, rules) => rules.For(() => m.Name).StartsWith("Dr.")); +Console.WriteLine($"Name StartsWith('Dr.'): IsSuccess={swResult.IsSuccess}"); + +Result ewResult = await Validator.ValidateAsync( + new CreateUserCommand("user@example.com", "Smith Jr.", 30, "P@ssw0rd!"), + (m, rules) => rules.For(() => m.Name).EndsWith("Jr.")); +Console.WriteLine($"Name EndsWith('Jr.'): IsSuccess={ewResult.IsSuccess}"); + +Result notNullResult = await Validator.ValidateAsync( + new CreateUserCommand("user@example.com", null, 30, "P@ssw0rd!"), + (m, rules) => rules.For(() => m.Name).NotNull()); +Console.WriteLine($"Name=null NotNull: IsFailure={notNullResult.IsFailure}"); + +Console.WriteLine(); + +// ============================================================================ +// 13. VALIDATEWITH (RAILWAY BINDING) +// ============================================================================ +Console.WriteLine("--- 13. ValidateWith / ValidateWithAsync ---"); + +Result invalidPipeline = Result.Success(new CreateUserCommand("bad-email", "Alice", 30, "P@ssw0rd!")) + .ValidateWith((m, rules) => + { + rules.For(() => m.Email).NotEmpty().EmailAddress(); + rules.For(() => m.Name).NotEmpty().MinLength(2); + }); + +invalidPipeline.Switch( + onSuccess: v => Console.WriteLine($"ValidateWith success: {v.Email}"), + onError: errors => + { + Console.WriteLine($"ValidateWith failed with {errors.Length} error(s):"); + foreach (var e in errors) + Console.WriteLine($" [{e.Code}] {e.Description}"); + }); + +Result validPipeline = Result.Success(new CreateUserCommand("alice@example.com", "Alice", 30, "P@ssw0rd!")) + .ValidateWith((m, rules) => + { + rules.For(() => m.Email).NotEmpty().EmailAddress(); + rules.For(() => m.Name).NotEmpty().MinLength(2); + }); +Console.WriteLine($"ValidateWith (valid): IsSuccess={validPipeline.IsSuccess}"); + +Result alreadyFailed = Result.Failure(CSharpEssentials.Errors.Error.Validation("Pre", "Already failed")) + .ValidateWith((m, rules) => rules.For(() => m.Email).NotEmpty()); +Console.WriteLine($"ValidateWith short-circuits on pre-existing failure: IsFailure={alreadyFailed.IsFailure}"); + +Console.WriteLine(); + Console.WriteLine("========================================"); Console.WriteLine("Demo complete."); Console.WriteLine("========================================");