From 1fcb754e552d2a36913beeb5cd5baab2c1e46d2c Mon Sep 17 00:00:00 2001 From: Bram van den Heuvel Date: Thu, 9 Feb 2023 18:57:56 +0100 Subject: [PATCH] Add documentation + add `sameForVersion` function --- src/Internal/Api/SendMessageEvent/Api.elm | 2 +- src/Internal/Api/SendMessageEvent/Main.elm | 80 ++------- src/Internal/Tools/DecodeExtra.elm | 9 ++ src/Internal/Tools/EncodeExtra.elm | 11 +- src/Internal/Tools/VersionControl.elm | 179 +++++++++++++++++++++ 5 files changed, 210 insertions(+), 71 deletions(-) create mode 100644 src/Internal/Tools/VersionControl.elm diff --git a/src/Internal/Api/SendMessageEvent/Api.elm b/src/Internal/Api/SendMessageEvent/Api.elm index 919fb89..3db5739 100644 --- a/src/Internal/Api/SendMessageEvent/Api.elm +++ b/src/Internal/Api/SendMessageEvent/Api.elm @@ -1,4 +1,4 @@ -module Internal.Api.SendMessageEvent.V1.Api exposing (sendMessageEventV1, sendMessageEventV2, SendMessageEventOutputV1, SendMessageEventInputV1) +module Internal.Api.SendMessageEvent.Api exposing (sendMessageEventV1, sendMessageEventV2, SendMessageEventOutputV1, SendMessageEventInputV1) import Internal.Api.Request as R import Internal.Api.SendMessageEvent.V1.SpecObjects as SO1 diff --git a/src/Internal/Api/SendMessageEvent/Main.elm b/src/Internal/Api/SendMessageEvent/Main.elm index 57e94f3..526330a 100644 --- a/src/Internal/Api/SendMessageEvent/Main.elm +++ b/src/Internal/Api/SendMessageEvent/Main.elm @@ -2,9 +2,6 @@ module Internal.Api.SendMessageEvent.Main exposing (..) import Internal.Api.SendMessageEvent.Api as Api import Internal.Tools.VersionControl as VC -import Internal.Tools.Exceptions as X -import Task exposing (Task) - sendMessageEvent : List String -> Maybe (SendMessageEventInput -> SendMessageEventOutput) sendMessageEvent versions = @@ -12,78 +9,23 @@ sendMessageEvent versions = { current = Api.sendMessageEventV1 , version = "r0.0.0" } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV1 - , upcast = identity - , version = "r0.0.1" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV1 - , upcast = identity - , version = "r0.1.0" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV1 - , upcast = identity - , version = "r0.2.0" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV1 - , upcast = identity - , version = "r0.3.0" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV1 - , upcast = identity - , version = "r0.5.0" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV1 - , upcast = identity - , version = "r0.6.0" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV1 - , upcast = identity - , version = "r0.6.1" - } + |> VC.sameForVersion "r0.0.1" + |> VC.sameForVersion "r0.1.0" + |> VC.sameForVersion "r0.2.0" + |> VC.sameForVersion "r0.3.0" + |> VC.sameForVersion "r0.5.0" + |> VC.sameForVersion "r0.6.0" + |> VC.sameForVersion "r0.6.1" |> VC.addMiddleLayer { downcast = identity , current = Api.sendMessageEventV2 , upcast = identity , version = "v1.1" } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV2 - , upcast = identity - , version = "v1.2" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV2 - , upcast = identity - , version = "v1.3" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV2 - , upcast = identity - , version = "v1.4" - } - |> VC.addMiddleLayer - { downcast = identity - , current = Api.sendMessageEventV2 - , upcast = identity - , version = "v1.5" - } + |> VC.sameForVersion "v1.2" + |> VC.sameForVersion "v1.3" + |> VC.sameForVersion "v1.4" + |> VC.sameForVersion "v1.5" |> VC.mostRecentFromVersionList versions diff --git a/src/Internal/Tools/DecodeExtra.elm b/src/Internal/Tools/DecodeExtra.elm index 531bb5c..761cd02 100644 --- a/src/Internal/Tools/DecodeExtra.elm +++ b/src/Internal/Tools/DecodeExtra.elm @@ -1,5 +1,14 @@ module Internal.Tools.DecodeExtra exposing (opField, opFieldWithDefault) +{-| Module that helps while decoding JSON. + + +# Optional field decoders + +@docs opField, opFieldWithDefault + +-} + import Json.Decode as D diff --git a/src/Internal/Tools/EncodeExtra.elm b/src/Internal/Tools/EncodeExtra.elm index 3b9cd25..2458903 100644 --- a/src/Internal/Tools/EncodeExtra.elm +++ b/src/Internal/Tools/EncodeExtra.elm @@ -1,4 +1,13 @@ -module Internal.Tools.EncodeExtra exposing (..) +module Internal.Tools.EncodeExtra exposing (maybeObject) + +{-| Module that helps with encoding objects into JSON. + + +# Optional body object + +@docs maybeObject + +-} import Json.Encode as E diff --git a/src/Internal/Tools/VersionControl.elm b/src/Internal/Tools/VersionControl.elm new file mode 100644 index 0000000..59a0eb7 --- /dev/null +++ b/src/Internal/Tools/VersionControl.elm @@ -0,0 +1,179 @@ +module Internal.Tools.VersionControl exposing + ( VersionControl, withBottomLayer + , MiddleLayer, addMiddleLayer + , toDict, fromVersion, fromVersionList + , mostRecentFromVersionList, sameForVersion + ) + +{-| This module helps you create multiple functions for different (spec) versions +while still having only one input, one output. + +The module can be best described as a layered version type. + + |----------------------------------------------| + | VersionControl | + | input output | + | | ^ | + |---------------------- | -------------- | ----| + | | + |---------------------- | -------------- | ----| + | MiddleLayer v3 | | | + | [---> current ---] | + | | | | + | downcast upcast | + | | ^ | + |---------------------- | -------------- | ----| + | | + |---------------------- | -------------- | ----| + | MiddleLayer v2 | | | + | [---> current ---] | + | | | | + | downcast upcast | + | | ^ | + |---------------------- | -------------- | ----| + | | + |---------------------- | -------------- | ----| + | BottomLayer v1 | | | + | \---> current ---/ | + | | + |----------------------------------------------| + +This method means you will only need to write one downcast, one current and one upcast +whenever you introduce a new version. This means you can instantly update all functions +without having to write every version! + +The VersionControl keeps a `Dict` type but also tracks the version order. +This way, you can either get the VersionControl type to render the function for +the most recent supported version, or you can choose for yourself which version +you prefer to use. + + +# Making VersionControl + +@docs VersionControl, withBottomLayer + + +# Adding more versions + +@docs MiddleLayer, addMiddleLayer + + +# Getting functions + +@docs toDict, fromVersion, mostRecentFromVerionList, fromVersionList + +-} + +import Dict exposing (Dict) + + +{-| The bottom layer is the final version option. + +If even this version is not approved, there will be no function to execute. + +-} +type alias BottomLayer cin cout = + { current : cin -> cout, version : String } + + +{-| The middle layer is an optional function to execute. + +If the version is approved, it is executed - otherwise, the next version will be considered. + +-} +type alias MiddleLayer cin cout din dout = + { current : cin -> cout + , downcast : cin -> din + , upcast : dout -> cout + , version : String + } + + +{-| The VersionControl layer is the layer that keeps track of all potential versions. +-} +type VersionControl cin cout + = VersionControl + { latestVersion : cin -> cout + , order : List String + , versions : Dict String (cin -> cout) + } + + +{-| Add an extra version to the VersionControl. This will completely change the input and output +of every function, as all functions will abide by the most recent version's specifics. +-} +addMiddleLayer : MiddleLayer cin cout din dout -> VersionControl din dout -> VersionControl cin cout +addMiddleLayer { current, downcast, upcast, version } (VersionControl d) = + VersionControl + { latestVersion = current + , order = version :: d.order + , versions = + d.versions + |> Dict.map (\_ f -> downcast >> f >> upcast) + |> Dict.insert version current + } + + +{-| Provided that a version has been passed to the VersionControl, you will receive the appropriate version. +-} +fromVersion : String -> VersionControl a b -> Maybe (a -> b) +fromVersion version (VersionControl { versions }) = + Dict.get version versions + + +{-| Provided a list of versions, this function will provide a list of compatible versions to you in your preferred order. + +If you just care about getting the most recent function, you will be better off using `mostRecentFromVersionList`, +but this function can help if you care about knowing which Matrix spec version you're using. + +-} +fromVersionList : List String -> VersionControl a b -> List ( String, a -> b ) +fromVersionList versionList vc = + List.filterMap + (\version -> + vc + |> fromVersion version + |> Maybe.map (\f -> ( version, f )) + ) + versionList + + +{-| Get a dict of all available functions. +-} +toDict : VersionControl a b -> Dict String (a -> b) +toDict (VersionControl d) = + d.versions + + +{-| Get the most recent event based on a list of versions. +-} +mostRecentFromVersionList : List String -> VersionControl a b -> Maybe (a -> b) +mostRecentFromVersionList versionList ((VersionControl { order }) as vc) = + order + |> List.filter (\o -> List.member o versionList) + |> List.filterMap (\v -> fromVersion v vc) + |> List.head + +{-| Sometimes, no changes are needed and a function works just the same as the one in the previous version. +In that case, you can amend with a `sameForVersion` function to indicate that the spec is +identical for this version. +-} +sameForVersion : String -> VersionControl a b -> VersionControl a b +sameForVersion version (VersionControl data) = + VersionControl + { data + | order = version :: data.order + , versions = Dict.insert version data.latestVersion data.versions + } + + +{-| You cannot create an empty VersionControl layer, you must always start with a BottomLayer +and then stack MiddleLayer types on top until you've reached the version that you're happy with. +-} +withBottomLayer : BottomLayer a b -> VersionControl a b +withBottomLayer { current, version } = + VersionControl + { latestVersion = current + , order = List.singleton version + , versions = Dict.singleton version current + }