@@ -4,43 +4,62 @@
4{-# OPTIONS_GHC -Wno-orphans #-}
4{-# OPTIONS_GHC -Wno-orphans #-}
6module Data.Id
6module Data.Id
7− ( Id(..)
7+ ( Id, mkId, unId, unsafeIdTagConvert, coerceId, _Id, nilId
8− , coerceId
8+ , IntId, mkIntId, unIntId, unsafeIntIdTagConvert, coerceIntId, _IntId
9− , nilId
9+ , Name, mkName, unName, unsafeNameTagConvert, coerceName, _Name
13import Control.DeepSeq (NFData)
13import Control.DeepSeq (NFData)
14import Control.Lens
14import Control.Lens
15import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)
16import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)
16import Data.Binary (Binary)
17import Data.Binary (Binary)
17import Data.Coerce (coerce)
18import Data.Coerce (coerce)
18import Data.Data
19import Data.Data
19import Data.Hashable (Hashable)
20import Data.Hashable (Hashable)
20import Data.OpenApi
21import Data.OpenApi
21import Data.UUID (UUID)
24import Data.UUID (UUID)
22import qualified Data.UUID as UUID
25import qualified Data.UUID as UUID
23#ifndef ghcjs_HOST_OS
26#ifndef ghcjs_HOST_OS
24−import Database.PostgreSQL.Simple.FromField (FromField)
27+import Database.PostgreSQL.Simple.FromField as PG (FromField)
25−import Database.PostgreSQL.Simple.ToField (ToField)
28+import Database.PostgreSQL.Simple.ToField as PG (ToField)
29+import Data.Csv as Csv hiding(Name)
27−import GHC.Generics (Generic)
31+import Flat as F
28import Test.QuickCheck
33import Test.QuickCheck
29import Web.HttpApiData
34import Web.HttpApiData
30import Web.PathPieces (PathPiece(..))
35import Web.PathPieces (PathPiece(..))
32newtype Id t = Id { unId :: UUID }
37newtype Id t = Id { unId :: UUID }
33− deriving
38+ deriving stock Data
34− ( Eq, Ord, Generic, Read, Show, Data
39+ deriving newtype
35#ifndef ghcjs_HOST_OS
41#ifndef ghcjs_HOST_OS
36− , ToField, FromField
42+ , PG.ToField, PG.FromField
38− , PathPiece, FromJSON
39− , ToJSON, NFData, Hashable, FromJSONKey, ToJSONKey, ToSchema, ToParamSchema
44+ , FromJSON, ToJSON, NFData, Hashable, FromJSONKey, ToJSONKey, ToSchema
40− , FromHttpApiData, ToHttpApiData )
45+ , ToParamSchema, FromHttpApiData, ToHttpApiData )
42type role Id nominal
47type role Id nominal
51+mkId :: forall s. UUID -> Id s
55+instance KnownSymbol s => Show (Id s) where
56+ show (Id v) = "Id-" <> symbolVal (Proxy @s) <> "-" <> show v
58+instance Flat (Id s) where
59+ encode = F.encode . UUID.toWords . coerce
60+ decode = coerce . (\(a,b,c,d) -> UUID.fromWords a b c d) <$> F.decode
61+ size = F.size . UUID.toWords . coerce
44instance Arbitrary (Id t) where
63instance Arbitrary (Id t) where
45 arbitrary = fmap Id $ UUID.fromWords
64 arbitrary = fmap Id $ UUID.fromWords
46 <$> arbitrary
65 <$> arbitrary
@@ -48,20 +67,29 @@ instance Arbitrary (Id t) where
48 <*> arbitrary
67 <*> arbitrary
49 <*> arbitrary
68 <*> arbitrary
51−instance Binary (Id t) where
70+instance PathPiece (Id t) where
71+ fromPathPiece = coerce . UUID.fromText
72+ toPathPiece = UUID.toText . coerce
53−instance PathPiece UUID.UUID where
76+instance Csv.FromField (Id tag) where
54− fromPathPiece = UUID.fromText
77+ parseField = parseField
55− toPathPiece = UUID.toText
78+ >=> fmap mkId . maybe (fail "invalid UUID in CSV") pure . UUID.fromText
57−makePrisms ''Id
80+instance Csv.ToField (Id tag) where
81+ toField = Csv.toField . UUID.toText . coerce
59-- | This is a more \"explicit\" 'coerce' specifically for 'Id'.
85-- | This is a more \"explicit\" 'coerce' specifically for 'Id'.
60-- You are forced to explicitly specify the phantom types you are converting
86-- You are forced to explicitly specify the phantom types you are converting
61-- via the @TypeApplications@ compiler extension.
87-- via the @TypeApplications@ compiler extension.
62−coerceId :: forall a b. Id (Ambiguous a) -> Id (Ambiguous b)
88+coerceId, unsafeIdTagConvert :: forall a b. Id (Ambiguous a) -> Id (Ambiguous b)
63coerceId = coerce
89coerceId = coerce
64{-# INLINE coerceId #-}
90{-# INLINE coerceId #-}
91+unsafeIdTagConvert = coerce
92+{-# INLINE unsafeIdTagConvert #-}
66-- | Id corresponding to UUID nil. Useful for testing etc.
94-- | Id corresponding to UUID nil. Useful for testing etc.
67nilId :: forall a. Id (Ambiguous a)
95nilId :: forall a. Id (Ambiguous a)
@@ -70,3 +98,72 @@ nilId = Id UUID.nil
70-- https://kcsongor.github.io/ambiguous-tags/
98-- https://kcsongor.github.io/ambiguous-tags/
71type family Ambiguous (a :: k) :: j where
99type family Ambiguous (a :: k) :: j where
72 Ambiguous x = x
100 Ambiguous x = x
102+----------------- IntId
103+newtype IntId t = IntId { unIntId :: Integer }
107+#ifndef ghcjs_HOST_OS
108+ , PG.ToField, PG.FromField, Csv.ToField, Csv.FromField
110+ , FromJSON, ToJSON, NFData, Hashable, FromJSONKey, ToJSONKey, ToSchema
111+ , ToParamSchema, FromHttpApiData, ToHttpApiData, PathPiece, Flat, Arbitrary
112+ , Num, Integral, Real, Enum )
114+type role IntId nominal
118+mkIntId :: forall s. Integer -> IntId s
120+{-# INLINE mkIntId #-}
122+instance KnownSymbol s => Show (IntId s) where
123+ show (IntId v) = "IntId-" <> symbolVal (Proxy @s) <> "-" <> show v
125+-- | This is a more \"explicit\" 'coerce' specifically for 'IntId'.
126+-- You are forced to explicitly specify the phantom types you are converting
127+-- via the @TypeApplications@ compiler extension.
128+coerceIntId, unsafeIntIdTagConvert
129+ :: forall a b. IntId (Ambiguous a) -> IntId (Ambiguous b)
131+{-# INLINE coerceIntId #-}
132+unsafeIntIdTagConvert = coerce
133+{-# INLINE unsafeIntIdTagConvert #-}
135+----------------- Name
136+newtype Name t = Name { unName :: Text }
140+#ifndef ghcjs_HOST_OS
141+ , PG.ToField, PG.FromField, Csv.ToField, Csv.FromField
143+ , FromJSON, ToJSON, NFData, Hashable, FromJSONKey, ToJSONKey, ToSchema
144+ , ToParamSchema, FromHttpApiData, ToHttpApiData, PathPiece, Flat
145+ , IsString, Semigroup, Monoid )
147+type role Name nominal
151+mkName :: forall s. Text -> Name s
153+{-# INLINE mkName #-}
155+instance KnownSymbol s => Show (Name s) where
156+ show (Name v) = "Name-" <> symbolVal (Proxy @s) <> "-" <> show v
158+instance Arbitrary (Name s) where
159+ arbitrary = coerce . T.pack . getPrintableString <$> arbitrary
161+-- | This is a more \"explicit\" 'coerce' specifically for 'Name'.
162+-- You are forced to explicitly specify the phantom types you are converting
163+-- via the @TypeApplications@ compiler extension.
164+coerceName, unsafeNameTagConvert
165+ :: forall a b. Name (Ambiguous a) -> Name (Ambiguous b)
167+{-# INLINE coerceName #-}
168+unsafeNameTagConvert = coerce
169+{-# INLINE unsafeNameTagConvert #-}