@@ -1,151 +1,179 @@
1{-# LANGUAGE AllowAmbiguousTypes #-}
1{-# LANGUAGE AllowAmbiguousTypes #-}
2−{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE OverloadedLists #-}
3−{-# LANGUAGE InstanceSigs #-}
3+{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE UndecidableInstances #-}
5{-# OPTIONS_GHC -fprint-explicit-kinds #-}
4{-# OPTIONS_GHC -fprint-explicit-kinds #-}
7module Data.Schematic.Schema where
6module Data.Schematic.Schema where
9−import Data.Functor.Foldable
8+import Control.Applicative
9+import Control.Category ((<<<), (>>>))
11+import Data.Aeson as J
12+import Data.Aeson.Types as J
13+import Data.Eq.Deriving (deriveEq1)
14+import Data.Foldable as F
15+import Data.Functor.Classes
16+import Data.HashMap.Strict as H
10−import Data.Kind hiding (Type)
17+import Data.Kind
19+import Data.Scientific
20+import Data.Singletons.Decide
21+import Data.Singletons.Prelude.List
22+import Data.Singletons.TH
23+import Data.Singletons.TypeLits
25+import Data.Vector as V
26+import Data.Vinyl hiding (Dict)
11−import Data.Proxy
27+import Data.Vinyl.Functor
12−import Data.Schematic.Validation
13−import Data.Text (Text)
14−import GHC.Generics
28+import GHC.Generics (Generic)
15−import GHC.TypeLits (KnownNat, natVal, KnownSymbol, symbolVal, Symbol, Nat)
29+import Test.SmallCheck.Series
16−
30+import Text.Show.Deriving (deriveShow1)
18−type family JT (s :: k) :: JType where
33+type family CRepr (s :: Schema) :: Type where
19− JT (SNumber cs) = 'JNumber
34+ CRepr (SchemaText cs) = TextConstraint
20− JT (SText cs) = 'JText
35+ CRepr (SchemaNumber cs) = NumberConstraint
21− JT (SObject els) = 'JObject
36+ CRepr (SchemaObject fs) = (String, Schema)
22− JT (SArray cs s) = 'JArray
37+ CRepr (SchemaArray ar s) = ArrayConstraint
23− JT (Field field ty cs) = JT ty
38+
24−
39+data TextConstraint
25−class Constrained (ty :: JType) c where
40+ = TEq Nat
26− cRep :: Proxy ty -> Proxy c -> CRepr ty
41+ | TLe Nat
28−-- Validators
43+ | Regex Symbol
29−
44+ deriving (Generic)
30−data LengthEqNat nat
45+
31−
46+data instance Sing (tc :: TextConstraint) where
32−instance KnownNat nat => Constrained 'JText (LengthEqNat nat) where
47+ STextLengthEq :: Sing n -> Sing (TEq n)
33− cRep _ _ = LengthEq $ natVal (Proxy @nat)
48+ STextLengthLe :: Sing n -> Sing (TLe n)
34−
49+ STextLengthGt :: Sing n -> Sing (TGt n)
36−
51+instance Eq (Sing (TEq n)) where a == b = True
37−instance KnownNat nat => Constrained 'JNumber (Gt nat) where
52+instance Eq (Sing (TLe n)) where a == b = True
38− cRep _ _ = Gt $ natVal (Proxy @nat)
53+instance Eq (Sing (TGt n)) where a == b = True
40−instance KnownNat nat => Constrained 'JArray (LengthEqNat nat) where
55+data NumberConstraint
41− cRep _ _ = LengthArrEq $ natVal (Proxy @nat)
56+ = NLe Nat
43−-- /Validators
58+ | NEq Nat
44−
59+ deriving (Generic)
45−data Field field ty schema
60+
46−
61+data instance Sing (nc :: NumberConstraint) where
47−data Empty
62+ SNumberEq :: Sing n -> Sing (NEq n)
48−
63+ SNumberGt :: Sing n -> Sing (NGt n)
49−class Verifiable ty cs where
64+ SNumberLe :: Sing n -> Sing (NLe n)
50− schema :: Proxy ty -> Proxy cs -> [CRepr ty]
65+
52−instance
66+instance Eq (Sing (NEq n)) where a == b = True
53− ( Constrained 'JText c
67+instance Eq (Sing (NLe n)) where a == b = True
54− , Verifiable 'JText cs )
68+instance Eq (Sing (NGt n)) where a == b = True
55− => Verifiable 'JText (c ': cs) where
69+
56− schema _ _ = cRep (Proxy @'JText) (Proxy @c)
70+data ArrayConstraint
57− : schema (Proxy @'JText) (Proxy @cs)
71+ = AEq Nat
74+data instance Sing (ac :: ArrayConstraint) where
75+ SArrayEq :: Sing n -> Sing (AEq n)
77+instance Eq (Sing (AEq n)) where a == b = True
80+ = SchemaText [TextConstraint]
81+ | SchemaNumber [NumberConstraint]
82+ | SchemaObject [(Symbol, Schema)]
83+ | SchemaArray [ArrayConstraint] Schema
87+data instance Sing (schema :: Schema) where
88+ SSchemaText :: Sing tcs -> Sing (SchemaText tcs)
89+ SSchemaNumber :: Sing ncs -> Sing (SchemaNumber ncs)
90+ SSchemaArray :: Sing acs -> Sing schema -> Sing (SchemaArray acs schema)
91+ SSchemaObject :: Sing fields -> Sing (SchemaObject fields)
92+ SSchemaNull :: Sing SchemaNull
94+instance Eq (Sing (SchemaText cs)) where a == b = True
95+instance Eq (Sing (SchemaNumber cs)) where a == b = True
96+instance Eq (Sing SchemaNull) where a == b = True
97+instance Eq (Sing (SchemaArray as s)) where a == b = True
98+instance Eq (Sing (SchemaObject cs)) where a == b = True
100+data FieldRepr :: (Symbol, Schema) -> Type where
101+ FieldRepr :: KnownSymbol name => JsonRepr schema -> FieldRepr '(name, schema)
103+instance Eq (JsonRepr schema) => Eq (FieldRepr '(name, schema)) where
104+ FieldRepr a == FieldRepr b = a == b
60− ( Constrained 'JNumber c
61− , Verifiable 'JNumber cs )
62− => Verifiable 'JNumber (c ': cs) where
63− schema _ _ = cRep (Proxy @'JNumber) (Proxy @c)
64− : schema (Proxy @'JNumber) (Proxy @cs)
66−instance Verifiable jty '[] where
70− ( Constrained 'JArray c
71− , Verifiable 'JArray cs )
72− => Verifiable 'JArray (c ': cs) where
73− schema _ _ = cRep (Proxy @'JArray) (Proxy @c)
74− : schema (Proxy @'JArray) (Proxy @cs)
77− ( SchemaConstructor jty
78− , Verifiable 'JObject fs
79− , KnownSymbol field
107+ ( KnownSymbol name
80− , Verifiable jty cs )
108+ , Serial m (JsonRepr schema) )
81− => Verifiable 'JObject ((Field field jty cs) ': fs) where
109+ => Serial m (FieldRepr '(name, schema)) where
82− schema _ _ = (fieldName, fieldSchema) : schema (Proxy @'JObject) (Proxy @fs)
110+ series = FieldRepr <$> series
112+data JsonRepr :: Schema -> Type where
113+ ReprText :: Text -> JsonRepr (SchemaText cs)
114+ ReprNumber :: Scientific -> JsonRepr (SchemaNumber cs)
115+ ReprNull :: JsonRepr SchemaNull
116+ ReprArray :: V.Vector (JsonRepr s) -> JsonRepr (SchemaArray cs s)
117+ ReprObject :: Rec FieldRepr fs -> JsonRepr (SchemaObject fs)
119+instance Eq (Rec FieldRepr fs) => Eq (JsonRepr (SchemaObject fs)) where
120+ ReprObject a == ReprObject b = a == b
122+instance Eq (JsonRepr (SchemaText cs)) where
123+ ReprText a == ReprText b = a == b
125+instance Eq (JsonRepr (SchemaNumber cs)) where
126+ ReprNumber a == ReprNumber b = a == b
128+instance Eq (JsonRepr SchemaNull) where
129+ ReprNull == ReprNull = True
131+instance Eq (JsonRepr s) => Eq (JsonRepr (SchemaArray as s)) where
132+ ReprArray a == ReprArray b = a == b
134+instance (SingI schema) => J.FromJSON (JsonRepr schema) where
135+ parseJSON value = case sing :: Sing schema of
136+ SSchemaText _ -> withText "String" (pure . ReprText) value
137+ SSchemaNumber _ -> withScientific "Number" (pure . ReprNumber) value
138+ SSchemaNull -> pure ReprNull
139+ SSchemaArray c s -> withArray "Array" f value
141+ f = fmap ReprArray . (withSingI s $ traverse parseJSON)
142+ SSchemaObject fs -> ReprObject <$> withObject "Object" (demoteFields fs) value
146+ -> H.HashMap Text J.Value
147+ -> Parser (Rec FieldRepr s)
149+ | H.null h = pure RNil
151+demoteFields (SCons (STuple2 (n :: Sing fn) s) tl) h = withKnownSymbol n $ do
152+ let fieldName = T.pack $ symbolVal (Proxy @fn)
153+ fieldRepr <- case H.lookup fieldName h of
154+ Just v -> FieldRepr <$> (withSingI s $ parseJSON v)
156+ (fieldRepr :&) <$> demoteFields tl h
158+instance J.ToJSON (JsonRepr a) where
159+ toJSON ReprNull = J.Null
160+ toJSON (ReprText t) = J.String t
161+ toJSON (ReprNumber n) = J.Number n
162+ toJSON (ReprArray v) = J.Array $ toJSON <$> v
163+ toJSON (ReprObject r) = J.Object . H.fromList . fold $ r
84− fieldName = symbolVal (Proxy @field)
165+ extract :: forall name s . (KnownSymbol name)
85− fieldSchema = constructor (Proxy @jty) $ schema (Proxy @jty) (Proxy @cs)
166+ => FieldRepr '(name, s)
87−class SchemaConstructor jty where
168+ extract (FieldRepr s) = (T.pack $ symbolVal $ Proxy @name, toJSON s)
88− constructor :: Proxy jty -> [CRepr jty] -> Schema
169+ fold :: Rec FieldRepr fs -> [(Text, J.Value)]
90−instance SchemaConstructor 'JText where
171+ RNil -> []
91− constructor _ = Fix . SchemaText
172+ fr@(FieldRepr s) :& tl -> (extract fr) : fold tl
93−instance SchemaConstructor 'JNumber where
174+class FalseConstraint
94− constructor _ = Fix . SchemaNumber
175+
95−
176+type family TopLevel (spec :: Schema) :: Constraint where
96−instance SchemaConstructor 'JObject where
177+ TopLevel (SchemaArray a e) = ()
97− constructor _ = Fix . SchemaObject
178+ TopLevel (SchemaObject o) = ()
99−class Schematic ty where
100− build :: Proxy ty -> Schema
104−instance (Verifiable 'JText cs) => Schematic (SText cs) where
105− build _ = constructor (Proxy @(JT (SText cs))) $ schema (Proxy @JText) (Proxy @cs)
109−instance (Verifiable 'JNumber cs) => Schematic (SNumber cs) where
110− build _ = constructor (Proxy @(JT (SNumber cs)))
111− $ schema (Proxy @'JNumber) (Proxy @cs)
114− ( Verifiable (JT ty) cs
115− , SchemaConstructor (JT ty))
116− => Schematic (Field field ty cs) where
117− build _ = constructor (Proxy @(JT (Field field ty cs)))
118− $ schema (Proxy @(JT (Field field ty cs))) (Proxy @cs)
123− ( Verifiable 'JObject els )
124− => Schematic (SObject els) where
126− constructor (Proxy @'JObject) $ schema (Proxy @(JT (SObject els))) (Proxy @els)
132− , Verifiable 'JArray cs )
133− => Schematic (SArray cs s) where
136− (schema (Proxy @'JArray) (Proxy @cs))
139−-- | Shows it's top-level definition according to the json-schema
140−class Schematic spec => TopLevel spec
179+ TopLevel spec = FalseConstraint
142−instance (Schematic (SObject els)) => TopLevel (SObject els)
144−instance Schematic (SArray cs s) => TopLevel (SArray cs s)
146−-- | TODO: abstract over objects and arrays
148− :: forall spec. TopLevel spec