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

Move Equinox.Codec out to FsCodec #156

Merged
merged 13 commits into from
Aug 30, 2019
Prev Previous commit
Next Next commit
Add/use Gardelloyd.NewtonsoftJson.Settings
  • Loading branch information
bartelink committed Aug 29, 2019
commit d48dd64a8a9308a9197ae25b3c0cfa08e02b1284
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The components within this repository are delivered as a series of multi-targete
- `Gardelloyd.IUnionEncoder`: allows tagging of F# Discriminated Union cases in a versionable manner with low-dependency `DataMember(Name=` tags using [TypeShape](https://github.com/eiriktsarpalis/TypeShape)'s [`UnionContractEncoder`](https://github.com/eiriktsarpalis/TypeShape/blob/master/tests/TypeShape.Tests/UnionContractTests.fs)
- `Gardelloyd.Custom`: enables plugging in a serializer and/or Union Encoder of your choice (typically this is used to supply a pair of functions:- `encode` and `tryDecode`)
- `Gardelloyd.NewtonsoftJson` [![Codec NuGet](https://img.shields.io/nuget/v/Gardelloyd.NewtonsoftJson.svg)](https://www.nuget.org/packages/Gardelloyd.NewtonsoftJson/): Implementation of `Gardelloyd.IUnionEncoder` that uses Json.net to serialize the bodies of the union cases. ([depends](https://www.fuget.org/packages/Gardelloyd.NewtonsoftJson) on `Gardelloyd`, `Microsoft.IO.RecyclableMemoryStream 1.2.2`, `Newtonsoft.Json >= 11.0.2`
- (Not yet implemented) `Gardelloyd.SystemTextJson`: drop in replacement that allows one to target the .NET `System.Text.Json` serializer solely by changing the referenced namespace.
- (planned) `Gardelloyd.SystemTextJson`: drop in replacement that allows one to target the .NET `System.Text.Json` serializer solely by changing the referenced namespace.

### Store libraries

Expand Down
4 changes: 2 additions & 2 deletions samples/Store/Integration/EventStoreIntegration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
module Samples.Store.Integration.EventStoreIntegration

open Equinox.EventStore
open Gardelloyd.NewtonsoftJson
open System

let serializationSettings = Newtonsoft.Json.Converters.FSharp.Settings.CreateCorrect()
let genCodec<'Union when 'Union :> TypeShape.UnionContract.IUnionContract>() = Gardelloyd.NewtonsoftJson.Codec.Create<'Union>(serializationSettings)
let genCodec<'Union when 'Union :> TypeShape.UnionContract.IUnionContract>() = Codec.Create<'Union>(Settings.Create())

/// Connect with Gossip based cluster discovery using the default Commercial edition Manager port config
/// Such a config can be simulated on a single node with zero config via the EventStore OSS package:-
Expand Down
58 changes: 56 additions & 2 deletions src/Gardelloyd.NewtonsoftJson/NewtonsoftJson.fs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ module Core =

/// Provides Codecs that render to a UTF-8 array suitable for storage in EventStore or CosmosDb based on explicit functions you supply using `Newtonsoft.Json` and
/// `TypeShape.UnionContract.UnionContractEncoder` - if you need full control and/or have have your own codecs, see `Gardelloyd.Custom.Create` instead
type Codec =
type Codec private () =

/// <summary>
/// Generate a codec suitable for use with <c>Equinox.EventStore</c> or <c>Equinox.Cosmos</c>,
Expand All @@ -73,4 +73,58 @@ type Codec =
let enc = dataCodec.Encode value
Gardelloyd.Core.EventData.Create(enc.CaseName, enc.Payload) :> _
member __.TryDecode encoded =
dataCodec.TryDecode { CaseName = encoded.EventType; Payload = encoded.Data } }
dataCodec.TryDecode { CaseName = encoded.EventType; Payload = encoded.Data } }

open Newtonsoft.Json.Serialization
open System

type Settings private () =
/// <summary>
/// Creates a default set of serializer settings used by Json serialization. When used with no args, same as JsonSerializerSettings.CreateDefault()
/// </summary>
/// <param name="camelCase">Render idiomatic camelCase for PascalCase items by using `CamelCasePropertyNamesContractResolver`. Defaults to false.</param>
/// <param name="indent">Use multi-line, indented formatting when serializing json; defaults to false.</param>
/// <param name="ignoreNulls">Ignore null values in input data; defaults to false.</param>
/// <param name="errorOnMissing">Error on missing values (as opposed to letting them just be default-initialized); defaults to false.</param>
static member CreateDefault
( [<Optional;ParamArray>]converters : JsonConverter[],
[<Optional;DefaultParameterValue(null)>]?indent : bool,
[<Optional;DefaultParameterValue(null)>]?camelCase : bool,
[<Optional;DefaultParameterValue(null)>]?ignoreNulls : bool,
[<Optional;DefaultParameterValue(null)>]?errorOnMissing : bool) =
let indent = defaultArg indent false
let camelCase = defaultArg camelCase false
let ignoreNulls = defaultArg ignoreNulls false
let errorOnMissing = defaultArg errorOnMissing false
let resolver : IContractResolver =
if camelCase then CamelCasePropertyNamesContractResolver() :> _
else DefaultContractResolver() :> _
JsonSerializerSettings(
ContractResolver = resolver,
Converters = converters,
DateTimeZoneHandling = DateTimeZoneHandling.Utc, // Override default of RoundtripKind
DateFormatHandling = DateFormatHandling.IsoDateFormat, // Pin Json.Net claimed default
Formatting = (if indent then Formatting.Indented else Formatting.None),
MissingMemberHandling = (if errorOnMissing then MissingMemberHandling.Error else MissingMemberHandling.Ignore),
NullValueHandling = (if ignoreNulls then NullValueHandling.Ignore else NullValueHandling.Include))

/// <summary>Optionated helper that creates a set of serializer settings that fail fast, providing less surprises when working in F#.</summary>
/// <param name="camelCase">
/// Render idiomatic camelCase for PascalCase items by using `CamelCasePropertyNamesContractResolver`.
/// Defaults to false on basis that you'll use record and tuple field names that are camelCase (and hence not `CLSCompliant`).</param>
/// <param name="indent">Use multi-line, indented formatting when serializing json; defaults to false.</param>
/// <param name="ignoreNulls">Ignore null values in input data; defaults to `true`. NB OOTB, Json.Net defaults to false.</param>
/// <param name="errorOnMissing">Error on missing values (as opposed to letting them just be default-initialized); defaults to false.</param>
static member Create
( [<Optional;ParamArray>]converters : JsonConverter[],
[<Optional;DefaultParameterValue(null)>]?indent : bool,
[<Optional;DefaultParameterValue(null)>]?camelCase : bool,
[<Optional;DefaultParameterValue(null)>]?ignoreNulls : bool,
[<Optional;DefaultParameterValue(null)>]?errorOnMissing : bool) =
Settings.CreateDefault(
converters=converters,
// the key impact of this is that Nullables/options start to render as absent (same for strings etc)
ignoreNulls=defaultArg ignoreNulls true,
?errorOnMissing=errorOnMissing,
?indent=indent,
?camelCase=camelCase)
2 changes: 1 addition & 1 deletion src/Gardelloyd/Gardelloyd.fs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type IUnionEncoder<'Union, 'Format> =
abstract TryDecode : encoded:IEvent<'Format> -> 'Union option

/// Provides Codecs that render to a UTF-8 array suitable for storage in EventStore or CosmosDb based on explicit functions you supply
/// i.e., with using conventions / Type Shapes / Reflection or specific Json processing libraries - see Gardelloyd.NewtonsoftJson.Codec for batteries-included Coding/Decoding
/// i.e., with using conventions / Type Shapes / Reflection or specific Json processing libraries - see Gardelloyd.*.Codec for batteries-included Coding/Decoding
type Custom =

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions tests/Equinox.Cosmos.Integration/CosmosIntegration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ open Domain
open Equinox.Cosmos
open Equinox.Cosmos.Integration.Infrastructure
open FSharp.UMX
open Newtonsoft.Json
open Gardelloyd.NewtonsoftJson
open Swensen.Unquote
open System.Threading
open System

let serializationSettings = JsonSerializerSettings()
let defaultSettings = Settings.CreateDefault()
let genCodec<'Union when 'Union :> TypeShape.UnionContract.IUnionContract>() =
Gardelloyd.NewtonsoftJson.Codec.Create<'Union>(serializationSettings)
Codec.Create<'Union>(defaultSettings)

module Cart =
let fold, initial = Domain.Cart.Folds.fold, Domain.Cart.Folds.initial
Expand Down
5 changes: 3 additions & 2 deletions tests/Equinox.Cosmos.Integration/JsonConverterTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

open Equinox.Cosmos
open FsCheck.Xunit
open Gardelloyd.NewtonsoftJson
open Newtonsoft.Json
open Swensen.Unquote
open System
Expand All @@ -13,8 +14,8 @@ type Union =
| B of Embedded
interface TypeShape.UnionContract.IUnionContract

let defaultSettings = JsonSerializerSettings()
let mkUnionEncoder () = Gardelloyd.NewtonsoftJson.Codec.Create<Union>(defaultSettings)
let defaultSettings = Settings.Create()
let mkUnionEncoder () = Codec.Create<Union>(defaultSettings)

type EmbeddedString = { embed : string }
type EmbeddedDate = { embed : DateTime }
Expand Down