on 871cbf3 help sign in
9 files changed+5−202
since splitsnapshots
unifiedsplit
README.org−25 bucks.cabal−31 src/Data/Money.hs+2−6 src/Data/Money/Currencies.hs−6 src/Data/Money/Internal.hs+2−21 src/Data/Money/OpenApi.hs−22 src/Data/Money/Rel8.hs+1−3 stack.yaml−1 test/Rel8Spec.hs−87
README.orgdeleted−25
@@ -1,25 +0,0 @@
1−#+title: Bucks library README
2−#+author: Me, who else?
3−
4−* What is even going on here?
5−
6−Bucks is a new and shiny money handling library. I made it, because we have five
7−different 'Money' type implementations and they are all slightly different from
8−each other. Therefore, making a new one and pulling the best parts of those old
9−makes a whole lot more sense!
10−
11−* But how do I use it?
12−
13−Add 'bucks' to your .cabal file and maybe to your stack.yaml as well. You're a
14−big boy, you know the drill.
15−
16−* Features
17−
18−Things that require chonky third-party dependencies are hidden behind flags.
19−Want aeson instances? Enable the flag. Want Rel8 for your database shenanigans?
20−Enable the flag! Want something else? Add that thing, make a flag for it, and
21−don't forget to have fun!
22−
23−* You forgot feature X..
24−
25−And you're welcome to add it :)
bucks.cabal−31
@@ -10,20 +10,11 @@ maintainer: nick@typeable.io
1010category: Data
1111build-type: Simple
1212
13−source-repository head
14− type: git
15− location: https://github.com/typeable/bucks.git
16−
1713flag aeson
1814 description: Enable Aeson instances
1915 default: False
2016 manual: True
2117
22−flag openapi
23− description: Enable OpenAPI instances
24− default: False
25− manual: True
26−
2718flag rel8
2819 description: Enable Rel8 support
2920 default: False
@@ -42,31 +33,9 @@ library
4233 if flag(aeson)
4334 other-modules: Data.Money.JSON
4435 build-depends: aeson >= 2.0 && < 3.0
45− cpp-options: -DUSE_AESON
46− if flag(openapi)
47− other-modules: Data.Money.OpenApi
48− build-depends: openapi3
49− cpp-options: -DUSE_OPENAPI
5036 if flag(rel8)
5137 other-modules: Data.Money.Rel8
5238 build-depends: rel8 >= 1.4,
5339 scientific
54− cpp-options: -DUSE_REL8
5540 hs-source-dirs: src
5641 default-language: Haskell2010
57−
58−test-suite rel8-test
59− import: warnings
60− type: exitcode-stdio-1.0
61− hs-source-dirs: test
62− main-is: Rel8Spec.hs
63− build-depends: base >= 4.14 && < 5.0,
64− bucks,
65− hasql,
66− hspec,
67− rel8
68− default-language: Haskell2010
69− if flag(rel8)
70− buildable: True
71− else
72− buildable: False
src/Data/Money.hs+2−6
@@ -1,15 +1,11 @@
11{-# LANGUAGE CPP #-}
22module Data.Money (module Export) where
33
4−import Data.Money.Currencies as Export
54import Data.Money.Internal as Export
65
7−#ifdef USE_AESON
6+#ifdef flag_AESON
87import Data.Money.JSON as Export ()
98#endif
10−#ifdef USE_OPENAPI
11−import Data.Money.OpenApi as Export ()
12−#endif
13−#ifdef USE_REL8
9+#ifdef flag_REL8
1410import Data.Money.Rel8 as Export ()
1511#endif
src/Data/Money/Currencies.hs−6
@@ -11,9 +11,3 @@ type GBP = Currency "GBP" -- Good Boy Points lmao xDDDDD
1111type RUB = Currency "RUB"
1212type CNY = Currency "CNY"
1313type XBT = Currency "XBT"
14−type DKK = Currency "DKK"
15−type NOK = Currency "NOK"
16−type PLN = Currency "PLN"
17−type SEK = Currency "SEK"
18−type AUD = Currency "AUD"
19−type NZD = Currency "NZD"
src/Data/Money/Internal.hs+2−21
@@ -1,6 +1,5 @@
11{-# LANGUAGE AllowAmbiguousTypes #-}
22{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE DeriveDataTypeable #-}
43{-# LANGUAGE DeriveGeneric #-}
54{-# LANGUAGE KindSignatures #-}
65{-# LANGUAGE ScopedTypeVariables #-}
@@ -8,29 +7,19 @@
87module Data.Money.Internal ( Money(..)
98 , Currency
109 , KnownCurrency(..)
11− , (*%)
12− , (/%)
1310 ) where
1411
15−import Data.Data
1612import Data.Fixed (Centi)
1713import Data.Group
14+import Data.Proxy
1815import GHC.Generics
1916import GHC.TypeLits
20−import GHC.Stack (HasCallStack)
2117
22−-- | Money type. This is actually 'Centi' from 'Data.Fixed' in disguise.
23−-- The type parameter is phantom, you probably want to use 'Currency' there.
24−-- See also 'Data.Money.Currencies' for currency types.
2518newtype Money curr = Money { getAmount :: Centi }
26− deriving (Eq, Ord, Generic, Data)
19+ deriving (Eq, Ord, Generic)
2720
28−-- | Empty type, used only to carry its type parameter with a currency code around. E.g. 'Currency "USD"', 'Currency "GBP"', et cetera.
29−-- See Data.Money.Currencies for pre-defined currency types.
3021data Currency (name :: Symbol)
31− deriving (Generic, Data)
3222
33−-- | Statically known currency type class.
3423class KnownCurrency c where
3524 currencyCode :: String
3625
@@ -60,11 +49,3 @@ instance KnownCurrency c => Read (Money c) where
6049 | otherwise = []
6150 where expCurr = currencyCode @c <> " "
6251 l = length expCurr
63−
64−(*%) :: Money curr -> Centi -> Money curr
65−(Money m) *% pct = Money (m * pct)
66−
67−(/%) :: HasCallStack => Money curr -> Centi -> Money curr
68−_ /% 0 = error "Called /% with 0 divisor"
69−(Money m) /% pct = Money (fromRational r)
70− where r = toRational m / toRational pct
src/Data/Money/OpenApi.hsdeleted−22
@@ -1,22 +0,0 @@
1−{-# OPTIONS_GHC -Wno-orphans #-}
2−{-# LANGUAGE FlexibleContexts #-}
3−{-# LANGUAGE MonoLocalBinds #-}
4−{-# LANGUAGE OverloadedLists #-}
5−{-# LANGUAGE OverloadedStrings #-}
6−module Data.Money.OpenApi where
7−
8−import Data.Fixed
9−import Data.Money.Internal
10−import Data.OpenApi
11−import Data.Typeable
12−
13−instance (KnownCurrency c, Typeable (Money c)) => ToSchema (Money c) where
14− declareNamedSchema _ = do
15− centiSchema <- declareSchemaRef (Proxy :: Proxy Centi)
16− strSchema <- declareSchemaRef (Proxy :: Proxy String)
17− return $ NamedSchema (Just "Money") $
18− mempty { _schemaType = Just OpenApiObject
19− , _schemaProperties = [ ("currency", strSchema)
20− , ("amount", centiSchema) ]
21− , _schemaRequired = [ "currency", "amount" ]
22− }
src/Data/Money/Rel8.hs+1−3
@@ -7,13 +7,11 @@ import Data.Money.Internal
77import Data.Scientific
88import Rel8
99
10−import Debug.Trace
11−
1210instance DBType (Money curr) where
1311 typeInformation =
1412 let sci = typeInformation @Scientific
1513 in TypeInformation { encode = encode sci . realToFrac . getAmount
1614 , decode = fmap (Money . realToFrac) (decode sci)
17− , typeName = "decimal"
15+ , typeName = "money"
1816 }
1917
stack.yaml−1
@@ -6,7 +6,6 @@ packages:
66flags:
77 bucks:
88 aeson: true
9− openapi: true
109 rel8: true
1110
1211nix:
test/Rel8Spec.hsdeleted−87
@@ -1,87 +0,0 @@
1−{-# LANGUAGE DeriveAnyClass #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE DuplicateRecordFields #-}
4−{-# LANGUAGE FlexibleInstances #-}
5−{-# LANGUAGE ImportQualifiedPost #-}
6−{-# LANGUAGE OverloadedStrings #-}
7−{-# OPTIONS_GHC -Wno-orphans #-}
8−module Main where
9−
10−import Control.Exception
11−import Data.Money
12−import GHC.Generics
13−import Hasql.Connection (Connection, ConnectionError)
14−import Hasql.Connection qualified as Hasql
15−import Hasql.Session qualified as Hasql
16−import Rel8
17−import Test.Hspec
18−
19−instance Exception ConnectionError
20−
21−data Exc = Exc String
22− deriving (Show)
23−
24−instance Exception Exc
25−
26−data Bucks f = Bucks { amount :: f (Money USD)
27− } deriving (Generic, Rel8able)
28−
29−bucksSchema :: TableSchema (Bucks Name)
30−bucksSchema = TableSchema { schema = Nothing
31− , name = "bucks"
32− , columns = Bucks { amount = "amount" }
33− }
34−
35−dbsets :: Hasql.Settings
36−dbsets = Hasql.settings "10.233.1.2" 5432 "bucks" "password" "bucks"
37−
38−throwLeft :: (Exception e, Applicative f) => Either e a -> f a
39−throwLeft (Left e) = throw e
40−throwLeft (Right a) = pure a
41−
42−onLeft :: Applicative f => Either a b -> (a -> f b) -> f b
43−onLeft (Left a) f = f a
44−onLeft (Right b) _ = pure b
45−
46−createConnection :: IO Connection
47−createConnection = throwLeft =<< Hasql.acquire dbsets
48−
49−createTable :: Connection -> IO ()
50−createTable conn = throwLeft =<< Hasql.run sql conn
51− where sql = Hasql.sql "CREATE TABLE bucks (amount decimal not null);"
52−
53−dropTable :: Connection -> IO ()
54−dropTable conn = throwLeft =<< Hasql.run sql conn
55− where sql = Hasql.sql "DROP TABLE IF EXISTS bucks;"
56−
57−truncateTable :: Connection -> IO ()
58−truncateTable conn = throwLeft =<< Hasql.run sql conn
59− where sql = Hasql.sql "TRUNCATE bucks;"
60−
61−insertMoney :: Connection -> Money USD -> IO ()
62−insertMoney conn m = throwLeft =<< Hasql.run (Hasql.statement () (insert ins)) conn
63− where ins = Insert { into = bucksSchema
64− , rows = values [ Bucks (lit m) ]
65− , onConflict = Abort
66− , returning = pure ()
67− }
68−
69−fetchMoney :: Connection -> IO (Money USD)
70−fetchMoney conn = f =<< throwLeft =<< Hasql.run (Hasql.statement () st) conn
71− where st = select $ do b <- each bucksSchema
72− return (amount b)
73− f [] = throw (Exc "Zero entries returned")
74− f [x] = pure x
75− f _ = throw (Exc "Multiple entries returned")
76−
77−main :: IO ()
78−main = do conn <- createConnection
79− dropTable conn
80− createTable conn
81− hspec $ before_ (truncateTable conn) $ do
82−
83− describe "Database tests" $ do
84− it "insert == select" $ do
85− let m = Money 12.23
86− insertMoney conn m
87− fetchMoney conn `shouldReturn` m