Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(EventData): Add MapBodies overload with context #127

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ The `Unreleased` section name is replaced by the expected version of next releas
## [Unreleased]

### Added

- `Core.EventData.MapEx`: Enable contextual encoding of bodies [#127](https://github.com/jet/FsCodec/pull/127)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix


### Changed
### Removed
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion src/FsCodec.Box/Compression.fs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type Compression private () =
static member EncodeTryCompress<'Event, 'Context>(native: IEventCodec<'Event, ReadOnlyMemory<byte>, 'Context>, [<Optional; DefaultParameterValue null>] ?options)
: IEventCodec<'Event, EncodedBody, 'Context> =
let opts = defaultArg options CompressionOptions.Default
FsCodec.Core.EventCodec.Map(native, (fun x -> Compression.Utf8ToEncodedTryCompress(opts, x)), Func<_, _> Compression.EncodedToUtf8)
FsCodec.Core.EventCodec.Map(native, (fun d -> Compression.Utf8ToEncodedTryCompress(opts, d)), Func<_, _> Compression.EncodedToUtf8)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undo


/// <summary>Adapts an <c>IEventCodec</c> rendering to <c>ReadOnlyMemory&lt;byte&gt;</c> Event Bodies to encode as per <c>EncodeTryCompress</c>, but without attempting compression.</summary>
[<Extension>]
Expand Down
1 change: 1 addition & 0 deletions src/FsCodec.Box/FsCodec.Box.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

<ItemGroup>
<ProjectReference Condition=" '$(Configuration)' == 'Debug' " Include="../FsCodec/FsCodec.fsproj" />
<!-- TODO if taking a dependency on 3.1, the impl should switch to EventCodec.mapBodies, and EventCodec.Map should be Obsoleted -->
<PackageReference Condition=" '$(Configuration)' == 'Release' " Include="FsCodec" Version="[3.0.0, 4.0.0)" />
</ItemGroup>

Expand Down
6 changes: 4 additions & 2 deletions src/FsCodec.SystemTextJson/FsCodec.SystemTextJson.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Condition=" '$(Configuration)' == 'Debug' " Include="../FsCodec.Box/FsCodec.Box.fsproj" />
<PackageReference Condition=" '$(Configuration)' == 'Release' " Include="FsCodec.Box" Version="[3.0.0, 4.0.0)" />
<ProjectReference Include="../FsCodec.Box/FsCodec.Box.fsproj" />
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undo

<!-- NEXT PUBLISHED VERSION will take a 3.1 dependency to avoid using the Obsoleted API-->
<!-- <ProjectReference Condition=" '$(Configuration)' == 'Debug' " Include="../FsCodec.Box/FsCodec.Box.fsproj" />-->
<!-- <PackageReference Condition=" '$(Configuration)' == 'Release' " Include="FsCodec.Box" Version="[3.0.0, 4.0.0)" />-->
</ItemGroup>

</Project>
2 changes: 0 additions & 2 deletions src/FsCodec.SystemTextJson/Interop.fs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ type InteropHelpers private () =
[<Extension>]
static member ToUtf8Codec<'Event, 'Context>(native: FsCodec.IEventCodec<'Event, JsonElement, 'Context>)
: FsCodec.IEventCodec<'Event, ReadOnlyMemory<byte>, 'Context> =

FsCodec.Core.EventCodec.Map(native, Func<_, _> InteropHelpers.JsonElementToUtf8, Func<_, _> InteropHelpers.Utf8ToJsonElement)

/// <summary>Adapts an IEventCodec that's rendering to <c>ReadOnlyMemory&lt;byte&gt;</c> Event Bodies to handle <c>JsonElement</c> bodies instead.<br/>
/// NOTE where possible, it's better to use <c>CodecJsonElement</c> in preference to <c>Codec</c> to encode directly in order to avoid this mapping process.</summary>
[<Extension>]
static member ToJsonElementCodec<'Event, 'Context>(native: FsCodec.IEventCodec<'Event, ReadOnlyMemory<byte>, 'Context>)
: FsCodec.IEventCodec<'Event, JsonElement, 'Context> =

FsCodec.Core.EventCodec.Map(native, Func<_, _> InteropHelpers.Utf8ToJsonElement, Func<_, _> InteropHelpers.JsonElementToUtf8)
78 changes: 62 additions & 16 deletions src/FsCodec/FsCodec.fs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ namespace FsCodec.Core

open FsCodec
open System
open System.ComponentModel

/// <summary>An Event about to be written, see <c>IEventData</c> for further information.</summary>
[<NoComparison; NoEquality>]
Expand All @@ -61,16 +62,31 @@ type EventData<'Format>(eventType, data, meta, eventId, correlationId, causation
member _.CausationId = causationId
member _.Timestamp = timestamp

static member Map<'Mapped>(f: Func<'Format, 'Mapped>)
(x: IEventData<'Format>): IEventData<'Mapped> =
static member MapBodies<'Mapped>(f: Func<IEventData<'Format>, 'Format, 'Mapped>): Func<IEventData<'Format>, IEventData<'Mapped>> =
Func<_, _>(fun x ->
{ new IEventData<'Mapped> with
member _.EventType = x.EventType
member _.Data = f.Invoke x.Data
member _.Meta = f.Invoke x.Meta
member _.Data = f.Invoke(x, x.Data)
member _.Meta = f.Invoke(x, x.Meta)
member _.EventId = x.EventId
member _.CorrelationId = x.CorrelationId
member _.CausationId = x.CausationId
member _.Timestamp = x.Timestamp }
member _.Timestamp = x.Timestamp })

// Original ugly signature
[<Obsolete "Superseded by MapBodies / EventData.mapBodies; more importantly, the original signature mixed F# and C# types so was messy in all contexts"; EditorBrowsable(EditorBrowsableState.Never)>]
static member Map<'Mapped>(f: Func<'Format, 'Mapped>) (x: IEventData<'Format>): IEventData<'Mapped> =
EventData.MapBodies(Func<_, _, _>(fun _x -> f.Invoke)).Invoke(x)

/// F#-specific wrappers; for C#, use EventData.MapBodies directly
// These helper modules may move up to the FsCodec namespace in V4, along with breaking changes moving IsUnfold and Context from ITimelineEvent to IEventData
// If you have helpers that should be in the box alongside these, raise an Issue please
module EventData =

let mapBodies_<'Format, 'Mapped> (f: IEventData<'Format> -> 'Format -> 'Mapped) =
EventData.MapBodies(Func<IEventData<'Format>, 'Format, 'Mapped> f).Invoke
let mapBodies<'Format, 'Mapped> (f: 'Format -> 'Mapped) =
EventData.MapBodies(Func<IEventData<'Format>, 'Format, 'Mapped>(fun _ -> f)).Invoke

/// <summary>An Event or Unfold that's been read from a Store and hence has a defined <c>Index</c> on the Event Timeline.</summary>
[<NoComparison; NoEquality>]
Expand All @@ -90,7 +106,7 @@ type TimelineEvent<'Format>(index, eventType, data, meta, eventId, correlationId
TimelineEvent(index, inner.EventType, inner.Data, inner.Meta, inner.EventId, inner.CorrelationId, inner.CausationId, inner.Timestamp, isUnfold, Option.toObj context, size) :> _

override _.ToString() = sprintf "%s %s @%i" (if isUnfold then "Unfold" else "Event") eventType index

interface ITimelineEvent<'Format> with
member _.Index = index
member _.IsUnfold = isUnfold
Expand All @@ -104,36 +120,66 @@ type TimelineEvent<'Format>(index, eventType, data, meta, eventId, correlationId
member _.CausationId = causationId
member _.Timestamp = timestamp

static member Map<'Mapped>(f: Func<'Format, 'Mapped>)
(x: ITimelineEvent<'Format>): ITimelineEvent<'Mapped> =
static member MapBodies<'Mapped>(f: Func<ITimelineEvent<'Format>, 'Format, 'Mapped>): Func<ITimelineEvent<'Format>, ITimelineEvent<'Mapped>> =
Func<_, _>(fun x ->
{ new ITimelineEvent<'Mapped> with
member _.Index = x.Index
member _.IsUnfold = x.IsUnfold
member _.Context = x.Context
member _.Size = x.Size
member _.EventType = x.EventType
member _.Data = f.Invoke x.Data
member _.Meta = f.Invoke x.Meta
member _.Data = f.Invoke(x, x.Data)
member _.Meta = f.Invoke(x, x.Meta)
member _.EventId = x.EventId
member _.CorrelationId = x.CorrelationId
member _.CausationId = x.CausationId
member _.Timestamp = x.Timestamp }
member _.Timestamp = x.Timestamp })
// Original ugly signature
[<Obsolete "Superseded by MapBodies / TimeLineEvent.mapBodies; more importantly, the original signature mixed F# and C# types so was messy in all contexts"; EditorBrowsable(EditorBrowsableState.Never)>]
static member Map<'Mapped>(f: Func<'Format, 'Mapped>) (x: ITimelineEvent<'Format>): ITimelineEvent<'Mapped> =
TimelineEvent.MapBodies(Func<_, _, _>(fun _x -> f.Invoke)).Invoke(x)

/// F#-specific wrappers; for C#, use TimelineEvent.MapBodies directly
module TimelineEvent =

let mapBodies_<'Format, 'Mapped> (f: ITimelineEvent<'Format> -> 'Format -> 'Mapped) =
TimelineEvent.MapBodies(Func<ITimelineEvent<'Format>, 'Format, 'Mapped> f).Invoke
let mapBodies<'Format, 'Mapped> (f: 'Format -> 'Mapped) =
TimelineEvent.MapBodies(Func<ITimelineEvent<'Format>, 'Format, 'Mapped>(fun _ -> f)).Invoke

[<AbstractClass; Sealed>]
type EventCodec<'Event, 'Format, 'Context> private () =

static member Map<'TargetFormat>(native: IEventCodec<'Event, 'Format, 'Context>, up: Func<'Format,'TargetFormat>, down: Func<'TargetFormat, 'Format>)
static member MapBodies<'TargetFormat>(
native: IEventCodec<'Event, 'Format, 'Context>,
up: Func<IEventData<'Format>, 'Format, 'TargetFormat>,
down: Func<'TargetFormat, 'Format>)
: IEventCodec<'Event, 'TargetFormat, 'Context> =

let upConvert = EventData.Map up
let downConvert = TimelineEvent.Map down
let upConvert = EventData.MapBodies up
let downConvert = TimelineEvent.MapBodies(fun _ x -> down.Invoke x)

{ new IEventCodec<'Event, 'TargetFormat, 'Context> with

member _.Encode(context, event) =
let encoded = native.Encode(context, event)
upConvert encoded
upConvert.Invoke encoded

member _.Decode target =
let encoded = downConvert target
let encoded = downConvert.Invoke target
native.Decode encoded }

// NOTE To be be replaced by MapBodies/EventCodec.mapBodies for symmetry with TimelineEvent and EventData
// TO BE be Obsoleted and whenever FsCodec.Box is next released
[<EditorBrowsable(EditorBrowsableState.Never)>]
static member Map<'TargetFormat>(native: IEventCodec<'Event, 'Format, 'Context>, up: Func<'Format, 'TargetFormat>, down: Func<'TargetFormat, 'Format>)
: IEventCodec<'Event, 'TargetFormat, 'Context> =
EventCodec.MapBodies(native, Func<_, _, _>(fun _x -> up.Invoke), down)

/// F#-specific wrappers; for C#, use EventCodec.MapBodies directly
module EventCodec =

let mapBodies_ (up: IEventData<'Format> -> 'Format -> 'TargetFormat) (down: 'TargetFormat -> 'Format) x =
EventCodec<'Event, 'Format, 'Context>.MapBodies<'TargetFormat>(x, up, down)
let mapBodies (up: 'Format -> 'TargetFormat) (down: 'TargetFormat -> 'Format) x =
EventCodec<'Event, 'Format, 'Context>.MapBodies<'TargetFormat>(x, Func<_, _, _>(fun _ -> up), down)