Skip to content

Commit c36f97f

Browse files
authored
Merge pull request #31 from I-RzR-I/feature/NameSpaceConceptChange
name space and concept change
2 parents 11956a2 + dbb41bc commit c36f97f

86 files changed

Lines changed: 3677 additions & 509 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ For more flexible and intuitive use, in solution persist extension method like f
1515

1616
For more efficiently using, when in some cases when you may need to execute some custom actions/functions after a successful or failed execution request. In that case was added extension methods (`ActionOnSuccess`, `ActionOnFailure`, `ActionOn`, `ExecuteAction`, `FunctionOnSuccess`, `FunctionOnFailure`, `FunctionOn`, `ExecuteFunction`) which allow you to execute this action/function like insert log when execution has a status equal to failure.
1717

18+
> **Note:** As of the latest version, the legacy `ActionOn*` and `Func<Task<TResult>>`-taking `Function*` overloads are marked `[Obsolete]`. Prefer the modern combinators below — they return values, fail fast on null arguments, and use `ConfigureAwait(false)`:
19+
>
20+
> | Replace | With |
21+
> |---|---|
22+
> | `ActionOnSuccess` (side-effect) | `Result<T>.Tap(Action<T>)` |
23+
> | `ActionOnFailure` / `ActionOn` | `Match(onSuccess, onFailure)` |
24+
> | `FunctionOnSuccess(... Task<TResult>)` | `FunctionOnSuccessAsync` (in `RzR.ResultMessage.Extensions.Result.Functions.FunctionExtensionsAsync`) |
25+
>
26+
> The new monadic / async combinators are: `Map`, `Bind`, `Match`, `Tap`, `MapAsync`, `BindAsync`, `TapAsync`, `MatchAsync`, plus fluent `Validate` / `Ensure` validation aggregation. See [USING](docs/usage.md) for details.
27+
1828
No additional components or packs are required for use. So, it only needs to be added/installed in the project and can be used instantly.
1929

2030
**In case you wish to use it in your project, u can install the package from <a href="https://www.nuget.org/packages/AggregatedGenericResultMessage" target="_blank">nuget.org</a>** or specify what version you want:
@@ -23,5 +33,6 @@ No additional components or packs are required for use. So, it only needs to be
2333
2434
## Content
2535
1. [USING](docs/usage.md)
36+
1. [MIGRATION GUIDE](docs/migration-guide.md)
2637
1. [CHANGELOG](docs/CHANGELOG.md)
2738
1. [BRANCH-GUIDE](docs/branch-guide.md)

docs/CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
### **v2.0.0.4127** [[RzR](mailto:108324929+I-RzR-I@users.noreply.github.com)] 22-04-2026
2+
> **Major release — contains breaking changes.** See [migration-guide.md](migration-guide.md) for full before/after porting steps.
3+
4+
**Breaking changes**
5+
* [DEV] - (RzR) -> Change namespace from `AggregatedGenericResultMessage` to `RzR.ResultMessage`.
6+
* [FIX] - (RzR) -> Implicit `Exception -> Result` / `Result<T>` operators now throw `ArgumentNullException` on `null` (was: silent empty failure).
7+
* [FIX] - (RzR) -> `Result<T>(Exception)` constructor now adds **one** `MessageType.Exception` message (was: two: empty info + trace).
8+
* [FIX] - (RzR) -> `JoinErrors(IEnumerable<Result>)` now derives `IsSuccess` from the joined collection (was: inherited from caller).
9+
* [FIX] - (RzR) -> `Success<T>(T, params RelatedObjectModel[])` no longer adds a ghost `MessageType.None` message when `relatedObjects` is empty/null.
10+
* [DEV] - (RzR) -> `ExceptionHelper.PreserveStackTrace` signature changed: `void` -> `ExceptionDispatchInfo`
11+
* [DEV] - (RzR) -> `GetFirstMessage` / `GetFirstError` now fall back to the first exception message when no plain message exists (was: empty string).
12+
13+
**Obsoletions** (compile warnings; will fail with `TreatWarningsAsErrors`)
14+
* [DEV] - (RzR) -> `Result<T>.Instance` -> use `Result<T>.Create()`.
15+
* [DEV] - (RzR) -> `ActionOnSuccess` / `ActionOnFailure` / `ActionOn` (4 overloads) -> use `Tap` / `Match`.
16+
* [DEV] - (RzR) -> All 7 `FunctionOn*` / `ExecuteFunction` overloads taking `Func<Task<TResult>>` -> use `FunctionExtensionsAsync.*Async` (avoids sync-over-async deadlock).
17+
18+
---
19+
20+
* [DEV] - (RzR) -> `Map<TOut>(Func<T,TOut>)`, `Bind<TOut>(Func<T,Result<TOut>>)`, `Match<TOut>(Func<T,TOut>, Func<Result<T>,TOut>)`, `Tap(Action<T>)`.
21+
* [DEV] - (RzR) -> `MapAsync`, `BindAsync`, `TapAsync`, `MatchAsync`, overloads on both `Result<T>` and `Task<Result<T>>`, accepting sync and async delegates. All `ConfigureAwait(false)`.
22+
* [DEV] - (RzR) -> `Tap(this Task<Result<T>>, Action<T>)` for sync side-effect on awaited results.
23+
* [DEV] - (RzR) -> New static class `FunctionExtensionsAsync` exposing `FunctionOnSuccessAsync`, `FunctionOnFailureAsync`, `FunctionOnAsync` (4 overloads), `ExecuteFunctionAsync`.
24+
* [DEV] - (RzR) -> `Validate(Func<T,bool>, error)`- accumulates every violation in one chain pass; overloads for `(predicate, key, error)` and `(predicate, MessageDataModel)`.
25+
* [DEV] - (RzR) -> `Ensure(Func<T,bool>, error)` — short-circuits once `IsSuccess == false`.
26+
* [DEV] - (RzR) -> `ValidateAsync` — async predicate on both `Result<T>` and `Task<Result<T>>`.
27+
* [DEV] - (RzR) -> Match<TOut>(Func<IResult,TOut>, Func<IResult,TOut>)` and parameterless / `Action`-based overloads.
28+
* [DEV] - (RzR) -> `MatchAsync<TOut>(Func<IResult,Task<TOut>>, Func<IResult,Task<TOut>>)`.
29+
* [FIX] - (RzR) -> `RelatedObjectModel.ToString()` is now null-safe when `InDataSourceNames` is `null`
30+
131
### **v1.4.1.8497** [[RzR](mailto:108324929+I-RzR-I@users.noreply.github.com)] 24-02-2026
232
* [f47b134] (RzR) -> Auto commit uncommited files
333
* [6a0d48a] (RzR) -> Fix the `IsFailure` flag value.

docs/migration-guide.md

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# Migration guide — next major version (1.4.1.* -> 2.0.0.*)
2+
3+
This guide collects every source- or behavior-breaking change introduced in the
4+
upcoming major release of `AggregatedGenericResultMessage`, plus the new APIs
5+
that replace the obsolete ones. Each item shows a **before / after** snippet
6+
so you can mechanically port your callers.
7+
8+
> **TL;DR**
9+
> - All `ActionOn*` and `Func<Task<TResult>>`-taking `Function*` overloads are now `[Obsolete]`. Replace with `Tap` / `Match` / `*Async`.
10+
> - `Result<T>.Instance` is now `[Obsolete]`. Replace with `Result<T>.Create()`.
11+
> - Implicit `Exception → Result` / `Result<T>` operators **throw** on `null` instead of producing a silent empty failure.
12+
> - `Result<T>(Exception)` ctor adds **one** `Exception` message instead of two.
13+
> - `JoinErrors` now derives `IsSuccess` from the input collection (was a bug).
14+
> - `Success<T>(T, params RelatedObjectModel[])` no longer adds a ghost `MessageType.None` message when `relatedObjects` is empty/null.
15+
> - `ExceptionHelper.PreserveStackTrace` now returns `ExceptionDispatchInfo` instead of `void`.
16+
> - `RelatedObjectModel.ToString()` is null-safe.
17+
> - New combinators added: `Map`, `Bind`, `Match`, `Tap` (sync + async), `Validate` / `Ensure`, `FunctionExtensionsAsync.*Async`.
18+
19+
---
20+
21+
## 1. Compile-time obsoletions
22+
23+
These changes raise `CS0618` warnings. Builds with `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>` will **fail**. Either suppress with `#pragma warning disable CS0618` or migrate to the replacements below.
24+
25+
### 1.1 `Result<T>.Instance` to `Result<T>.Create()`
26+
27+
`Instance` was misleading, every access already returned a *new* instance. The new factory makes intent explicit.
28+
29+
```csharp
30+
// Before
31+
var r = Result<Foo>.Instance;
32+
33+
// After
34+
var r = Result<Foo>.Create();
35+
```
36+
37+
### 1.2 `ActionOnSuccess` to `Tap`
38+
39+
```csharp
40+
// Before
41+
result.ActionOnSuccess(r => _logger.LogInformation("ok: {V}", r.Response));
42+
43+
// After
44+
result.Tap(value => _logger.LogInformation("ok: {V}", value));
45+
```
46+
47+
`Tap` returns the same instance for chaining and only invokes the action on success.
48+
49+
### 1.3 `ActionOnFailure` / `ActionOn` to `Match`
50+
51+
```csharp
52+
// Before
53+
result.ActionOn(
54+
onSuccess: r => _audit.Saved(r.Response),
55+
onFailure: r => _logger.LogError("failed: {Msg}", r.GetFirstMessage()));
56+
57+
// After (side-effect form returns the same IResult)
58+
result.Match(
59+
onSuccess: r => _audit.Saved(((Result<Foo>)r).Response),
60+
onFailure: r => _logger.LogError("failed: {Msg}", r.GetFirstMessage()));
61+
62+
// After (fold-into-value form — the recommended pattern when you want a return value)
63+
int httpStatus = result.Match(
64+
onSuccess: _ => 200,
65+
onFailure: _ => 500);
66+
```
67+
68+
### 1.4 `FunctionOn*` / `ExecuteFunction` taking `Func<Task<TResult>>` to `*Async` equivalents
69+
70+
The seven sync-over-async overloads in `FunctionExtensions` are obsolete because awaiting a `Func<Task<T>>` from a synchronous extension is a possible deadlock. Use `RzR.ResultMessage.Extensions.Result.Functions.FunctionExtensionsAsync` instead.
71+
72+
```csharp
73+
// Before- sync-over-async
74+
var r = result.FunctionOnSuccess(async r => await _store.SaveAsync(r.Response));
75+
76+
// After
77+
var r = await result.FunctionOnSuccessAsync(async r => await _store.SaveAsync(r.Response));
78+
```
79+
80+
| Before | After |
81+
| --------------------------------------------------- | ------------------------------------------------------------- |
82+
| `FunctionOnSuccess(Func<TResult, Task<TOut>>)` | `FunctionOnSuccessAsync(Func<TResult, Task<TOut>>)` |
83+
| `FunctionOnFailure(Func<TResult, Task<TOut>>)` | `FunctionOnFailureAsync(Func<TResult, Task<TOut>>)` |
84+
| `FunctionOn(...) ` 4 overloads with async delegates | `FunctionOnAsync(...)` 4 overloads |
85+
| `ExecuteFunction(Func<TResult, Task<TOut>>)` | `ExecuteFunctionAsync(Func<TResult, Task<TOut>>)` |
86+
87+
---
88+
89+
## 2. Runtime behavior changes (silent- read carefully)
90+
91+
These do **not** raise warnings, but can change observable behavior.
92+
93+
### 2.1 Implicit `Exception -> Result` / `Result<T>` throws on `null`
94+
95+
```csharp
96+
// Before
97+
Result r = (Exception)null; // r.IsSuccess == false, r.Messages empty (silent)
98+
99+
// After
100+
Result r = (Exception)null; // throws ArgumentNullException
101+
```
102+
103+
If you previously relied on the silent fallback, guard at the call site:
104+
105+
```csharp
106+
Exception ex = TryGetException();
107+
Result r = ex is null ? Result.Failure() : ex;
108+
```
109+
110+
### 2.2 `Result<T>(Exception)` ctor adds one message instead of two
111+
112+
```csharp
113+
// Before
114+
new Result<Foo>(ex).Messages.Count; // 2 (empty info + trace)
115+
116+
// After
117+
new Result<Foo>(ex).Messages.Count; // 1 (single MessageType.Exception entry)
118+
```
119+
120+
If your tests asserted `Count == 2`, change them to `Count == 1`.
121+
122+
### 2.3 `GetFirstMessage` / `GetFirstError` fall back to exception messages
123+
124+
When a result contains only exception messages (no plain `Info`/`Error` text), these helpers used to return an empty string. They now return the exception's message text.
125+
126+
```csharp
127+
// Before
128+
new Result<Foo>(new InvalidOperationException("boom")).GetFirstMessage(); // ""
129+
130+
// After
131+
new Result<Foo>(new InvalidOperationException("boom")).GetFirstMessage(); // "boom"
132+
```
133+
134+
### 2.4 `JoinErrors(IEnumerable<Result>)` derives `IsSuccess` from the collection
135+
136+
Previously the joined result inherited `IsSuccess` from the calling instance, which was a bug — joining a failed result with a list of successes incorrectly stayed failed (and vice versa).
137+
138+
```csharp
139+
// Before
140+
var joined = Result<int>.Success().JoinErrors(new[] { Result.Failure("e") });
141+
joined.IsSuccess; // true (wrong-inherited from caller)
142+
143+
// After
144+
var joined = Result<int>.Success().JoinErrors(new[] { Result.Failure("e") });
145+
joined.IsSuccess; // false (correctly: not all inputs are successful)
146+
```
147+
148+
### 2.5 `Success<T>(T, params RelatedObjectModel[])` no longer adds a ghost message
149+
150+
```csharp
151+
// Before
152+
Result<Foo>.Success(foo).Messages.Count; // 1 (a stray MessageType.None entry)
153+
Result<Foo>.Success(foo, related).Messages.Count; // 1 (the related-object message)
154+
155+
// After
156+
Result<Foo>.Success(foo).Messages.Count; // 0
157+
Result<Foo>.Success(foo, related).Messages.Count; // 1 (unchanged)
158+
```
159+
160+
If you asserted `Messages.Count == 1` for a plain `Success(value)` call, update to `0`.
161+
162+
### 2.6 `ExceptionHelper.PreserveStackTrace` signature change
163+
164+
Rewritten on top of `ExceptionDispatchInfo`.
165+
166+
```csharp
167+
// Before
168+
public static void PreserveStackTrace(Exception ex);
169+
ExceptionHelper.PreserveStackTrace(ex);
170+
throw ex;
171+
172+
// After
173+
public static ExceptionDispatchInfo PreserveStackTrace(Exception ex);
174+
ExceptionHelper.PreserveStackTrace(ex).Throw();
175+
```
176+
177+
### 2.7 `RelatedObjectModel.ToString()` is null-safe
178+
179+
Calling `ToString()` on a `RelatedObjectModel` with a `null` `InDataSourceNames` no longer throws `NullReferenceException`. Output format for the null case:
180+
181+
```
182+
InCodeName: <name> <-> InDataSourceName:
183+
```
184+
185+
If you parse `ToString()` output (not recommended), allow for a trailing empty segment.
186+
187+
---
188+
189+
## 3. New APIs (additive — no migration required, but recommended)
190+
191+
### 3.1 Synchronous monadic combinators
192+
193+
Available as instance methods on `Result<T>`:
194+
195+
```csharp
196+
Result<int> mapped = source.Map(x => x.Length);
197+
Result<Order> bound = source.Bind(id => _repo.GetOrder(id));
198+
int value = source.Match(onSuccess: r => 200, onFailure: _ => 500);
199+
Result<int> same = source.Tap(v => _logger.LogInformation("v={V}", v));
200+
```
201+
202+
### 3.2 Async combinators
203+
204+
Available as extension methods in `RzR.ResultMessage.Extensions.Result`. Each combinator is offered both on `Result<T>` and on `Task<Result<T>>`, with sync and async delegates, so a full pipeline composes fluently:
205+
206+
```csharp
207+
using RzR.ResultMessage.Extensions.Result;
208+
209+
var dto = await _repo.GetOrderByIdAsync(id) // Task<Result<Order>>
210+
.MapAsync(o => o.ToSummary()) // sync projection
211+
.BindAsync(async s => await _enricher.EnrichAsync(s))
212+
.TapAsync(async s => await _audit.WriteAsync(s))
213+
.MatchAsync(
214+
onSuccess: s => OrderResponse.From(s),
215+
onFailure: r => OrderResponse.Error(r.GetFirstError()));
216+
```
217+
218+
### 3.3 Validation aggregation: `Validate` / `Ensure`
219+
220+
| Method | Behavior |
221+
| ------------------------------- | --------------------------------------------------------------------------------------------------- |
222+
| `Validate(predicate, error)` | Always evaluates; chain to **accumulate** every violation. Overloads for `(key, error)` and `MessageDataModel`. |
223+
| `Ensure(predicate, error)` | **Short-circuits** once `IsSuccess == false`, safe for guard chains where later predicates would NRE. |
224+
| `ValidateAsync(...)` | Async predicate, on both `Result<T>` and `Task<Result<T>>`. |
225+
226+
```csharp
227+
// Accumulating
228+
var result = Result<Order>.Success(order)
229+
.Validate(o => o.Items.Count > 0, "items required")
230+
.Validate(o => o.Total > 0, "total > 0")
231+
.Validate(o => o.Customer != null, "customer required");
232+
233+
// Short-circuiting (later predicates skipped if any earlier one fails)
234+
var safe = Result<User>.Success(user)
235+
.Ensure(u => u != null, "user required")
236+
.Ensure(u => !string.IsNullOrEmpty(u.Email), "email required")
237+
.Ensure(u => u.Email.Contains("@"), "email must be valid");
238+
```
239+
240+
---
241+
242+
## 4. Backward-compatibility shims considered
243+
244+
The obsolete attributes still allow existing code to compile and run. There is **no compatibility shim** for the runtime behavior changes in §2 — they are intentional bug fixes. If you depended on the old behavior, you must update at the call site.
245+
246+
If you need to stay on the old behavior temporarily, pin the previous major version in your `PackageReference` while you migrate.

0 commit comments

Comments
 (0)