packages feed

versioning (empty) → 0.1.0.0

raw patch · 8 files changed

+676/−0 lines, 8 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, hspec, versioning

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for version++## 0.1.0.0  -- 2018-06-06++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,202 @@++                                 Apache License+                           Version 2.0, January 2004+                        http://www.apache.org/licenses/++   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION++   1. Definitions.++      "License" shall mean the terms and conditions for use, reproduction,+      and distribution as defined by Sections 1 through 9 of this document.++      "Licensor" shall mean the copyright owner or entity authorized by+      the copyright owner that is granting the License.++      "Legal Entity" shall mean the union of the acting entity and all+      other entities that control, are controlled by, or are under common+      control with that entity. For the purposes of this definition,+      "control" means (i) the power, direct or indirect, to cause the+      direction or management of such entity, whether by contract or+      otherwise, or (ii) ownership of fifty percent (50%) or more of the+      outstanding shares, or (iii) beneficial ownership of such entity.++      "You" (or "Your") shall mean an individual or Legal Entity+      exercising permissions granted by this License.++      "Source" form shall mean the preferred form for making modifications,+      including but not limited to software source code, documentation+      source, and configuration files.++      "Object" form shall mean any form resulting from mechanical+      transformation or translation of a Source form, including but+      not limited to compiled object code, generated documentation,+      and conversions to other media types.++      "Work" shall mean the work of authorship, whether in Source or+      Object form, made available under the License, as indicated by a+      copyright notice that is included in or attached to the work+      (an example is provided in the Appendix below).++      "Derivative Works" shall mean any work, whether in Source or Object+      form, that is based on (or derived from) the Work and for which the+      editorial revisions, annotations, elaborations, or other modifications+      represent, as a whole, an original work of authorship. For the purposes+      of this License, Derivative Works shall not include works that remain+      separable from, or merely link (or bind by name) to the interfaces of,+      the Work and Derivative Works thereof.++      "Contribution" shall mean any work of authorship, including+      the original version of the Work and any modifications or additions+      to that Work or Derivative Works thereof, that is intentionally+      submitted to Licensor for inclusion in the Work by the copyright owner+      or by an individual or Legal Entity authorized to submit on behalf of+      the copyright owner. For the purposes of this definition, "submitted"+      means any form of electronic, verbal, or written communication sent+      to the Licensor or its representatives, including but not limited to+      communication on electronic mailing lists, source code control systems,+      and issue tracking systems that are managed by, or on behalf of, the+      Licensor for the purpose of discussing and improving the Work, but+      excluding communication that is conspicuously marked or otherwise+      designated in writing by the copyright owner as "Not a Contribution."++      "Contributor" shall mean Licensor and any individual or Legal Entity+      on behalf of whom a Contribution has been received by Licensor and+      subsequently incorporated within the Work.++   2. Grant of Copyright License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      copyright license to reproduce, prepare Derivative Works of,+      publicly display, publicly perform, sublicense, and distribute the+      Work and such Derivative Works in Source or Object form.++   3. Grant of Patent License. Subject to the terms and conditions of+      this License, each Contributor hereby grants to You a perpetual,+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable+      (except as stated in this section) patent license to make, have made,+      use, offer to sell, sell, import, and otherwise transfer the Work,+      where such license applies only to those patent claims licensable+      by such Contributor that are necessarily infringed by their+      Contribution(s) alone or by combination of their Contribution(s)+      with the Work to which such Contribution(s) was submitted. If You+      institute patent litigation against any entity (including a+      cross-claim or counterclaim in a lawsuit) alleging that the Work+      or a Contribution incorporated within the Work constitutes direct+      or contributory patent infringement, then any patent licenses+      granted to You under this License for that Work shall terminate+      as of the date such litigation is filed.++   4. Redistribution. You may reproduce and distribute copies of the+      Work or Derivative Works thereof in any medium, with or without+      modifications, and in Source or Object form, provided that You+      meet the following conditions:++      (a) You must give any other recipients of the Work or+          Derivative Works a copy of this License; and++      (b) You must cause any modified files to carry prominent notices+          stating that You changed the files; and++      (c) You must retain, in the Source form of any Derivative Works+          that You distribute, all copyright, patent, trademark, and+          attribution notices from the Source form of the Work,+          excluding those notices that do not pertain to any part of+          the Derivative Works; and++      (d) If the Work includes a "NOTICE" text file as part of its+          distribution, then any Derivative Works that You distribute must+          include a readable copy of the attribution notices contained+          within such NOTICE file, excluding those notices that do not+          pertain to any part of the Derivative Works, in at least one+          of the following places: within a NOTICE text file distributed+          as part of the Derivative Works; within the Source form or+          documentation, if provided along with the Derivative Works; or,+          within a display generated by the Derivative Works, if and+          wherever such third-party notices normally appear. The contents+          of the NOTICE file are for informational purposes only and+          do not modify the License. You may add Your own attribution+          notices within Derivative Works that You distribute, alongside+          or as an addendum to the NOTICE text from the Work, provided+          that such additional attribution notices cannot be construed+          as modifying the License.++      You may add Your own copyright statement to Your modifications and+      may provide additional or different license terms and conditions+      for use, reproduction, or distribution of Your modifications, or+      for any such Derivative Works as a whole, provided Your use,+      reproduction, and distribution of the Work otherwise complies with+      the conditions stated in this License.++   5. Submission of Contributions. Unless You explicitly state otherwise,+      any Contribution intentionally submitted for inclusion in the Work+      by You to the Licensor shall be under the terms and conditions of+      this License, without any additional terms or conditions.+      Notwithstanding the above, nothing herein shall supersede or modify+      the terms of any separate license agreement you may have executed+      with Licensor regarding such Contributions.++   6. Trademarks. This License does not grant permission to use the trade+      names, trademarks, service marks, or product names of the Licensor,+      except as required for reasonable and customary use in describing the+      origin of the Work and reproducing the content of the NOTICE file.++   7. Disclaimer of Warranty. Unless required by applicable law or+      agreed to in writing, Licensor provides the Work (and each+      Contributor provides its Contributions) on an "AS IS" BASIS,+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or+      implied, including, without limitation, any warranties or conditions+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+      PARTICULAR PURPOSE. You are solely responsible for determining the+      appropriateness of using or redistributing the Work and assume any+      risks associated with Your exercise of permissions under this License.++   8. Limitation of Liability. In no event and under no legal theory,+      whether in tort (including negligence), contract, or otherwise,+      unless required by applicable law (such as deliberate and grossly+      negligent acts) or agreed to in writing, shall any Contributor be+      liable to You for damages, including any direct, indirect, special,+      incidental, or consequential damages of any character arising as a+      result of this License or out of the use or inability to use the+      Work (including but not limited to damages for loss of goodwill,+      work stoppage, computer failure or malfunction, or any and all+      other commercial damages or losses), even if such Contributor+      has been advised of the possibility of such damages.++   9. Accepting Warranty or Additional Liability. While redistributing+      the Work or Derivative Works thereof, You may choose to offer,+      and charge a fee for, acceptance of support, warranty, indemnity,+      or other liability obligations and/or rights consistent with this+      License. However, in accepting such obligations, You may act only+      on Your own behalf and on Your sole responsibility, not on behalf+      of any other Contributor, and only if You agree to indemnify,+      defend, and hold each Contributor harmless for any liability+      incurred by, or claims asserted against, such Contributor by reason+      of your accepting any such warranty or additional liability.++   END OF TERMS AND CONDITIONS++   APPENDIX: How to apply the Apache License to your work.++      To apply the Apache License to your work, attach the following+      boilerplate notice, with the fields enclosed by brackets "[]"+      replaced with your own identifying information. (Don't include+      the brackets!)  The text should be enclosed in the appropriate+      comment syntax for the file format. We also recommend that a+      file or class name and description of purpose be included on the+      same "printed page" as the copyright notice for easier+      identification within third-party archives.++   Copyright [yyyy] [name of copyright owner]++   Licensed under the Apache License, Version 2.0 (the "License");+   you may not use this file except in compliance with the License.+   You may obtain a copy of the License at++       http://www.apache.org/licenses/LICENSE-2.0++   Unless required by applicable law or agreed to in writing, software+   distributed under the License is distributed on an "AS IS" BASIS,+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+   See the License for the specific language governing permissions and+   limitations under the License.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Versioning/Base.hs view
@@ -0,0 +1,156 @@+-- | This module provides some tools to encode multiple versions+--   of a data model in a single data-type parametrized by version number.+--   The addition or removal of a field can be expressed through the+--   'Since' and 'Until' type families.+--+--   Example:+--   data Rec v = Rec+--       { foo :: Int               -- this field exists in all versions+--       , bar :: Since V2 v Bool   -- this field has been introduced in V2+--       , baz :: Until V2 v Double -- this field has been removed in V3+--       }+--+--   Besides reducing the number of data declarations,+--   this approach also has other advantages:+--   * It makes migrations declarative and self-documenting.+--   * It allows for less verbose version-upcasting functions,+--     since the fields that have a non-parametric type do not need to be copied.+--   * It is a foundation on which other useful abstractions can be built.+--   However there are also some drawbacks to consider:+--   some classes may require a separate standalone deriving clause for each version.+--   Moreover the usage of type-level computations can make the error messages+--   harder to understand.+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE DeriveGeneric         #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StandaloneDeriving    #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE TypeOperators         #-}+{-# LANGUAGE UndecidableInstances  #-}+module Versioning.Base+  ( V(..)+  , Since+  , Until+  , NA+  , na+  , V1+  , V2+  , V3+  , V4+  , V5+  , V6+  , V7+  , V8+  , V9+  , V10+  , V11+  , V12+  , V13+  , V14+  , V15+  , V16+  , V17+  , V18+  , V19+  , V20+  )+where++import           Data.Aeson   (FromJSON (..), ToJSON (..), Value (..))+import           GHC.Generics (Generic)+import           GHC.TypeNats++-- | The version of a data model+newtype V = V Nat++-- | This allows us to express that a field is only present since+--   a given version.+--   The first parameter is the version in which the field has been introduced,+--   the second parameter is the actual version of the data-type.+type family Since (s :: V) (v :: V) a :: * where+    Since ('V 1) ('V v) a = a+    Since ('V s) ('V 1) a = NA+    Since ('V s) ('V v) a = Since ('V (s - 1)) ('V (v - 1)) a++-- | This allows us to express that a field is only present until+--   a given version.+--   The first parameter is the last version in which the field is present,+--   the second parameter is the actual version of the data-type.+type family Until (u :: V) (v :: V) a :: * where+    Until ('V u) ('V 1) a = a+    Until ('V 1) ('V v) a = NA+    Until ('V u) ('V v) a = Until ('V (u - 1)) ('V (v - 1)) a++-- | A type indicating absence.+--   The 'Maybe' is a hack needed to let aeson parse a record even+--   if a field is missing.+type NA = Maybe Bare++-- | A placeholder for an absent value.+na :: NA+na = Nothing++-- | An uninhabited type.+--   We define our own type instead of using "Data.Void"+--   because we need additional instances.+--   Moreover this type is hidden to force users to use 'NA',+--   which is needed because of the 'Maybe' hack.+data Bare++deriving instance Eq Bare++deriving instance Generic Bare++deriving instance Show Bare++-- Attempting to supply a value for an absent field must produce a+-- parsing failure+instance FromJSON Bare where+    parseJSON _ = fail "An NA field should be absent or null"++-- We provide an instance to make the compiler happy.+-- In practice it will never be used.+instance ToJSON Bare where+    toJSON _ = Null++-- Handy type synonyms to minimize parenthesis+type V1 = 'V 1++type V2 = 'V 2++type V3 = 'V 3++type V4 = 'V 4++type V5 = 'V 5++type V6 = 'V 6++type V7 = 'V 7++type V8 = 'V 8++type V9 = 'V 9++type V10 = 'V 10++type V11 = 'V 11++type V12 = 'V 12++type V13 = 'V 13++type V14 = 'V 14++type V15 = 'V 15++type V16 = 'V 16++type V17 = 'V 17++type V18 = 'V 18++type V19 = 'V 19++type V20 = 'V 20
+ src/Versioning/JSON.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE AllowAmbiguousTypes   #-}+{-# LANGUAGE ConstraintKinds       #-}+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes            #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE TypeInType            #-}+{-# LANGUAGE TypeOperators         #-}+{-# LANGUAGE UndecidableInstances  #-}+module Versioning.JSON+  ( Applied+  , DecodeAnyVersion+  , WithAnyVersion+  , decodeAnyVersion+  , withAnyVersion+  , withAnyVersionM+  )+where++import           Control.Applicative   ((<|>))+import           Data.Aeson            (FromJSON, decode)+import qualified Data.ByteString.Lazy  as LazyBS+import           Data.Functor.Identity (Identity (..))+import           Data.Kind             (Constraint, Type)+import           GHC.TypeLits++import           Versioning.Base+import           Versioning.Upgrade++-- | Decode a JSON string by trying all the versions decrementally+--   and upgrade the decoded object to the newest version.+decodeAnyVersion+  :: forall v a . DecodeAnyVersion v v a => LazyBS.ByteString -> Maybe (a v)+decodeAnyVersion = decodeAnyVersion' @v @v++-- | Decode a JSON string by trying all the versions decrementally+--   and apply an action to the decoded object at its original version.+withAnyVersionM+  :: forall c v a m+   . (WithAnyVersion v a c, Applicative m, c a v)+  => ApplyM m a c+  -> LazyBS.ByteString+  -> m (Maybe (Applied c a))+withAnyVersionM = withAnyVersion' @v @a @c++-- | Pure version of 'withAnyVersionM'.+withAnyVersion+  :: forall c v a+   . (WithAnyVersion v a c, c a v)+  => Apply a c+  -> LazyBS.ByteString+  -> Maybe (Applied c a)+withAnyVersion action = runIdentity . withAnyVersionM @c @v @a (Identity . action)++-- | The result type of the action that has been applied to the decoded object+--   with 'withAnyVersion'.+type family Applied (c :: (V -> Type) -> V -> Constraint) (a :: V -> Type) :: Type++type ApplyM m a c = forall v. c a v => a v -> m (Applied c a)++type Apply a c = forall v. c a v => a v -> Applied c a++class DecodeAnyVersion (v :: V) (w :: V) (a :: V -> Type) where+    decodeAnyVersion' :: LazyBS.ByteString -> Maybe (a w)++instance {-# OVERLAPPING #-} (FromJSON (a V1), Upgrade V1 w a) => DecodeAnyVersion V1 w a where+    decodeAnyVersion' bs = upgrade @V1 @w <$> decode @(a V1) bs++instance {-# OVERLAPPABLE #-} (DecodeAnyVersion (Decr v V1) w a, FromJSON (a v), FromJSON (a (Decr v V1)), Upgrade v w a)+  => DecodeAnyVersion v w a where+    decodeAnyVersion' bs = upgrade @v @w <$> decode @(a v) bs+                       <|> decodeAnyVersion' @(Decr v V1) @w bs++class WithAnyVersion (v :: V) (a :: V -> Type) c where+    withAnyVersion' :: (Applicative m, c a v) => ApplyM m a c -> LazyBS.ByteString -> m (Maybe (Applied c a))++instance {-# OVERLAPPING #-} (FromJSON (a V1), c a V1) => WithAnyVersion V1 a c where+    withAnyVersion' action bs = case decode @(a V1) bs of+        Just doc -> Just <$> action doc+        Nothing  -> pure Nothing++instance {-# OVERLAPPABLE #-} (WithAnyVersion (Decr v V1) a c, FromJSON (a v), FromJSON (a (Decr v V1)), c a v, c a (Decr v V1))+  => WithAnyVersion v a c where+    withAnyVersion' action bs = case decode @(a v) bs of+        Just doc -> Just <$> action doc+        Nothing  -> withAnyVersion' @(Decr v V1) @a @c action bs++-- | Decrement version until the target version is reached+type family Decr (c :: V) (v :: V) :: V where+    Decr ('V c) ('V v) = Decr' (CmpNat c v) ('V c)++type family Decr' (o :: Ordering) (c :: V) :: V where+    Decr' 'GT ('V c) = 'V (c - 1)+    Decr' 'EQ ('V c) = 'V c+    Decr' 'LT ('V c) = TypeError+                       ( 'Text "Cannot decrement "+                   ':<>: 'ShowType ('V c)+                   ':<>: 'Text " because the target is a higher version"+                       )
+ src/Versioning/Upgrade.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE TypeOperators         #-}+{-# LANGUAGE UndecidableInstances  #-}+module Versioning.Upgrade+  ( Adapt(..)+  , Upgrade(..)+  , upgrade+  )+where++import           Data.Kind       (Type)+import           GHC.TypeLits++import           Versioning.Base++-- | Adapt from a version to another+class Adapt (v :: V) (w :: V) (a :: V -> Type) where+    adapt :: a v -> a w++-- | Upgrade from a lower to a higher version by calling 'adapt' on all+--   the intermediary steps.+upgrade :: Upgrade v w a => a v -> a w+upgrade = upgrade'++-- | Upgrade from a lower to a higher version by calling 'adapt' on all+--   the intermediary steps.+--   You do not need to define any instance.+--   They are derived automatically if all the intermediary+--   'Adapt' instances are defined.+class Upgrade (v :: V) (w :: V) (a :: V -> Type) where+    upgrade' :: a v -> a w++instance {-# OVERLAPS #-} Upgrade v v a where+    upgrade' x = x++instance {-# OVERLAPPABLE #-} (Adapt v (Incr v w) a, Upgrade (Incr v w) w a)+  => Upgrade v w a where+    upgrade' x = upgrade' @(Incr v w) @w (adapt @v @(Incr v w) x)++-- | Increment version until the target version is reached+type family Incr (c :: V) (v :: V) :: V where+    Incr ('V c) ('V v) = Incr' (CmpNat c v) ('V c)++type family Incr' (o :: Ordering) (c :: V) :: V where+    Incr' 'LT ('V c) = 'V (c + 1)+    Incr' 'EQ ('V c) = 'V c+    Incr' 'GT ('V c) = TypeError+                       ( 'Text "Cannot increment "+                   ':<>: 'ShowType ('V c)+                   ':<>: 'Text " because the target is a lower version"+                       )
+ tests/Main.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE DeriveGeneric         #-}+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE KindSignatures        #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE StandaloneDeriving    #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE TypeSynonymInstances  #-}+{-# LANGUAGE UndecidableInstances  #-}+module Main where++import           Data.Aeson+import qualified Data.ByteString.Lazy  as LazyBS+import           GHC.Generics          (Generic)+import           Test.Hspec+import           Versioning.Base+import           Versioning.JSON+import           Versioning.Upgrade++main :: IO ()+main = hspec $ do+    describe "Upgrade" $ do+        it "Can upgrade across two versions" $+            upgrade @V1 foo1 `shouldBe` foo3++    describe "DecodeAnyVersion" $ do+        it "Can decode from V1" $+            decodeAnyVersion @V3 fooJsonV1 `shouldBe` Just foo3++    describe "DecodeAnyVersion" $ do+        it "Can decode from V3" $+            decodeAnyVersion @V3 fooJsonV3 `shouldBe` Just foo3++    describe "WithAnyVersion" $ do+        -- Decode a Foo and return its string representation without upgrading it+        it "Can apply a function on the decoded object" $ do+            let Just res = withAnyVersion @ShowAnyVersion @V3 @Foo showAnyVersion fooJsonV1+            res `shouldBe` show foo1++data Foo v = Foo+    { always  :: Int               -- this field exists in all versions+    , sinceV2 :: Since V2 v Bool   -- this field has been introduced in V2+    , sinceV3 :: Since V3 v String -- this field has been introduced in V3+    , untilV2 :: Until V2 v Double -- this field has been removed in V3+    } deriving (Generic)++deriving instance Eq (Foo V1)++deriving instance Eq (Foo V2)++deriving instance Eq (Foo V3)++instance FromJSON (Foo V1)++instance FromJSON (Foo V2)++instance FromJSON (Foo V3)++deriving instance Show (Foo V1)++deriving instance Show (Foo V2)++deriving instance Show (Foo V3)++-- How to upcast from V1 to V2+instance Adapt V1 V2 Foo where+    adapt foo = foo { sinceV2 = True+                    , sinceV3 = na+                    , untilV2 = untilV2 foo+                    }++-- How to upcast from V2 to V3+instance Adapt V2 V3 Foo where+    adapt foo = foo { sinceV2 = sinceV2 foo+                    , sinceV3 = "hello"+                    , untilV2 = na+                    }++type instance Applied ShowAnyVersion a = String++-- | Show an object, whatever its version+class ShowAnyVersion (a :: V -> *) (v :: V) where+    showAnyVersion :: a v -> String++instance Show (Foo v) => ShowAnyVersion Foo v where+    showAnyVersion = show++-- | A 'Foo' at version V1+foo1 :: Foo V1+foo1 = Foo+    { always = 1+    , sinceV2 = na+    , sinceV3 = na+    , untilV2 = 3.14+    }++-- | A 'Foo' at version V3+foo3 :: Foo V3+foo3 = Foo+    { always = 1+    , sinceV2 = True+    , sinceV3 = "hello"+    , untilV2 = Nothing+    }++fooJsonV1 :: LazyBS.ByteString+fooJsonV1 = "{\"always\":1, \"untilV2\": 3.14}"++fooJsonV3 :: LazyBS.ByteString+fooJsonV3 = "{\"always\":1, \"sinceV2\": true, \"sinceV3\": \"hello\"}"
+ versioning.cabal view
@@ -0,0 +1,38 @@+name:                versioning+version:             0.1.0.0+synopsis:            Type-safe data versioning.+description:         This package provides various tools to deal with+                     data versioning in a type-safe way.+homepage:            https://github.com/lortabac/versioning+license:             Apache-2.0+license-file:        LICENSE+author:              Lorenzo Tabacchini+maintainer:          lortabac@gmx.com+copyright:           (c) 2018 Lorenzo Tabacchini+category:            Data+build-type:          Simple+extra-source-files:  ChangeLog.md+cabal-version:       >=1.10++library+  exposed-modules:     Versioning.Base+                     , Versioning.Upgrade+                     , Versioning.JSON+  build-depends:       base >=4.11 && <4.12+                     , aeson >=1.0 && <1.5+                     , bytestring >=0.10 && <0.11+  hs-source-dirs:      src+  default-language:    Haskell2010+  ghc-options:       -Wall++test-suite tests+  type:                exitcode-stdio-1.0+  main-is:             Main.hs+  build-depends:       base >=4.11 && <4.12+                     , aeson >=1.0 && <1.5+                     , bytestring >=0.10 && <0.11+                     , hspec >=2.0 && <2.6+                     , versioning+  hs-source-dirs:      tests+  default-language:    Haskell2010+  ghc-options:       -Wall