diff --git a/src/FsCodec.Box/ByteArray.fs b/src/FsCodec.Box/ByteArray.fs index 85c456f..77316d2 100644 --- a/src/FsCodec.Box/ByteArray.fs +++ b/src/FsCodec.Box/ByteArray.fs @@ -19,4 +19,4 @@ type ByteArray private () = [] static member ToByteArrayCodec<'Event, 'Context>(native: IEventCodec<'Event, ReadOnlyMemory, 'Context>) : IEventCodec<'Event, byte[], 'Context> = - FsCodec.Core.EventCodec.Map(native, Func<_, _> ByteArray.ReadOnlyMemoryToBytes, Func<_, _> ByteArray.BytesToReadOnlyMemory) + FsCodec.Core.EventCodec.mapBodies ByteArray.ReadOnlyMemoryToBytes ByteArray.BytesToReadOnlyMemory native diff --git a/src/FsCodec.Box/Compression.fs b/src/FsCodec.Box/Compression.fs index aaa7cf9..1b0622f 100644 --- a/src/FsCodec.Box/Compression.fs +++ b/src/FsCodec.Box/Compression.fs @@ -8,77 +8,21 @@ open System.Runtime.InteropServices /// Enables the decoding side to transparently inflate the data on loading without burdening the application layer with tracking the encoding scheme used type EncodedBody = (struct(int * ReadOnlyMemory)) -module private EncodedMaybeCompressed = - - module Encoding = - let [] Direct = 0 // Assumed for all values not listed here - let [] Deflate = 1 // Deprecated encoding produced by versions pre 3.0.0-rc.13; no longer produced - let [] Brotli = 2 // Default encoding as of 3.0.0-rc.13 - - (* Decompression logic: triggered by extension methods below at the point where the Codec's Decode retrieves the Data or Meta properties *) - - // In versions pre 3.0.0-rc.13, the compression was implemented as follows; NOTE: use of Flush vs Close saves space but is unconventional - // let private deflate (eventBody: ReadOnlyMemory): System.IO.MemoryStream = - // let output = new System.IO.MemoryStream() - // let compressor = new System.IO.Compression.DeflateStream(output, System.IO.Compression.CompressionLevel.Optimal, leaveOpen = true) - // compressor.Write(eventBody.Span) - // compressor.Flush() // NOTE: using Flush in lieu of close means the result is not padded, which can hinder interop - // output - let private inflate (data: ReadOnlyMemory): byte[] = - let s = new System.IO.MemoryStream(data.ToArray(), writable = false) - let decompressor = new System.IO.Compression.DeflateStream(s, System.IO.Compression.CompressionMode.Decompress, leaveOpen = true) - let output = new System.IO.MemoryStream() - decompressor.CopyTo output - output.ToArray() - let private brotliDecompress (data: ReadOnlyMemory): byte[] = - let s = new System.IO.MemoryStream(data.ToArray(), writable = false) - use decompressor = new System.IO.Compression.BrotliStream(s, System.IO.Compression.CompressionMode.Decompress) - use output = new System.IO.MemoryStream() - decompressor.CopyTo output - output.ToArray() - let decode struct (encoding, data): ReadOnlyMemory = - match encoding with - | Encoding.Deflate -> inflate data |> ReadOnlyMemory - | Encoding.Brotli -> brotliDecompress data |> ReadOnlyMemory - | Encoding.Direct | _ -> data - - (* Conditional compression logic: triggered as storage layer pulls Data/Meta fields - Bodies under specified minimum size, or not meeting a required compression gain are stored directly, equivalent to if compression had not been wired in *) - - let private brotliCompress (eventBody: ReadOnlyMemory): System.IO.MemoryStream = - let output = new System.IO.MemoryStream() - use compressor = new System.IO.Compression.BrotliStream(output, System.IO.Compression.CompressionLevel.Optimal, leaveOpen = true) - compressor.Write(eventBody.Span) - compressor.Close() // NOTE Close, not Flush; we want the output fully terminated to reduce surprises when decompressing - output - let encodeUncompressed (raw: ReadOnlyMemory): EncodedBody = Encoding.Direct, raw - let encode minSize minGain (raw: ReadOnlyMemory): EncodedBody = - if raw.Length < minSize then encodeUncompressed raw - else match brotliCompress raw with - | tmp when raw.Length > int tmp.Length + minGain -> Encoding.Brotli, tmp.ToArray() |> ReadOnlyMemory - | _ -> encodeUncompressed raw - type [] CompressionOptions = { minSize: int; minGain: int } with - /// Attempt to compress anything possible - // TL;DR in general it's worth compressing everything to minimize RU consumption both on insert and update - // For DynamoStore, every time we need to calve from the tip, the RU impact of using TransactWriteItems is significant, - // so preventing or delaying that is of critical importance - // Empirically not much JSON below 48 bytes actually compresses - while we don't assume that, it is what is guiding the derivation of the default static member Default = { minSize = 48; minGain = 4 } - /// Encode the data without attempting to compress, regardless of size static member Uncompressed = { minSize = Int32.MaxValue; minGain = 0 } -[] +[] type Compression private () = static member Utf8ToEncodedDirect(x: ReadOnlyMemory): EncodedBody = - EncodedMaybeCompressed.encodeUncompressed x + FsCodec.Encoding.FromBlob x static member Utf8ToEncodedTryCompress(options, x: ReadOnlyMemory): EncodedBody = - EncodedMaybeCompressed.encode options.minSize options.minGain x + FsCodec.Encoding.FromBlobTryCompress({ minSize = options.minSize; minGain = options.minGain }, x) static member EncodedToUtf8(x: EncodedBody): ReadOnlyMemory = - EncodedMaybeCompressed.decode x + FsCodec.Encoding.DecodeToBlob x static member EncodedToByteArray(x: EncodedBody): byte[] = - Compression.EncodedToUtf8(x).ToArray() + FsCodec.Encoding.DecodeToBlob(x).ToArray() /// Adapts an IEventCodec rendering to ReadOnlyMemory<byte> Event Bodies to attempt to compress the data.
/// If sufficient compression, as defined by options is not achieved, the body is saved as-is.
@@ -87,22 +31,23 @@ type Compression private () = static member EncodeTryCompress<'Event, 'Context>(native: IEventCodec<'Event, ReadOnlyMemory, 'Context>, [] ?options) : IEventCodec<'Event, EncodedBody, 'Context> = let opts = defaultArg options CompressionOptions.Default - FsCodec.Core.EventCodec.Map(native, (fun d -> Compression.Utf8ToEncodedTryCompress(opts, d)), Func<_, _> Compression.EncodedToUtf8) + let opts: FsCodec.CompressionOptions = { minSize = opts.minSize; minGain = opts.minGain } + FsCodec.Core.EventCodec.mapBodies (fun d -> Encoding.FromBlobTryCompress(opts, d)) Encoding.DecodeToBlob native /// Adapts an IEventCodec rendering to ReadOnlyMemory<byte> Event Bodies to encode as per EncodeTryCompress, but without attempting compression. [] static member EncodeUncompressed<'Event, 'Context>(native: IEventCodec<'Event, ReadOnlyMemory, 'Context>) : IEventCodec<'Event, EncodedBody, 'Context> = - FsCodec.Core.EventCodec.Map(native, Func<_, _> Compression.Utf8ToEncodedDirect, Func<_, _> Compression.EncodedToUtf8) + Encoding.EncodeUncompressed native /// Adapts an IEventCodec rendering to int * ReadOnlyMemory<byte> Event Bodies to render and/or consume from Uncompressed ReadOnlyMemory<byte>. [] static member ToUtf8Codec<'Event, 'Context>(native: IEventCodec<'Event, EncodedBody, 'Context>) : IEventCodec<'Event, ReadOnlyMemory, 'Context> = - FsCodec.Core.EventCodec.Map(native, Func<_, _> Compression.EncodedToUtf8, Func<_, _> Compression.Utf8ToEncodedDirect) + Encoding.ToBlobCodec native /// Adapts an IEventCodec rendering to int * ReadOnlyMemory<byte> Event Bodies to render and/or consume from Uncompressed byte[]. [] static member ToByteArrayCodec<'Event, 'Context>(native: IEventCodec<'Event, EncodedBody, 'Context>) : IEventCodec<'Event, byte[], 'Context> = - FsCodec.Core.EventCodec.Map(native, Func<_, _> Compression.EncodedToByteArray, Func<_, _> Compression.Utf8ToEncodedDirect) + Encoding.ToBlobArrayCodec native diff --git a/src/FsCodec.Box/FsCodec.Box.fsproj b/src/FsCodec.Box/FsCodec.Box.fsproj index 93ebe76..e56dd88 100644 --- a/src/FsCodec.Box/FsCodec.Box.fsproj +++ b/src/FsCodec.Box/FsCodec.Box.fsproj @@ -23,9 +23,9 @@ - - - + + + diff --git a/src/FsCodec.SystemTextJson/Encoding.fs b/src/FsCodec.SystemTextJson/Encoding.fs index 7cebc5e..98f09b5 100644 --- a/src/FsCodec.SystemTextJson/Encoding.fs +++ b/src/FsCodec.SystemTextJson/Encoding.fs @@ -13,11 +13,6 @@ type EncodedBody = (struct(int * JsonElement)) module private Impl = - module Encoding = - let [] Direct = 0 // Assumed for all values not listed here - let [] Deflate = 1 // Deprecated encoding produced by Equinox.Cosmos/CosmosStore < v 4.1.0; no longer produced - let [] Brotli = 2 // Default encoding - (* Decompression logic: triggered by extension methods below at the point where the Codec's Decode retrieves the Data or Meta properties *) // Equinox.Cosmos / Equinox.CosmosStore Deflate logic was as below: @@ -73,14 +68,6 @@ module private Impl = if utf8.Length <= int brotli.Length + minGain then encodeUncompressedUtf8 utf8 else Encoding.Brotli, brotli.ToArray() |> blobToBase64StringJsonElement -type [] CompressionOptions = { minSize: int; minGain: int } with - /// Attempt to compress anything possible - // TL;DR in general it's worth compressing everything to minimize RU consumption both on insert and update - // For CosmosStore, every time we touch the tip, the RU impact of the write is significant, - // so preventing or delaying that is of critical importance - // Empirically not much JSON below 48 bytes actually compresses - while we don't assume that, it is what is guiding the derivation of the default - static member Default = { minSize = 48; minGain = 4 } - [] type Encoding private () = @@ -88,16 +75,14 @@ type Encoding private () = Impl.encodeUncompressed x static member FromUtf8(x: ReadOnlyMemory): EncodedBody = Impl.encodeUncompressedUtf8 x - static member TryCompress(options, x: JsonElement): EncodedBody = + static member FromJsonElementTryCompress(options, x: JsonElement): EncodedBody = Impl.tryCompress options.minSize options.minGain x - static member TryCompressUtf8(options, x: ReadOnlyMemory): EncodedBody = + static member FromUtf8TryCompress(options, x: ReadOnlyMemory): EncodedBody = Impl.tryCompressUtf8 options.minSize options.minGain x - static member ToJsonElement(x: EncodedBody): JsonElement = + static member DecodeToJsonElement(x: EncodedBody): JsonElement = Impl.decode x - static member ToUtf8(x: EncodedBody): ReadOnlyMemory = + static member DecodeToUtf8(x: EncodedBody): ReadOnlyMemory = Impl.decodeUtf8 x - static member ToByteArray(x: EncodedBody): byte[] = - Encoding.ToUtf8(x).ToArray() static member ExpandTo(ms: System.IO.Stream, x: EncodedBody) = Impl.decode_ (fun el -> JsonSerializer.Serialize(ms, el)) (fun dec -> dec ms) x @@ -105,7 +90,7 @@ type Encoding private () = [] static member EncodeUncompressed<'Event, 'Context>(native: IEventCodec<'Event, JsonElement, 'Context>) : IEventCodec<'Event, EncodedBody, 'Context> = - FsCodec.Core.EventCodec.mapBodies Encoding.FromJsonElement Encoding.ToJsonElement native + FsCodec.Core.EventCodec.mapBodies Encoding.FromJsonElement Encoding.DecodeToJsonElement native /// The body will be saved as-is under the following circumstances:
/// - the shouldCompress predicate is not satisfied for the event in question.
@@ -113,16 +98,16 @@ type Encoding private () = /// The int produced when Encodeing conveys the encoding used, and must be round tripped alongside the body as a required input of a future Decode.
/// NOTE this is intended for interoperability only; a Codec (such as CodecJsonElement) that encodes to JsonElement is strongly recommended unless you don't have a choice. [] - static member EncodeTryCompressUtf8<'Event, 'Context>( + static member EncodeUtf8TryCompress<'Event, 'Context>( native: IEventCodec<'Event, ReadOnlyMemory, 'Context>, [] ?shouldCompress: Func>, bool>, [] ?options) : IEventCodec<'Event, EncodedBody, 'Context> = let opts = defaultArg options CompressionOptions.Default let encode = shouldCompress |> function - | None -> fun _x (d: ReadOnlyMemory) -> Encoding.TryCompressUtf8(opts, d) - | Some predicate -> fun x d -> if predicate.Invoke x then Encoding.TryCompressUtf8(opts, d) else Encoding.FromUtf8 d - FsCodec.Core.EventCodec.mapBodies_ encode Encoding.ToUtf8 native + | None -> fun _x (d: ReadOnlyMemory) -> Encoding.FromUtf8TryCompress(opts, d) + | Some predicate -> fun x d -> if predicate.Invoke x then Encoding.FromUtf8TryCompress(opts, d) else Encoding.FromUtf8 d + FsCodec.Core.EventCodec.mapBodies_ encode Encoding.DecodeToUtf8 native /// Adapts an IEventCodec rendering to JsonElement Event Bodies to attempt to compress the data.
/// The body will be saved as-is under the following circumstances:
@@ -137,18 +122,18 @@ type Encoding private () = : IEventCodec<'Event, EncodedBody, 'Context> = let opts = defaultArg options CompressionOptions.Default let encode = shouldCompress |> function - | None -> fun _x (d: JsonElement) -> Encoding.TryCompress(opts, d) - | Some predicate -> fun x d -> if predicate.Invoke x then Encoding.TryCompress(opts, d) else Encoding.FromJsonElement d - FsCodec.Core.EventCodec.mapBodies_ encode Encoding.ToJsonElement native + | None -> fun _x (d: JsonElement) -> Encoding.FromJsonElementTryCompress(opts, d) + | Some predicate -> fun x d -> if predicate.Invoke x then Encoding.FromJsonElementTryCompress(opts, d) else Encoding.FromJsonElement d + FsCodec.Core.EventCodec.mapBodies_ encode Encoding.DecodeToJsonElement native /// Adapts an IEventCodec rendering to int * JsonElement Event Bodies to render and/or consume Uncompressed ReadOnlyMemory<byte>. [] static member ToUtf8Codec<'Event, 'Context>(native: IEventCodec<'Event, EncodedBody, 'Context>) : IEventCodec<'Event, ReadOnlyMemory, 'Context> = - FsCodec.Core.EventCodec.mapBodies Encoding.ToUtf8 Encoding.FromUtf8 native + FsCodec.Core.EventCodec.mapBodies Encoding.DecodeToUtf8 Encoding.FromUtf8 native /// Adapts an IEventCodec rendering to int * JsonElement Event Bodies to render and/or consume Uncompressed byte[]. [] - static member ToByteArrayCodec<'Event, 'Context>(native: IEventCodec<'Event, EncodedBody, 'Context>) + static member ToUtf8ArrayCodec<'Event, 'Context>(native: IEventCodec<'Event, EncodedBody, 'Context>) : IEventCodec<'Event, byte[], 'Context> = - FsCodec.Core.EventCodec.mapBodies Encoding.ToByteArray Encoding.FromUtf8 native + FsCodec.Core.EventCodec.mapBodies (Encoding.DecodeToUtf8 >> _.ToArray()) Encoding.FromUtf8 native diff --git a/src/FsCodec.SystemTextJson/FsCodec.SystemTextJson.fsproj b/src/FsCodec.SystemTextJson/FsCodec.SystemTextJson.fsproj index 25d1bb6..9454694 100644 --- a/src/FsCodec.SystemTextJson/FsCodec.SystemTextJson.fsproj +++ b/src/FsCodec.SystemTextJson/FsCodec.SystemTextJson.fsproj @@ -31,11 +31,10 @@ - - - + + - + diff --git a/src/FsCodec.SystemTextJson/Interop.fs b/src/FsCodec.SystemTextJson/Interop.fs index 5e6bb1e..5230d1c 100644 --- a/src/FsCodec.SystemTextJson/Interop.fs +++ b/src/FsCodec.SystemTextJson/Interop.fs @@ -21,11 +21,11 @@ type InteropHelpers private () = [] static member ToUtf8Codec<'Event, 'Context>(native: FsCodec.IEventCodec<'Event, JsonElement, 'Context>) : FsCodec.IEventCodec<'Event, ReadOnlyMemory, 'Context> = - FsCodec.Core.EventCodec.Map(native, Func<_, _> InteropHelpers.JsonElementToUtf8, Func<_, _> InteropHelpers.Utf8ToJsonElement) + FsCodec.Core.EventCodec.mapBodies InteropHelpers.JsonElementToUtf8 InteropHelpers.Utf8ToJsonElement native /// Adapts an IEventCodec that's rendering to ReadOnlyMemory<byte> Event Bodies to handle JsonElement bodies instead.
/// NOTE where possible, it's better to use CodecJsonElement in preference to Codec to encode directly in order to avoid this mapping process.
[] static member ToJsonElementCodec<'Event, 'Context>(native: FsCodec.IEventCodec<'Event, ReadOnlyMemory, 'Context>) : FsCodec.IEventCodec<'Event, JsonElement, 'Context> = - FsCodec.Core.EventCodec.Map(native, Func<_, _> InteropHelpers.Utf8ToJsonElement, Func<_, _> InteropHelpers.JsonElementToUtf8) + FsCodec.Core.EventCodec.mapBodies InteropHelpers.Utf8ToJsonElement InteropHelpers.JsonElementToUtf8 native diff --git a/src/FsCodec/Encoding.fs b/src/FsCodec/Encoding.fs new file mode 100644 index 0000000..6f122f3 --- /dev/null +++ b/src/FsCodec/Encoding.fs @@ -0,0 +1,104 @@ +namespace FsCodec + +open System +open System.Runtime.CompilerServices +open System.Runtime.InteropServices + +/// Represents the body of an Event (or its Metadata), holding the encoded form of the buffer together with an enum value signifying the encoding scheme. +/// Enables the decoding side to transparently inflate the data on loading without burdening the application layer with tracking the encoding scheme used +type EncodedBody = (struct(int * ReadOnlyMemory)) + +module Encoding = + let [] Direct = 0 // Assumed for all values not listed here + let [] Deflate = 1 // Deprecated encoding produced by versions pre 3.0.0-rc.13; no longer produced + let [] Brotli = 2 // Default encoding as of 3.0.0-rc.13 + +module private Impl = + + (* Decompression logic: triggered by extension methods below at the point where the Codec's Decode retrieves the Data or Meta properties *) + + // In versions pre 3.0.0-rc.13, the compression was implemented as follows; NOTE: use of Flush vs Close saves space but is unconventional + // let private deflate (eventBody: ReadOnlyMemory): System.IO.MemoryStream = + // let output = new System.IO.MemoryStream() + // let compressor = new System.IO.Compression.DeflateStream(output, System.IO.Compression.CompressionLevel.Optimal, leaveOpen = true) + // compressor.Write(eventBody.Span) + // compressor.Flush() // NOTE: using Flush in lieu of close means the result is not padded, which can hinder interop + // output + let private inflateTo output (data: ReadOnlyMemory) = + let input = new System.IO.MemoryStream(data.ToArray(), writable = false) + let decompressor = new System.IO.Compression.DeflateStream(input, System.IO.Compression.CompressionMode.Decompress, leaveOpen = true) + decompressor.CopyTo output + let private brotliDecompressTo output (data: ReadOnlyMemory) = + let input = new System.IO.MemoryStream(data.ToArray(), writable = false) + use decompressor = new System.IO.Compression.BrotliStream(input, System.IO.Compression.CompressionMode.Decompress) + decompressor.CopyTo output + let private unpack alg compressedBytes = + use output = new System.IO.MemoryStream() + compressedBytes |> alg output + output.ToArray() |> ReadOnlyMemory + let decode struct (encoding, data): ReadOnlyMemory = + match encoding with + | Encoding.Deflate -> data |> unpack inflateTo + | Encoding.Brotli -> data |> unpack brotliDecompressTo + | Encoding.Direct | _ -> data + + (* Conditional compression logic: triggered as storage layer pulls Data/Meta fields + Bodies under specified minimum size, or not meeting a required compression gain are stored directly, equivalent to if compression had not been wired in *) + + let private brotliCompress (eventBody: ReadOnlyMemory): System.IO.MemoryStream = + let output = new System.IO.MemoryStream() + use compressor = new System.IO.Compression.BrotliStream(output, System.IO.Compression.CompressionLevel.Optimal, leaveOpen = true) + compressor.Write(eventBody.Span) + compressor.Close() // NOTE Close, not Flush; we want the output fully terminated to reduce surprises when decompressing + output + let encodeUncompressed (raw: ReadOnlyMemory): EncodedBody = Encoding.Direct, raw + let tryCompress minSize minGain (raw: ReadOnlyMemory): EncodedBody = + if raw.Length < minSize then encodeUncompressed raw + else match brotliCompress raw with + | tmp when raw.Length > int tmp.Length + minGain -> Encoding.Brotli, tmp.ToArray() |> ReadOnlyMemory + | _ -> encodeUncompressed raw + +type [] CompressionOptions = { minSize: int; minGain: int } with + /// Attempt to compress anything possible + // TL;DR in general it's worth compressing everything to minimize RU consumption both on insert and update + // For DynamoStore, every time we need to calve from the tip, the RU impact of using TransactWriteItems is significant, + // so preventing or delaying that is of critical importance + // Empirically not much JSON below 48 bytes actually compresses - while we don't assume that, it is what is guiding the derivation of the default + static member Default = { minSize = 48; minGain = 4 } + +[] +type Encoding private () = + + static member FromBlob(x: ReadOnlyMemory): EncodedBody = + Impl.encodeUncompressed x + static member FromBlobTryCompress(options, x: ReadOnlyMemory): EncodedBody = + Impl.tryCompress options.minSize options.minGain x + static member DecodeToBlob(x: EncodedBody): ReadOnlyMemory = + Impl.decode x + + /// Adapts an IEventCodec rendering to ReadOnlyMemory<byte> Event Bodies to attempt to compress the data.
+ /// If sufficient compression, as defined by options is not achieved, the body is saved as-is.
+ /// The int conveys a value that must be round tripped alongside the body in order for the decoding process to correctly interpret it.
+ [] + static member EncodeTryCompress<'Event, 'Context>(native: IEventCodec<'Event, ReadOnlyMemory, 'Context>, [] ?options) + : IEventCodec<'Event, EncodedBody, 'Context> = + let opts = defaultArg options CompressionOptions.Default + FsCodec.Core.EventCodec.mapBodies (fun d -> Encoding.FromBlobTryCompress(opts, d)) Encoding.DecodeToBlob native + + /// Adapts an IEventCodec rendering to ReadOnlyMemory<byte> Event Bodies to encode as per EncodeTryCompress, but without attempting compression. + [] + static member EncodeUncompressed<'Event, 'Context>(native: IEventCodec<'Event, ReadOnlyMemory, 'Context>) + : IEventCodec<'Event, EncodedBody, 'Context> = + FsCodec.Core.EventCodec.mapBodies Encoding.FromBlob Encoding.DecodeToBlob native + + /// Adapts an IEventCodec rendering to int * ReadOnlyMemory<byte> Event Bodies to render and/or consume from Uncompressed ReadOnlyMemory<byte>. + [] + static member ToBlobCodec<'Event, 'Context>(native: IEventCodec<'Event, EncodedBody, 'Context>) + : IEventCodec<'Event, ReadOnlyMemory, 'Context> = + FsCodec.Core.EventCodec.mapBodies Encoding.DecodeToBlob Encoding.FromBlob native + + /// Adapts an IEventCodec rendering to int * ReadOnlyMemory<byte> Event Bodies to render and/or consume from Uncompressed byte[]. + [] + static member ToBlobArrayCodec<'Event, 'Context>(native: IEventCodec<'Event, EncodedBody, 'Context>) + : IEventCodec<'Event, byte[], 'Context> = + FsCodec.Core.EventCodec.mapBodies (Encoding.DecodeToBlob >> _.ToArray()) Encoding.FromBlob native diff --git a/src/FsCodec/FsCodec.fs b/src/FsCodec/FsCodec.fs index ff203ed..28311c8 100755 --- a/src/FsCodec/FsCodec.fs +++ b/src/FsCodec/FsCodec.fs @@ -169,9 +169,7 @@ type EventCodec<'Event, 'Format, 'Context> private () = 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 - [] + [] 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) diff --git a/src/FsCodec/FsCodec.fsproj b/src/FsCodec/FsCodec.fsproj index 2fa424e..7cd10d0 100644 --- a/src/FsCodec/FsCodec.fsproj +++ b/src/FsCodec/FsCodec.fsproj @@ -3,13 +3,14 @@ - netstandard2.0 + netstandard2.1 3.0.0 + diff --git a/tests/FsCodec.SystemTextJson.Tests/EncodingTests.fs b/tests/FsCodec.SystemTextJson.Tests/EncodingTests.fs index 76002c2..9df7775 100644 --- a/tests/FsCodec.SystemTextJson.Tests/EncodingTests.fs +++ b/tests/FsCodec.SystemTextJson.Tests/EncodingTests.fs @@ -38,8 +38,8 @@ module InternalDecoding = let explicitBrotli = struct (2, JsonSerializer.SerializeToElement "CwuAeyJ2YWx1ZSI6IkhlbGxvIFdvcmxkIn0D") let decode useRom = - if useRom then FsCodec.SystemTextJson.Encoding.ToByteArray >> JsonSerializer.Deserialize - else FsCodec.SystemTextJson.Encoding.ToJsonElement >> JsonSerializer.Deserialize + if useRom then FsCodec.SystemTextJson.Encoding.DecodeToUtf8 >> _.ToArray() >> JsonSerializer.Deserialize + else FsCodec.SystemTextJson.Encoding.DecodeToJsonElement >> JsonSerializer.Deserialize let [] ``Can decode all known representations`` useRom = test <@ decode useRom direct = inputValue @> diff --git a/tests/FsCodec.Tests/CompressionTests.fs b/tests/FsCodec.Tests/EncodingTests.fs similarity index 87% rename from tests/FsCodec.Tests/CompressionTests.fs rename to tests/FsCodec.Tests/EncodingTests.fs index ab89e32..a73f1d8 100644 --- a/tests/FsCodec.Tests/CompressionTests.fs +++ b/tests/FsCodec.Tests/EncodingTests.fs @@ -1,4 +1,4 @@ -module FsCodec.Tests.CompressionTests +module FsCodec.Tests.EncodingTests open System open Swensen.Unquote @@ -30,7 +30,7 @@ module StringUtf8 = module TryCompress = - let sut = FsCodec.Compression.EncodeTryCompress(StringUtf8.sut) + let sut = FsCodec.Encoding.EncodeTryCompress(StringUtf8.sut) let compressibleValue = String('x', 5000) @@ -52,7 +52,7 @@ module TryCompress = module Uncompressed = - let sut = FsCodec.Compression.EncodeUncompressed(StringUtf8.sut) + let sut = FsCodec.Encoding.EncodeUncompressed(StringUtf8.sut) // Borrow a demonstrably compressible value let value = TryCompress.compressibleValue @@ -74,13 +74,13 @@ module Decoding = let brotli = struct(2, Convert.FromBase64String("CwWASGVsbG8gV29ybGQ=") |> ReadOnlyMemory) let [] ``Can decode all known bodies`` () = - let decode = FsCodec.Compression.EncodedToByteArray >> Text.Encoding.UTF8.GetString + let decode = FsCodec.Encoding.DecodeToBlob >> _.ToArray() >> Text.Encoding.UTF8.GetString test <@ decode raw = "Hello World" @> test <@ decode deflated = "Hello World" @> test <@ decode brotli = "Hello World" @> let [] ``Defaults to leaving the memory alone if unknown`` () = let struct(_, mem) = raw - let body = struct(99, mem) - let decoded = body |> FsCodec.Compression.EncodedToByteArray |> Text.Encoding.UTF8.GetString + let body = struct (99, mem) + let decoded = body |> FsCodec.Encoding.DecodeToBlob |> _.ToArray() |> Text.Encoding.UTF8.GetString test <@ decoded = "Hello World" @> diff --git a/tests/FsCodec.Tests/FsCodec.Tests.fsproj b/tests/FsCodec.Tests/FsCodec.Tests.fsproj index e46994f..c635b36 100644 --- a/tests/FsCodec.Tests/FsCodec.Tests.fsproj +++ b/tests/FsCodec.Tests/FsCodec.Tests.fsproj @@ -7,7 +7,7 @@ - + @@ -21,6 +21,7 @@ +