packages feed

mu-schema (empty) → 0.1.0.0

raw patch · 17 files changed

+2545/−0 lines, 17 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, containers, sop-core, template-haskell, text, th-abstraction, unordered-containers, vector

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for mu-haskell++## 0.1.0.0 -- YYYY-mm-dd++* 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 © 2019-2020 47 Degrees. <http://47deg.com>++   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
+ mu-schema.cabal view
@@ -0,0 +1,51 @@+cabal-version:       >=1.10+name:                mu-schema+version:             0.1.0.0+synopsis:            Format-independent schemas for serialization+description:         With @mu-schema@ you can describe schemas using type-level constructs, and derive serializers from those. See @mu-avro@, @mu-protobuf@ for the actual adapters.+-- bug-reports:+license:             Apache-2.0+license-file:        LICENSE+author:              Alejandro Serrano, Flavio Corpa+maintainer:          alejandro.serrano@47deg.com+copyright:           Copyright © 2019-2020 <http://47deg.com 47 Degrees>+category:            Network+build-type:          Simple+extra-source-files:  CHANGELOG.md+homepage:            https://higherkindness.io/mu-haskell/+bug-reports:         https://github.com/higherkindness/mu-haskell/issues++source-repository head+  type:     git+  location: https://github.com/higherkindness/mu-haskell++library+  exposed-modules:     Mu.Schema+                     , Mu.Schema.Definition+                     , Mu.Schema.Interpretation+                     , Mu.Schema.Interpretation.Schemaless+                     , Mu.Schema.Interpretation.Anonymous+                     , Mu.Schema.Class+                     , Mu.Schema.Registry+                     , Mu.Schema.Conversion.TypesToSchema+                     , Mu.Schema.Conversion.SchemaToTypes+                     , Mu.Schema.Examples+                     , Mu.Schema.Annotations+                     , Mu.Adapter.Json+                     , Data.Functor.MaybeLike+  -- other-modules:+  -- other-extensions:+  build-depends:       base >=4.12 && <5+                     , sop-core+                     , containers+                     , unordered-containers+                     , bytestring+                     , vector+                     , text+                     , aeson+                     , template-haskell >= 2.12+                     , th-abstraction+  hs-source-dirs:      src+  default-language:    Haskell2010+  ghc-options:         -Wall+                       -fprint-potential-instances
+ src/Data/Functor/MaybeLike.hs view
@@ -0,0 +1,29 @@+{-|+Description : Type constructors which can be turned into 'Maybe'.++Type constructors which can be turned into 'Maybe'.+-}+module Data.Functor.MaybeLike where++import Data.Functor.Identity++-- | This class may be defined in two ways:+--+--   * Type constructors which can be turned into 'Maybe' generically.+--   * Type constructors which admit a natural transformation to 'Maybe'.+--+--   We expect the following rules to hold for those+--   instances of 'MaybeLike' which are also 'Control.Applicative.Alternative':+--+--   * @likeMaybe empty = empty = Nothing@+--   * @likeMaybe (x <|> y) = likeMaybe x <|> likeMaybe y@+class MaybeLike f where+  likeMaybe :: f a -> Maybe a++instance MaybeLike Identity where+  likeMaybe = Just . runIdentity+instance MaybeLike Maybe where+  likeMaybe = id+instance MaybeLike (Either a) where+  likeMaybe (Left  _) = Nothing+  likeMaybe (Right y) = Just y
+ src/Mu/Adapter/Json.hs view
@@ -0,0 +1,175 @@+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language FlexibleInstances     #-}+{-# language GADTs                 #-}+{-# language MultiParamTypeClasses #-}+{-# language PolyKinds             #-}+{-# language ScopedTypeVariables   #-}+{-# language TypeApplications      #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+Description : Adapter for JSON serialization++Just import the module and you can turn any+value with a 'ToSchema' and 'FromSchema' from+and to JSON values.+-}+module Mu.Adapter.Json () where++import           Control.Applicative                 ((<|>))+import           Data.Aeson+import           Data.Aeson.Types+import           Data.Functor.Contravariant+import           Data.Functor.Identity+import qualified Data.HashMap.Strict                 as HM+import qualified Data.Text                           as T+import qualified Data.Vector                         as V++import           Mu.Schema+import qualified Mu.Schema.Interpretation.Schemaless as SLess++instance Applicative w => SLess.ToSchemalessTerm Value w where+  toSchemalessTerm (Object o)+    = SLess.TRecord $ map (\(k,v) -> SLess.Field k (pure $ SLess.toSchemalessValue v))+                    $ HM.toList o+  toSchemalessTerm v = SLess.TSimple (SLess.toSchemalessValue v)++instance Applicative w => SLess.ToSchemalessValue Value w where+  toSchemalessValue r@(Object _)+    = SLess.FSchematic (SLess.toSchemalessTerm r)+  toSchemalessValue Null       = SLess.FNull+  toSchemalessValue (String s) = SLess.FPrimitive s+  toSchemalessValue (Number n) = SLess.FPrimitive n+  toSchemalessValue (Bool   b) = SLess.FPrimitive b+  toSchemalessValue (Array xs)+    = SLess.FList $ map SLess.toSchemalessValue $ V.toList xs++instance (ToSchema w sch sty a, ToJSON (Term w sch (sch :/: sty)))+         => ToJSON (WithSchema w sch sty a) where+  toJSON (WithSchema x) = toJSON (toSchema' @_ @_ @sch @w x)+instance (FromSchema w sch sty a, FromJSON (Term w sch (sch :/: sty)))+         => FromJSON (WithSchema w sch sty a) where+  parseJSON v = WithSchema . fromSchema' @_ @_ @sch @w <$> parseJSON v++instance ToJSONFields sch args => ToJSON (Term Identity sch ('DRecord name args)) where+  toJSON (TRecord fields) = Object (toJSONFields fields)+instance FromJSONFields w sch args => FromJSON (Term w sch ('DRecord name args)) where+  parseJSON (Object v) = TRecord <$> parseJSONFields v+  parseJSON _          = fail "expected object"++class ToJSONFields sch fields where+  toJSONFields :: NP (Field Identity sch) fields -> Object+instance ToJSONFields sch '[] where+  toJSONFields _ = HM.empty+instance (KnownName name, ToJSON (FieldValue Identity sch t), ToJSONFields sch fs)+         => ToJSONFields sch ('FieldDef name t ': fs) where+  toJSONFields (Field (Identity v) :* rest) = HM.insert key value $ toJSONFields rest+    where key = T.pack (nameVal (Proxy @name))+          value = toJSON v++class FromJSONFields w sch fields where+  parseJSONFields :: Object -> Parser (NP (Field w sch) fields)+instance FromJSONFields w sch '[] where+  parseJSONFields _ = return Nil+instance (Applicative w, KnownName name, FromJSON (FieldValue w sch t), FromJSONFields w sch fs)+         => FromJSONFields w sch ('FieldDef name t ': fs) where+  parseJSONFields v = (:*) <$> (Field <$> (pure <$> v .: key)) <*> parseJSONFields v+    where key = T.pack (nameVal (Proxy @name))++instance ToJSONEnum choices => ToJSON (Term w sch ('DEnum name choices)) where+  toJSON (TEnum choice) = String (toJSONEnum choice)+instance FromJSONEnum choices => FromJSON (Term w sch ('DEnum name choices)) where+  parseJSON (String s) = TEnum <$> parseJSONEnum s+  parseJSON _          = fail "expected string"++class ToJSONEnum choices where+  toJSONEnum :: NS Proxy choices -> T.Text+instance ToJSONEnum '[] where+  toJSONEnum = error "empty enum"+instance (KnownName c, ToJSONEnum cs)+         => ToJSONEnum ('ChoiceDef c ': cs) where+  toJSONEnum (Z _) = T.pack (nameVal (Proxy @c))+  toJSONEnum (S v) = toJSONEnum v++class FromJSONEnum choices where+  parseJSONEnum :: T.Text -> Parser (NS Proxy choices)+instance FromJSONEnum '[] where+  parseJSONEnum _ = fail "unknown enum value"+instance (KnownName c, FromJSONEnum cs)+         => FromJSONEnum ('ChoiceDef c ': cs) where+  parseJSONEnum v+    | v == key  = return (Z Proxy)+    | otherwise = S <$> parseJSONEnum v+    where key = T.pack (nameVal (Proxy @c))++instance ToJSON (FieldValue w sch t) => ToJSON (Term w sch ('DSimple t)) where+  toJSON (TSimple x) = toJSON x+instance FromJSON (FieldValue w sch t) => FromJSON (Term w sch ('DSimple t)) where+  parseJSON v = TSimple <$> parseJSON v++instance ToJSON (FieldValue w sch 'TNull) where+  toJSON FNull = Null+instance ToJSON t => ToJSON (FieldValue w sch ('TPrimitive t)) where+  toJSON (FPrimitive v) = toJSON v+instance ToJSONKey t => ToJSONKey (FieldValue w sch ('TPrimitive t)) where+  toJSONKey = contramap FPrimitive toJSONKey+  toJSONKeyList = contramap (map FPrimitive) toJSONKeyList+instance ToJSON (Term w sch (sch :/: t))+         => ToJSON (FieldValue w sch ('TSchematic t)) where+  toJSON (FSchematic v) = toJSON v+instance ToJSON (FieldValue w sch t)+         => ToJSON (FieldValue w sch ('TOption t)) where+  toJSON (FOption v) = toJSON v+instance ToJSON (FieldValue w sch t)+         => ToJSON (FieldValue w sch ('TList t)) where+  toJSON (FList v) = toJSON v+instance (ToJSONKey (FieldValue w sch k), ToJSON (FieldValue w sch v))+         => ToJSON (FieldValue w sch ('TMap k v)) where+  toJSON (FMap v) = toJSON v+instance (ToJSONUnion w sch us)+         => ToJSON (FieldValue w sch ('TUnion us)) where+  toJSON (FUnion v) = unionToJSON v++class ToJSONUnion w sch us where+  unionToJSON :: NS (FieldValue w sch) us -> Value+instance ToJSONUnion w sch '[] where+  unionToJSON = error "this should never happen"+instance (ToJSON (FieldValue w sch u), ToJSONUnion w sch us)+         => ToJSONUnion w sch (u ': us) where+  unionToJSON (Z v) = toJSON v+  unionToJSON (S r) = unionToJSON r++instance FromJSON (FieldValue w sch 'TNull) where+  parseJSON Null = return FNull+  parseJSON _    = fail "expected null"+instance FromJSON t => FromJSON (FieldValue w sch ('TPrimitive t)) where+  parseJSON v = FPrimitive <$> parseJSON v+instance FromJSONKey t => FromJSONKey (FieldValue w sch ('TPrimitive t)) where+  fromJSONKey = fmap FPrimitive fromJSONKey+  fromJSONKeyList = fmap (map FPrimitive) fromJSONKeyList+instance FromJSON (Term w sch (sch :/: t))+         => FromJSON (FieldValue w sch ('TSchematic t)) where+  parseJSON v = FSchematic <$> parseJSON v+instance FromJSON (FieldValue w sch t)+         => FromJSON (FieldValue w sch ('TOption t)) where+  parseJSON v = FOption <$> parseJSON v+instance FromJSON (FieldValue w sch t)+         => FromJSON (FieldValue w sch ('TList t)) where+  parseJSON v = FList <$> parseJSON v+instance ( FromJSONKey (FieldValue w sch k), FromJSON (FieldValue w sch v)+         , Ord (FieldValue w sch k) )+         => FromJSON (FieldValue w sch ('TMap k v)) where+  parseJSON v = FMap <$> parseJSON v+instance (FromJSONUnion w sch us)+         => FromJSON (FieldValue w sch ('TUnion us)) where+  parseJSON v = FUnion <$> unionFromJSON v++class FromJSONUnion w sch us where+  unionFromJSON :: Value -> Parser (NS (FieldValue w sch) us)+instance FromJSONUnion w sch '[] where+  unionFromJSON _ = fail "value does not match any of the types of the union"+instance (FromJSON (FieldValue w sch u), FromJSONUnion w sch us)+         => FromJSONUnion w sch (u ': us) where+  unionFromJSON v = Z <$> parseJSON v <|> S <$> unionFromJSON v
+ src/Mu/Schema.hs view
@@ -0,0 +1,41 @@+{-# language DataKinds #-}+{-|+Description : Schemas for Mu microservices++Definition and interpretation of schemas in+the vein of Avro, Protocol Buffers, or JSON Schema.++Each 'Schema' is made out of types (which in turn+be records or enumerations). A value which obbeys+such a schema is called a 'Term'. Conversion between+Haskell types and schema types is mediated by the+type classes 'ToSchema' and 'FromSchema'.+-}+module Mu.Schema (+  -- * Schema definition+  Schema, Schema'+, KnownName(..)+, TypeDef, TypeDefB(..)+, ChoiceDef(..)+, FieldDef, FieldDefB(..)+, FieldType, FieldTypeB(..)+  -- ** Lookup type in schema+, (:/:)+  -- * Interpretation of schemas+, Term(..), Field(..), FieldValue(..)+, NS(..), NP(..), Proxy(..)+  -- * Conversion from types to schemas+, WithSchema(..)+, FromSchema(..), fromSchema'+, ToSchema(..), toSchema'+, CustomFieldMapping(..)+  -- ** Mappings between fields+, Mapping(..), Mappings, MappingRight, MappingLeft+  -- ** Field annotations+, AnnotatedSchema, AnnotationDomain, Annotation(..)+) where++import           Mu.Schema.Annotations+import           Mu.Schema.Class+import           Mu.Schema.Definition+import           Mu.Schema.Interpretation
+ src/Mu/Schema/Annotations.hs view
@@ -0,0 +1,75 @@+{-# language DataKinds            #-}+{-# language GADTs                #-}+{-# language PolyKinds            #-}+{-# language TypeFamilies         #-}+{-# language TypeOperators        #-}+{-# language UndecidableInstances #-}+{-|+Description : Protocol-defined annotations.++Libraries can define custom annotations to+indicate additional information not found+in the 'Schema' itself. For example, Protocol+Buffers requires a numerical identifier for+each field in a record.+-}+module Mu.Schema.Annotations (+  -- * Annotate a schema+  Annotation(..)+, AnnotatedSchema+, AnnotationDomain+  -- * Find annotations for an element+, GetSchemaAnnotation+, GetTypeAnnotation+, GetFieldAnnotation+) where++import           Data.Kind+import           GHC.TypeLits++import           Mu.Schema.Definition++-- | Each annotation belongs to a domain.+type AnnotationDomain = Type++-- | Annotations proper.+data Annotation domain typeName fieldName where+  -- | Annotation over the whole schema.+  AnnSchema :: domain+            -> Annotation domain typeName fieldName+  -- | Annotation over a type in the schema.+  AnnType   :: typeName -> domain+            -> Annotation domain typeName fieldName+  -- | Annotation over a field in a record+  --   or a choice in an enumeration.+  AnnField  :: typeName -> fieldName -> domain+            -> Annotation domain typeName fieldName++-- | This type family links each schema to+--   its corresponding annotations from one domain.+type family AnnotatedSchema domain (sch :: Schema typeName fieldName)+  :: [Annotation domain typeName fieldName]++-- | Find the annotation over the schema in the given set.+--   If the annotation cannot be found, raise a 'TypeError'.+type family GetSchemaAnnotation (anns :: [Annotation domain t f]) :: domain where+  GetSchemaAnnotation '[]+    = TypeError ('Text "cannot find schema annotation")+  GetSchemaAnnotation ('AnnSchema d ': rs) = d+  GetSchemaAnnotation (r            ': rs) = GetSchemaAnnotation rs++-- | Find the annotation over the given type in the given set.+--   If the annotation cannot be found, raise a 'TypeError'.+type family GetTypeAnnotation (anns :: [Annotation domain t f]) (ty :: t) :: domain where+  GetTypeAnnotation '[] ty+    = TypeError ('Text "cannot find annotation for " ':<>: 'ShowType ty)+  GetTypeAnnotation ('AnnType ty d ': rs) ty = d+  GetTypeAnnotation (r ': rs) ty = GetTypeAnnotation rs ty++-- | Find the annotation over the given field or choice in the given type.+--   If the annotation cannot be found, raise a 'TypeError'.+type family GetFieldAnnotation (anns :: [Annotation domain t f]) (ty :: t) (fl :: f) :: domain where+  GetFieldAnnotation '[] ty fl+    = TypeError ('Text "cannot find annotation for " ':<>: 'ShowType ty ':<>: 'Text "/" ':<>: 'ShowType fl)+  GetFieldAnnotation ('AnnField ty fl d ': rs) ty fl = d+  GetFieldAnnotation (r                 ': rs) ty fl = GetFieldAnnotation rs ty fl
+ src/Mu/Schema/Class.hs view
@@ -0,0 +1,546 @@+{-# language DataKinds              #-}+{-# language DefaultSignatures      #-}+{-# language FlexibleContexts       #-}+{-# language FlexibleInstances      #-}+{-# language FunctionalDependencies #-}+{-# language GADTs                  #-}+{-# language PolyKinds              #-}+{-# language QuantifiedConstraints  #-}+{-# language RankNTypes             #-}+{-# language ScopedTypeVariables    #-}+{-# language TypeApplications       #-}+{-# language TypeFamilies           #-}+{-# language TypeOperators          #-}+{-# language UndecidableInstances   #-}+{-|+Description : Conversion from types to schemas++This module defines a couple of type classes+'ToSchema' and 'FromSchema' to turn Haskell+types back and forth @mu-haskell@ 'Term's.++In most cases, the instances can be automatically+derived. If you enable the extensions+@DeriveGeneric@ and @DeriveAnyClass@, you can do:++> data MyHaskellType = ...+>   deriving ( ToSchema   f MySchema "MySchemaType" MyHaskellType+>            , FromSchema f MySchema "MySchemaType" MyHaskellType)++If the default mapping which required identical+names for fields in the Haskell and schema types+does not suit you, use 'CustomFieldMapping'.+-}+module Mu.Schema.Class (+  WithSchema(..)+, FromSchema(..), fromSchema'+, ToSchema(..), toSchema'+, CustomFieldMapping(..)+, Mapping(..), Mappings, MappingRight, MappingLeft+, transSchema+  -- * Internal use only+, GToSchemaRecord(..)+) where++import           Data.Functor.Identity+import           Data.Kind+import           Data.Map                 as M+import           Data.SOP+import           GHC.Generics+import           GHC.TypeLits++import           Mu.Schema.Definition+import           Mu.Schema.Interpretation++-- | Tags a value with its schema.+--   For usage with @deriving via@.+newtype WithSchema (w :: Type -> Type) (sch :: Schema tn fn) (sty :: tn) a = WithSchema a++-- | Defines the conversion of a type @t@ into a 'Term'+--   which follows the schema @sch@.+--   You can give an optional mapping between the+--   field names of @t@ and that of @sty@+--   by means of 'CustomFieldMapping'.+class ToSchema (w :: Type -> Type) (sch :: Schema typeName fieldName) (sty :: typeName) (t :: Type)+      | sch t -> sty where+  -- | Conversion from Haskell type to schema term.+  toSchema   :: t -> Term w sch (sch :/: sty)++  default+    toSchema :: (Generic t, GToSchemaTypeDef w sch '[] (sch :/: sty) (Rep t))+              => t -> Term w sch (sch :/: sty)+  toSchema x = toSchemaTypeDef (Proxy @'[]) (from x)++-- | Defines the conversion from a 'Term'+--   which follows the schema @sch@ into a type @t@.+--   You can give an optional mapping between the+--   field names of @t@ and that of @sty@+--   by means of 'CustomFieldMapping'.+class FromSchema (w :: Type -> Type) (sch :: Schema typeName fieldName) (sty :: typeName) (t :: Type)+      | sch t -> sty where+  -- | Conversion from schema term to Haskell type.+  fromSchema :: Term w sch (sch :/: sty) -> t++  default+    fromSchema :: (Generic t, GFromSchemaTypeDef w sch '[] (sch :/: sty) (Rep t) )+               => Term w sch (sch :/: sty) -> t+  fromSchema x = to (fromSchemaTypeDef (Proxy @'[]) x)++-- | Conversion from Haskell type to schema term.+--   This version is intended for usage with @TypeApplications@:+--   > toSchema' @MySchema myValue+toSchema' :: forall fn tn (sch :: Schema tn fn) w t sty.+             ToSchema w sch sty t => t -> Term w sch (sch :/: sty)+toSchema' = toSchema+-- | Conversion from schema term to Haskell type.+--   This version is intended for usage with @TypeApplications@:+--   > fromSchema' @MySchema mySchemaTerm+fromSchema' :: forall fn tn (sch :: Schema tn fn) w t sty.+               FromSchema w sch sty t => Term w sch (sch :/: sty) -> t+fromSchema' = fromSchema++-- | By default, the names of the fields in the Haskell type+--   and those of the schema types must coincide. By using+--   this wrapper you can override this default setting.+--+--   This type should be used with @DerivingVia@, as follows:+--+--   > type MyCustomFieldMapping = '[ "A" ':-> "a", ...]+--   > data MyHaskellType = ...+--   >   deriving ( ToSchema   f MySchema "MySchemaType" MyHaskellType+--   >            , FromSchema f MySchema "MySchemaType" MyHaskellType)+--   >     via (CustomFieldMapping "MySchemaType" MyCustomFieldMapping MyHaskellType)+newtype CustomFieldMapping (sty :: typeName) (fmap :: [Mapping Symbol fieldName])  a+  = CustomFieldMapping a++instance (Generic t, GToSchemaTypeDef w sch fmap (sch :/: sty) (Rep t))+         => ToSchema w sch sty (CustomFieldMapping sty fmap t) where+  toSchema (CustomFieldMapping x) = toSchemaTypeDef (Proxy @fmap) (from x)++instance (Generic t, GFromSchemaTypeDef w sch fmap (sch :/: sty) (Rep t))+         => FromSchema w sch sty (CustomFieldMapping sty fmap t) where+  fromSchema x = CustomFieldMapping $ to (fromSchemaTypeDef (Proxy @fmap) x)++-- | Changes the underlying wrapper of a Haskell type,+--   by converting back and forth 'Term's with those wrappers.+transSchema+  :: forall fn tn (sch :: Schema tn fn) sty u v a b.+     ( ToSchema u sch sty a, FromSchema v sch sty b+     , Functor u, forall k. Ord (FieldValue u sch k) => Ord (FieldValue v sch k) )+  => (forall x. u x -> v x) -> Proxy sch -> a -> b+transSchema f _ = fromSchema @_ @_ @v @sch @sty . transWrap f . toSchema @_ @_ @u @sch @sty++-- ======================+-- CRAZY GENERICS SECTION+-- ======================++-- Auxiliary type families to find elements in lists+-- They return an indication of where the thing was found+--+-- Note: it turns out that GHC.Generics generates some weird+-- instances for records in the form (x :*: y) :*: z+-- and we cover them with the special HereLeft and HereRight+data Where = Here | HereLeft | HereRight | There Where++type family Find (xs :: [k]) (x :: k) :: Where where+  Find '[]       y = TypeError ('Text "Could not find " ':<>: 'ShowType y)+  Find (y ': xs) y = 'Here+  Find (x ': xs) y = 'There (Find xs y)++type family FindCon (xs :: * -> *) (x :: Symbol) :: Where where+  FindCon (C1 ('MetaCons x p s) f) x = 'Here+  FindCon (C1 ('MetaCons x p s) f :+: rest) x = 'Here+  FindCon (other :+: rest) x = 'There (FindCon rest x)+  FindCon nothing          x = TypeError ('Text "Could not find constructor " ':<>: 'ShowType x)++type family FindSel (xs :: * -> *) (x :: Symbol) :: Where where+  FindSel (S1 ('MetaSel ('Just x) u ss ds) f) x = 'Here+  FindSel (S1 ('MetaSel ('Just x) u ss ds) f :*: rest) x = 'Here+  FindSel ((S1 ('MetaSel ('Just x) u ss ds) f :*: other) :*: rest) x = 'HereLeft+  FindSel ((other :*: S1 ('MetaSel ('Just x) u ss ds) f) :*: rest) x = 'HereRight+  FindSel (other :*: rest) x = 'There (FindSel rest x)+  FindSel nothing          x = TypeError ('Text "Could not find selector " ':<>: 'ShowType x)++type family FindEnumChoice (xs :: [ChoiceDef fs]) (x :: fs) :: Where where+  FindEnumChoice '[] x = TypeError ('Text "Could not find enum choice " ':<>: 'ShowType x)+  FindEnumChoice ('ChoiceDef name ': xs) name = 'Here+  FindEnumChoice (other           ': xs) name = 'There (FindEnumChoice xs name)++type family FindField (xs :: [FieldDef ts fs]) (x :: fs) :: Where where+  FindField '[] x = TypeError ('Text "Could not find field " ':<>: 'ShowType x)+  FindField ('FieldDef name t ': xs) name = 'Here+  FindField (other            ': xs) name = 'There (FindField xs name)++-- Generic type definitions+class GToSchemaTypeDef+        (w :: * -> *) (sch :: Schema ts fs) (fmap :: Mappings Symbol fs)+        (t :: TypeDef ts fs) (f :: * -> *) where+  toSchemaTypeDef   :: Proxy fmap -> f a -> Term w sch t+class GFromSchemaTypeDef+        (w :: * -> *) (sch :: Schema ts fs) (fmap :: Mappings Symbol fs)+        (t :: TypeDef ts fs) (f :: * -> *) where+  fromSchemaTypeDef :: Proxy fmap -> Term w sch t -> f a++-- ------------------+-- TYPES OF FIELDS --+-- ------------------++instance GToSchemaFieldTypeWrap w sch t f+         => GToSchemaTypeDef w sch fmap ('DSimple t) f where+  toSchemaTypeDef _ x = TSimple (toSchemaFieldTypeW x)+instance GFromSchemaFieldTypeWrap w sch t f+         => GFromSchemaTypeDef w sch fmap ('DSimple t) f where+  fromSchemaTypeDef _ (TSimple x) = fromSchemaFieldTypeW x++class GToSchemaFieldTypeWrap+        (w :: * -> *) (sch :: Schema ts fs) (t :: FieldType ts) (f :: * -> *) where+  toSchemaFieldTypeW   :: f a -> FieldValue w sch t+class GFromSchemaFieldTypeWrap+        (w :: * -> *) (sch :: Schema ts fs) (t :: FieldType ts) (f :: * -> *) where+  fromSchemaFieldTypeW :: FieldValue w sch t -> f a++instance GToSchemaFieldType w sch t f+         => GToSchemaFieldTypeWrap w sch t (K1 i f) where+  toSchemaFieldTypeW (K1 x) = toSchemaFieldType x+instance GFromSchemaFieldType w sch t f+         => GFromSchemaFieldTypeWrap w sch t (K1 i f) where+  fromSchemaFieldTypeW x = K1 (fromSchemaFieldType x)+instance GToSchemaFieldTypeWrap w sch t f+         => GToSchemaFieldTypeWrap w sch t (M1 s m f) where+  toSchemaFieldTypeW (M1 x) = toSchemaFieldTypeW x+instance GFromSchemaFieldTypeWrap w sch t f+         => GFromSchemaFieldTypeWrap w sch t (M1 s m f) where+  fromSchemaFieldTypeW x = M1 (fromSchemaFieldTypeW x)++class GToSchemaFieldType+        (w :: * -> *) (sch :: Schema ts fs) (t :: FieldType ts) (f :: *) where+  toSchemaFieldType   :: f -> FieldValue w sch t+class GFromSchemaFieldType+        (w :: * -> *) (sch :: Schema ts fs) (t :: FieldType ts) (f :: *) where+  fromSchemaFieldType :: FieldValue w sch t -> f++class GToSchemaFieldTypeUnion+        (w :: * -> *) (sch :: Schema ts fs) (t :: [FieldType ts]) (f :: * -> *) where+  toSchemaFieldTypeUnion   :: f a -> NS (FieldValue w sch) t+class GFromSchemaFieldTypeUnion+        (w :: * -> *) (sch :: Schema ts fs) (t :: [FieldType ts]) (f :: * -> *) where+  fromSchemaFieldTypeUnion :: NS (FieldValue w sch) t -> f a++-- These instances are straightforward,+-- just turn the "real types" into their+-- schema correspondants.+instance GToSchemaFieldType w sch 'TNull () where+  toSchemaFieldType _   = FNull+instance GFromSchemaFieldType w sch 'TNull () where+  fromSchemaFieldType _ = ()+instance GToSchemaFieldType w sch ('TPrimitive t) t where+  toSchemaFieldType = FPrimitive+instance GFromSchemaFieldType w sch ('TPrimitive t) t where+  fromSchemaFieldType (FPrimitive x) = x+-- These instances "tie the loop" with the whole schema,+-- and they are the reason why we need to thread the @sch@+-- type throghout the whole implementation.+instance ToSchema w sch t v+         => GToSchemaFieldType w sch ('TSchematic t) v where+  toSchemaFieldType x = FSchematic $ toSchema x+instance FromSchema w sch t v+         => GFromSchemaFieldType w sch ('TSchematic t) v where+  fromSchemaFieldType (FSchematic x) = fromSchema x+instance GToSchemaFieldType w sch t v+         => GToSchemaFieldType w sch ('TOption t) (Maybe v) where+  toSchemaFieldType x = FOption (toSchemaFieldType <$> x)+instance GFromSchemaFieldType w sch t v+         => GFromSchemaFieldType w sch ('TOption t) (Maybe v) where+  fromSchemaFieldType (FOption x) = fromSchemaFieldType <$> x+instance GToSchemaFieldType w sch t v+         => GToSchemaFieldType w sch ('TList t) [v] where+  toSchemaFieldType x = FList (toSchemaFieldType <$> x)+instance GFromSchemaFieldType w sch t v+         => GFromSchemaFieldType w sch ('TList t) [v] where+  fromSchemaFieldType (FList x) = fromSchemaFieldType <$> x+instance (GToSchemaFieldType w sch sk hk, GToSchemaFieldType w sch sv hv,+          Ord (FieldValue w sch sk))  -- Ord is required to build a map+         => GToSchemaFieldType w sch ('TMap sk sv) (M.Map hk hv) where+  toSchemaFieldType x = FMap (M.mapKeys toSchemaFieldType (M.map toSchemaFieldType x))+instance (GFromSchemaFieldType w sch sk hk, GFromSchemaFieldType w sch sv hv, Ord hk)+         => GFromSchemaFieldType w sch ('TMap sk sv) (M.Map hk hv) where+  fromSchemaFieldType (FMap x) = M.mapKeys fromSchemaFieldType (M.map fromSchemaFieldType x)+-- This assumes that a union is represented by+-- a value of type 'NS', where types are in+-- the same order.+instance {-# OVERLAPS #-}+         AllZip (GToSchemaFieldType w sch) ts vs+         => GToSchemaFieldType w sch ('TUnion ts) (NS I vs) where+  toSchemaFieldType t = FUnion (go t)+    where go :: AllZip (GToSchemaFieldType w sch) tss vss+             => NS I vss -> NS (FieldValue w sch) tss+          go (Z (I x)) = Z (toSchemaFieldType x)+          go (S n)     = S (go n)+instance {-# OVERLAPS #-}+         AllZip (GFromSchemaFieldType w sch) ts vs+         => GFromSchemaFieldType w sch ('TUnion ts) (NS I vs) where+  fromSchemaFieldType (FUnion t) = go t+    where go :: AllZip (GFromSchemaFieldType w sch) tss vss+             => NS (FieldValue w sch) tss -> NS I vss+          go (Z x) = Z (I (fromSchemaFieldType x))+          go (S n) = S (go n)+-- But we can also use any other if it has+-- the right structure+instance {-# OVERLAPPABLE #-}+         (Generic f, GToSchemaFieldTypeUnion w sch ts (Rep f))+         => GToSchemaFieldType w sch ('TUnion ts) f where+  toSchemaFieldType x = FUnion (toSchemaFieldTypeUnion (from x))+instance {-# OVERLAPPABLE #-}+         (Generic f, GFromSchemaFieldTypeUnion w sch ts (Rep f))+         => GFromSchemaFieldType w sch ('TUnion ts) f where+  fromSchemaFieldType (FUnion x) = to (fromSchemaFieldTypeUnion x)++instance {-# OVERLAPS #-} GToSchemaFieldTypeUnion w sch '[] U1 where+  toSchemaFieldTypeUnion U1 = error "this should never happen"+instance {-# OVERLAPS #-} GFromSchemaFieldTypeUnion w sch '[] U1 where+  fromSchemaFieldTypeUnion _ = U1+instance {-# OVERLAPPABLE #-}+         TypeError ('Text "the type does not match the union")+         => GToSchemaFieldTypeUnion w sch '[] f where+  toSchemaFieldTypeUnion = error "this should never happen"+instance {-# OVERLAPPABLE #-}+         TypeError ('Text "the type does not match the union")+         => GFromSchemaFieldTypeUnion w sch '[] f where+  fromSchemaFieldTypeUnion = error "this should never happen"++instance (GToSchemaFieldTypeWrap w sch t v)+         => GToSchemaFieldTypeUnion w sch '[t] v where+  toSchemaFieldTypeUnion   x     = Z (toSchemaFieldTypeW x)+instance (GFromSchemaFieldTypeWrap w sch t v)+         => GFromSchemaFieldTypeUnion w sch '[t] v where+  fromSchemaFieldTypeUnion (Z x) = fromSchemaFieldTypeW x+  fromSchemaFieldTypeUnion (S _) = error "this should never happen"+instance (GToSchemaFieldTypeWrap w sch t v, GToSchemaFieldTypeUnion w sch ts vs)+         => GToSchemaFieldTypeUnion w sch (t ': ts) (v :+: vs) where+  toSchemaFieldTypeUnion (L1 x) = Z (toSchemaFieldTypeW x)+  toSchemaFieldTypeUnion (R1 r) = S (toSchemaFieldTypeUnion r)+instance (GFromSchemaFieldTypeWrap w sch t v, GFromSchemaFieldTypeUnion w sch ts vs)+         => GFromSchemaFieldTypeUnion w sch (t ': ts) (v :+: vs) where+  fromSchemaFieldTypeUnion (Z x) = L1 (fromSchemaFieldTypeW x)+  fromSchemaFieldTypeUnion (S r) = R1 (fromSchemaFieldTypeUnion r)+-- Weird nested instance produced by GHC+instance ( GToSchemaFieldTypeWrap w sch t1 v1+         , GToSchemaFieldTypeWrap w sch t2 v2+         , GToSchemaFieldTypeUnion w sch ts vs )+         => GToSchemaFieldTypeUnion w sch (t1 ': t2 ': ts) ((v1 :+: v2) :+: vs) where+  toSchemaFieldTypeUnion (L1 (L1 x)) = Z (toSchemaFieldTypeW x)+  toSchemaFieldTypeUnion (L1 (R1 x)) = S (Z (toSchemaFieldTypeW x))+  toSchemaFieldTypeUnion (R1 r)      = S (S (toSchemaFieldTypeUnion r))+instance ( GFromSchemaFieldTypeWrap w sch t1 v1+         , GFromSchemaFieldTypeWrap w sch t2 v2+         , GFromSchemaFieldTypeUnion w sch ts vs )+         => GFromSchemaFieldTypeUnion w sch (t1 ': t2 ': ts) ((v1 :+: v2) :+: vs) where+  fromSchemaFieldTypeUnion (Z x)     = L1 (L1 (fromSchemaFieldTypeW x))+  fromSchemaFieldTypeUnion (S (Z x)) = L1 (R1 (fromSchemaFieldTypeW x))+  fromSchemaFieldTypeUnion (S (S r)) = R1 (fromSchemaFieldTypeUnion r)+++-- ---------------+-- ENUMERATIONS --+------------------++instance {-# OVERLAPPABLE #-}+         (GToSchemaEnumDecompose fmap choices f)+         => GToSchemaTypeDef w sch fmap ('DEnum name choices) f where+  toSchemaTypeDef p x = TEnum (toSchemaEnumDecomp p x)+instance {-# OVERLAPPABLE #-}+         (GFromSchemaEnumDecompose fmap choices f)+         => GFromSchemaTypeDef w sch fmap ('DEnum name choices) f where+  fromSchemaTypeDef p (TEnum x) = fromSchemaEnumDecomp p x+-- This instance removes unneeded metadata from the+-- top of the type.+instance {-# OVERLAPS #-}+         GToSchemaTypeDef w sch fmap ('DEnum name choices) f+         => GToSchemaTypeDef w sch fmap ('DEnum name choices) (D1 meta f) where+  toSchemaTypeDef p (M1 x) = toSchemaTypeDef p x+instance {-# OVERLAPS #-}+         GFromSchemaTypeDef w sch fmap ('DEnum name choices) f+         => GFromSchemaTypeDef w sch fmap ('DEnum name choices) (D1 meta f) where+  fromSchemaTypeDef p x = M1 (fromSchemaTypeDef p x)++-- 'toSchema' for enumerations:+-- 1. recursively decompose the (:+:)s into their atomic components+--    this is done by 'GToSchemaEnumSymbol'+-- 2. for each atomic component, figure out which is the element+--    in the schema's enumeration that it corresponds to+--    this is done by 'MappingRight' and 'Find'+-- 3. from that location, build a 'Proxy' value+--    this is done by 'GToSchemaEnumProxy'+class GToSchemaEnumDecompose (fmap :: Mappings Symbol fs)+                             (choices :: [ChoiceDef fs]) (f :: * -> *) where+  toSchemaEnumDecomp :: Proxy fmap -> f a -> NS Proxy choices+instance (GToSchemaEnumDecompose fmap choices oneway, GToSchemaEnumDecompose fmap choices oranother)+         => GToSchemaEnumDecompose fmap choices (oneway :+: oranother) where+  toSchemaEnumDecomp p (L1 x) = toSchemaEnumDecomp p x+  toSchemaEnumDecomp p (R1 x) = toSchemaEnumDecomp p x+instance GToSchemaEnumProxy choices (FindEnumChoice choices (MappingRight fmap c))+         => GToSchemaEnumDecompose fmap choices (C1 ('MetaCons c p s) f) where+  toSchemaEnumDecomp _ _+    = toSchemaEnumProxy (Proxy @choices) (Proxy @(FindEnumChoice choices (MappingRight fmap c)))+-- Types which have no constructor information cannot be used here++class GToSchemaEnumProxy (choices :: [k]) (w :: Where) where+  toSchemaEnumProxy :: Proxy choices -> Proxy w -> NS Proxy choices+instance GToSchemaEnumProxy (c ': cs) 'Here where+  toSchemaEnumProxy _ _ = Z Proxy+instance forall c cs w. GToSchemaEnumProxy cs w+         => GToSchemaEnumProxy (c ': cs) ('There w) where+  toSchemaEnumProxy _ _ = S (toSchemaEnumProxy (Proxy @cs) (Proxy @w))++-- 'fromSchema' for enumerations:+-- 1. for each element in the list of choices+--    (this iteration is done by 'GFromSchemaEnumDecomp')+--    figure out the constructor it corresponds to+--    this is done by 'MappingLeft' and 'FindCon'+-- 2. from that location, build a 'U1' value wrapped+--    in as many 'L1' and 'R1' required.+--    this is done by 'GFromSchemaEnumU1'+class GFromSchemaEnumDecompose (fmap :: Mappings Symbol fs) (choices :: [ChoiceDef fs]) (f :: * -> *) where+  fromSchemaEnumDecomp :: Proxy fmap -> NS Proxy choices -> f a+instance GFromSchemaEnumDecompose fmap '[] f where+  fromSchemaEnumDecomp _ _ = error "This should never happen"+instance (GFromSchemaEnumU1 f (FindCon f (MappingLeft fmap c)), GFromSchemaEnumDecompose fmap cs f)+         => GFromSchemaEnumDecompose fmap ('ChoiceDef c ': cs) f where+  fromSchemaEnumDecomp _ (Z _) = fromSchemaEnumU1 (Proxy @f) (Proxy @(FindCon f (MappingLeft fmap c)))+  fromSchemaEnumDecomp p (S x) = fromSchemaEnumDecomp p x++class GFromSchemaEnumU1 (f :: * -> *) (w :: Where) where+  fromSchemaEnumU1 :: Proxy f -> Proxy w -> f a+instance GFromSchemaEnumU1 (C1 m U1 :+: rest) 'Here where+  fromSchemaEnumU1 _ _ = L1 (M1 U1)+instance GFromSchemaEnumU1 (C1 m U1) 'Here where+  fromSchemaEnumU1 _ _ = M1 U1+instance forall other rest w. GFromSchemaEnumU1 rest w+         => GFromSchemaEnumU1 (other :+: rest) ('There w) where+  fromSchemaEnumU1 _ _ = R1 (fromSchemaEnumU1 (Proxy @rest) (Proxy @w))++-- ----------+-- RECORDS --+-------------++instance {-# OVERLAPPABLE #-}+         (GToSchemaRecord w sch fmap args f)+         => GToSchemaTypeDef w sch fmap ('DRecord name args) f where+  toSchemaTypeDef p x = TRecord (toSchemaRecord p x)+instance {-# OVERLAPPABLE #-}+         (GFromSchemaRecord w sch fmap args f)+         => GFromSchemaTypeDef w sch fmap ('DRecord name args) f where+  fromSchemaTypeDef p (TRecord x) = fromSchemaRecord p x+-- This instance removes unneeded metadata from the+-- top of the type.+instance {-# OVERLAPS #-}+         GToSchemaTypeDef w sch fmap ('DRecord name args) f+         => GToSchemaTypeDef w sch fmap ('DRecord name args) (D1 meta f) where+  toSchemaTypeDef p (M1 x) = toSchemaTypeDef p x+instance {-# OVERLAPS #-}+         GFromSchemaTypeDef w sch fmap ('DRecord name args) f+         => GFromSchemaTypeDef w sch fmap ('DRecord name args) (D1 meta f) where+  fromSchemaTypeDef p x = M1 (fromSchemaTypeDef p x)+instance {-# OVERLAPS #-}+         GToSchemaTypeDef w sch fmap ('DRecord name args) f+         => GToSchemaTypeDef w sch fmap ('DRecord name args) (C1 meta f) where+  toSchemaTypeDef p (M1 x) = toSchemaTypeDef p x+instance {-# OVERLAPS #-}+         GFromSchemaTypeDef w sch fmap ('DRecord name args) f+         => GFromSchemaTypeDef w sch fmap ('DRecord name args) (C1 meta f) where+  fromSchemaTypeDef p x = M1 (fromSchemaTypeDef p x)++-- 'toSchema' for records:+-- 1. iterate over each field in the schema of the record+--    this is done by 'GToSchemaRecord'+-- 2. figure out the selector (field) in the Haskell type+--    to which that record corresponds to+--    this is done by 'MappingLeft' and 'FindSel'+-- 3. using that location, obtain the value of the field+--    this is done by 'GToSchemaRecordSearch'+--+-- Due to some glitch in 'GHC.Generics', sometimes products+-- are not represented by a linear sequence of ':*:',+-- so we need to handle some cases in a special way+-- (see 'HereLeft' and 'HereRight' instances)++-- | For internal use only: generic conversion of a list of fields.+class GToSchemaRecord (w :: * -> *) (sch :: Schema ts fs) (fmap :: Mappings Symbol fs)+                      (args :: [FieldDef ts fs]) (f :: * -> *) where+  toSchemaRecord :: Proxy fmap -> f a -> NP (Field w sch) args+instance GToSchemaRecord w sch fmap '[] f where+  toSchemaRecord _ _ = Nil+instance ( GToSchemaRecord w sch fmap cs f+         , GToSchemaRecordSearch w sch t f (FindSel f (MappingLeft fmap name)) )+         => GToSchemaRecord w sch fmap ('FieldDef name t ': cs) f where+  toSchemaRecord p x = this  :* toSchemaRecord p x+    where this = Field (toSchemaRecordSearch (Proxy @(FindSel f (MappingLeft fmap name))) x)++class GToSchemaRecordSearch (w :: * -> *) (sch :: Schema ts fs)+                            (t :: FieldType ts) (f :: * -> *) (wh :: Where) where+  toSchemaRecordSearch :: Proxy wh -> f a -> w (FieldValue w sch t)+instance {-# OVERLAPS #-} GToSchemaFieldType Identity sch t v+         => GToSchemaRecordSearch Identity sch t (S1 m (K1 i v)) 'Here where+  toSchemaRecordSearch _ (M1 (K1 x)) = Identity (toSchemaFieldType x)+instance {-# OVERLAPPABLE #-} (Functor w, GToSchemaFieldType w sch t v)+         => GToSchemaRecordSearch w sch t (S1 m (K1 i (w v))) 'Here where+  toSchemaRecordSearch _ (M1 (K1 x)) = toSchemaFieldType <$> x+instance {-# OVERLAPS #-} GToSchemaFieldType Identity sch t v+         => GToSchemaRecordSearch Identity sch t (S1 m (K1 i v) :*: rest) 'Here where+  toSchemaRecordSearch _ (M1 (K1 x) :*: _) = Identity (toSchemaFieldType x)+instance {-# OVERLAPPABLE #-} (Functor w, GToSchemaFieldType w sch t v)+         => GToSchemaRecordSearch w sch t (S1 m (K1 i (w v)) :*: rest) 'Here where+  toSchemaRecordSearch _ (M1 (K1 x) :*: _) = toSchemaFieldType <$> x+instance {-# OVERLAPS #-} GToSchemaFieldType Identity sch t v+         => GToSchemaRecordSearch Identity sch t ((S1 m (K1 i v) :*: other) :*: rest) 'HereLeft where+  toSchemaRecordSearch _ ((M1 (K1 x) :*: _) :*: _) = Identity (toSchemaFieldType x)+instance {-# OVERLAPPABLE #-} (Functor w, GToSchemaFieldType w sch t v)+         => GToSchemaRecordSearch w sch t ((S1 m (K1 i (w v)) :*: other) :*: rest) 'HereLeft where+  toSchemaRecordSearch _ ((M1 (K1 x) :*: _) :*: _) = toSchemaFieldType <$> x+instance {-# OVERLAPS #-} GToSchemaFieldType Identity sch t v+         => GToSchemaRecordSearch Identity sch t ((other :*: S1 m (K1 i v)) :*: rest) 'HereRight where+  toSchemaRecordSearch _ ((_ :*: M1 (K1 x)) :*: _) = Identity (toSchemaFieldType x)+instance {-# OVERLAPPABLE #-} (Functor w, GToSchemaFieldType w sch t v)+         => GToSchemaRecordSearch w sch t ((other :*: S1 m (K1 i (w v))) :*: rest) 'HereRight where+  toSchemaRecordSearch _ ((_ :*: M1 (K1 x)) :*: _) = toSchemaFieldType <$> x+instance forall sch t other rest n w.+         GToSchemaRecordSearch w sch t rest n+         => GToSchemaRecordSearch w sch t (other :*: rest) ('There n) where+  toSchemaRecordSearch _ (_ :*: xs) = toSchemaRecordSearch (Proxy @n) xs++-- 'fromSchema' for records+-- 1. decompose the sequence of products into atomic components+--    until we arrive to the selector metadata 'S1'+--    this is done by 'GFromSchemaRecord'+-- 2. figure out the field in the schema it corresponds to+--    this is done by 'MappingRight' and 'FindField'+-- 3. using that location, obtain the value of the field+--    this is done by 'GFromSchemaRecordSearch'+class GFromSchemaRecord (w :: * -> *) (sch :: Schema ts fs) (fmap :: Mappings Symbol fs)+                        (args :: [FieldDef ts fs]) (f :: * -> *) where+  fromSchemaRecord :: Proxy fmap -> NP (Field w sch) args -> f a+instance {-# OVERLAPS #-}+         (GFromSchemaRecordSearch Identity sch v args (FindField args (MappingRight fmap name)))+         => GFromSchemaRecord Identity sch fmap args (S1 ('MetaSel ('Just name) u ss ds) (K1 i v)) where+  fromSchemaRecord _ x = M1 $ K1 $ runIdentity $ fromSchemaRecordSearch (Proxy @(FindField args (MappingRight fmap name))) x+instance {-# OVERLAPPABLE #-}+         (GFromSchemaRecordSearch w sch v args (FindField args (MappingRight fmap name)))+         => GFromSchemaRecord w sch fmap args (S1 ('MetaSel ('Just name) u ss ds) (K1 i (w v))) where+  fromSchemaRecord _ x = M1 $ K1 $ fromSchemaRecordSearch (Proxy @(FindField args (MappingRight fmap name))) x+instance ( GFromSchemaRecord w sch fmap args oneway+         , GFromSchemaRecord w sch fmap args oranother )+         => GFromSchemaRecord w sch fmap args (oneway :*: oranother) where+  fromSchemaRecord p x =  fromSchemaRecord p x :*: fromSchemaRecord p x+instance GFromSchemaRecord w sch fmap args U1 where+  fromSchemaRecord _ _ = U1++class GFromSchemaRecordSearch (w :: * -> *) (sch :: Schema ts fs)+                              (v :: *) (args :: [FieldDef ts fs]) (wh :: Where) where+  fromSchemaRecordSearch :: Proxy wh -> NP (Field w sch) args -> w v+instance (Functor w, GFromSchemaFieldType w sch t v)+         => GFromSchemaRecordSearch w sch v ('FieldDef name t ': rest) 'Here where+  fromSchemaRecordSearch _ (Field x :* _) = fromSchemaFieldType <$> x+instance forall sch v other rest n w.+         GFromSchemaRecordSearch w sch v rest n+         => GFromSchemaRecordSearch w sch v (other ': rest) ('There n) where+  fromSchemaRecordSearch _ (_ :* xs) = fromSchemaRecordSearch (Proxy @n) xs
+ src/Mu/Schema/Conversion/SchemaToTypes.hs view
@@ -0,0 +1,300 @@+{-# language CPP             #-}+{-# language DataKinds       #-}+{-# language TemplateHaskell #-}+{-# language TypeOperators   #-}+{-|+Description : (Deprecated) Generate a set of Haskell types from a 'Schema'++This module is deprecated. Haskell types+corresponding to schema types should be+written manually.+-}+module Mu.Schema.Conversion.SchemaToTypes (+  generateTypesFromSchema+, Namer+) where++import           Control.Applicative+import           Data.Char+import qualified Data.Map                     as M+import           Data.SOP+import           GHC.Generics                 (Generic)+import           Language.Haskell.TH+import           Language.Haskell.TH.Datatype++import           Mu.Schema.Definition++-- | Generate the name from each new Haskell type+--   from the name given in the schema.+type Namer = String -> String++-- | Generates types to represent each of the types+--   in a given schema. You should call it as:+--   > $(generateTypesFromSchema f 'Schema)+--   where @f@ is a function @String -> String@+--   which obtains the Haskell name for a type+--   given the name in the schema. The second argument+--   is simply the name of the schema.+generateTypesFromSchema :: Namer -> Name -> Q [Dec]+generateTypesFromSchema namer schemaTyName+  = do let schemaTy = ConT schemaTyName+       schDef <- typeToSchemaDef schemaTy+       case schDef of+         Nothing -> fail "schema cannot be parsed"+         Just sd -> concat <$> traverse (typeDefToDecl schemaTy namer) sd++-- Generation of types+-- ===================++typeDefToDecl :: Type -> Namer -> TypeDefB Type String String -> Q [Dec]+-- Records with one field+typeDefToDecl _schemaTy namer (DRecord name [f])+  = do let complete = completeName namer name+       fVar <- newName "f"+       d <- newtypeD (pure [])+                     (mkName complete)+                     [PlainTV fVar]+                     Nothing+                     (pure (RecC (mkName complete) [fieldDefToDecl namer complete fVar f]))+                     [pure (DerivClause Nothing [ConT ''Generic])]+       _wTy <- VarT <$> newName "w"+       -- let hsi = generateHasSchemaInstance wTy schemaTy name complete (fieldMapping complete [f])+       return [d] -- , hsi]+-- Records with more than one field+typeDefToDecl _schemaTy namer (DRecord name fields)+  = do let complete = completeName namer name+       fVar <- newName "f"+       d <- dataD (pure [])+                  (mkName complete)+                  [PlainTV fVar]+                  Nothing+                  [pure (RecC (mkName complete) (map (fieldDefToDecl namer complete fVar) fields))]+                  [pure (DerivClause Nothing [ConT ''Generic])]+       _wTy <- VarT <$> newName "w"+       -- let hsi = generateHasSchemaInstance wTy schemaTy name complete (fieldMapping complete fields)+       return [d] -- , hsi]+-- Enumerations+typeDefToDecl _schemaTy namer (DEnum name choices)+  = do let complete = completeName namer name+       fVar <- newName "f"+       d <- dataD (pure [])+                  (mkName complete)+                  [PlainTV fVar]+                  Nothing+                  [ pure (RecC (mkName (choiceName complete choicename)) [])+                    | ChoiceDef choicename <- choices]+                  [pure (DerivClause Nothing [ConT ''Eq, ConT ''Ord, ConT ''Show, ConT ''Generic])]+       _wTy <- VarT <$> newName "w"+       -- let hsi = generateHasSchemaInstance wTy schemaTy name complete (choiceMapping complete choices)+       return [d] --, hsi]+-- Simple things+typeDefToDecl _ _ (DSimple _)+  = fail "DSimple is not supported"++fieldDefToDecl :: Namer -> String -> Name -> FieldDefB Type String String -> (Name, Bang, Type)+fieldDefToDecl namer complete fVar (FieldDef name ty)+  = ( mkName (fieldName complete name)+    , Bang NoSourceUnpackedness NoSourceStrictness+    , AppT (VarT fVar) (fieldTypeToDecl namer fVar ty) )++{- broken for now+generateBuiltinInstance :: Bool -> Type -> String -> Name -> Dec+generateBuiltinInstance withPrereq wTy complete className+#if MIN_VERSION_template_haskell(2,12,0)+  = StandaloneDerivD Nothing ctx ty+#else+  = StandaloneDerivD ctx ty++#endif+  where+    me  = ConT (mkName complete)+    ctx = [AppT (ConT className) (AppT wTy (AppT me wTy)) | withPrereq]+    ty  = AppT (ConT className) (AppT me wTy)+-}++{-+generateHasSchemaInstance :: Type -> Type -> String -> String -> Type -> Dec+generateHasSchemaInstance wTy schemaTy schemaName complete mapping+  = InstanceD Nothing [AppT (ConT ''Applicative) wTy]+              (AppT (AppT (AppT (AppT (ConT ''HasSchema)+                                      wTy )+                                      schemaTy )+                                      (LitT (StrTyLit schemaName)))+                                      (AppT (ConT (mkName complete)) wTy) )+#if MIN_VERSION_template_haskell(2,15,0)+              [TySynInstD (TySynEqn Nothing+                                    (AppT (AppT (AppT (AppT (ConT ''FieldMapping)+                                                      wTy )+                                                      schemaTy )+                                                      (LitT (StrTyLit schemaName)) )+                                                      (AppT (ConT (mkName complete)) wTy))+                                    mapping) ]+#else+              [TySynInstD ''FieldMapping+                          (TySynEqn [ wTy, schemaTy, LitT (StrTyLit schemaName)+                                    , AppT (ConT (mkName complete)) wTy ]+                                     mapping) ]+#endif+-}++{-+fieldMapping :: String -> [FieldDefB Type String String] -> Type+fieldMapping _complete [] = PromotedNilT+fieldMapping complete (FieldDef name _ : rest)+  = AppT (AppT PromotedConsT thisMapping) (fieldMapping complete rest)+  where thisMapping+          = AppT (AppT (PromotedT '(:->))+                       (LitT (StrTyLit (fieldName complete name))))+                       (LitT (StrTyLit name))++choiceMapping :: String -> [ChoiceDef String] -> Type+choiceMapping _complete [] = PromotedNilT+choiceMapping complete (ChoiceDef name : rest)+  = AppT (AppT PromotedConsT thisMapping) (choiceMapping complete rest)+  where thisMapping+          = AppT (AppT (PromotedT '(:->))+                       (LitT (StrTyLit (choiceName complete name))))+                       (LitT (StrTyLit name))+-}++-- Name manipulation+-- =================++completeName :: Namer -> String -> String+completeName namer name = firstUpper (namer (firstUpper name))++choiceName :: String -> String -> String+choiceName complete cname = firstUpper (complete ++ firstUpper cname)++fieldName :: String -> String -> String+fieldName complete fname = firstLower (complete ++ firstUpper fname)++firstUpper :: String -> String+firstUpper []       = error "Empty names are not allowed"+firstUpper (x:rest) = toUpper x : rest++firstLower :: String -> String+firstLower []       = error "Empty names are not allowed"+firstLower (x:rest) = toLower x : rest++fieldTypeToDecl :: Namer -> Name -> FieldTypeB Type String -> Type+fieldTypeToDecl _namer _fVar TNull+  = ConT ''()+fieldTypeToDecl _namer _fVar (TPrimitive t)+  = t+fieldTypeToDecl namer fVar (TSchematic nm)+  = AppT (ConT (mkName $ completeName namer nm)) (VarT fVar)+fieldTypeToDecl namer fVar (TOption t)+  = AppT (ConT ''Maybe) (fieldTypeToDecl namer fVar t)+fieldTypeToDecl namer fVar (TList t)+  = AppT ListT (fieldTypeToDecl namer fVar t)+fieldTypeToDecl namer fVar (TMap k v)+  = AppT (AppT (ConT ''M.Map) (fieldTypeToDecl namer fVar k)) (fieldTypeToDecl namer fVar v)+fieldTypeToDecl namer fVar (TUnion ts)+  = AppT (AppT (ConT ''NS) (ConT ''I)) (fieldTypeUnion namer fVar ts)++fieldTypeUnion :: Namer -> Name -> [FieldTypeB Type String] -> Type+fieldTypeUnion _ _fVar [] = PromotedNilT+fieldTypeUnion namer fVar (t:ts)+  = AppT (AppT PromotedConsT (fieldTypeToDecl namer fVar t)) (fieldTypeUnion namer fVar ts)++-- Parsing+-- =======++typeToSchemaDef :: Type -> Q (Maybe (SchemaB Type String String))+typeToSchemaDef toplevelty+  = typeToSchemaDef' <$> resolveTypeSynonyms toplevelty+  where+    typeToSchemaDef' :: Type -> Maybe (SchemaB Type String String)+    typeToSchemaDef' expanded+      = do types <- tyList expanded+           mapM typeToTypeDef types++    typeToTypeDef, typeToRecordDef, typeToEnumDef, typeToSimpleType+      :: Type -> Maybe (TypeDefB Type String String)+    typeToTypeDef t+      = typeToRecordDef t <|> typeToEnumDef t <|> typeToSimpleType t+    typeToRecordDef t+      = do (nm, fields) <- tyD2 'DRecord t+           DRecord <$> tyString nm+                   <*> (mapM typeToFieldDef =<< tyList fields)+    typeToEnumDef t+      = do (nm, choices) <- tyD2 'DEnum t+           DEnum <$> tyString nm+                 <*> (mapM typeToChoiceDef =<< tyList choices)+    typeToSimpleType t+      = do innerT <- tyD1 'DSimple t+           DSimple <$> typeToFieldType innerT++    typeToFieldDef :: Type -> Maybe (FieldDefB Type String String)+    typeToFieldDef t+      = do (nm, innerTy) <- tyD2 'FieldDef t+           FieldDef <$> tyString nm+                    <*> typeToFieldType innerTy++    typeToChoiceDef :: Type -> Maybe (ChoiceDef String)+    typeToChoiceDef t+      = do nm <- tyD1 'ChoiceDef t+           ChoiceDef <$> tyString nm++    typeToFieldType :: Type -> Maybe (FieldTypeB Type String)+    typeToFieldType t+      =     TNull <$ tyD0 'TNull t+        <|> TPrimitive <$>tyD1 'TPrimitive t+        <|> (do sch <- tyD1 'TSchematic t+                TSchematic <$> tyString sch)+        <|> (do inner <- tyD1 'TOption t+                TOption <$> typeToFieldType inner)+        <|> (do inner <- tyD1 'TList t+                TList <$> typeToFieldType inner)+        <|> (do (k,v) <- tyD2 'TMap t+                TMap <$> typeToFieldType k <*> typeToFieldType v)+        <|> (do inners <- tyD1 'TUnion t+                TUnion <$> (mapM typeToFieldType =<< tyList inners))++tyString :: Type -> Maybe String+tyString (SigT t _)+  = tyString t+tyString (LitT (StrTyLit s))+  = Just s+tyString _+  = Nothing++tyList :: Type -> Maybe [Type]+tyList (SigT t _)+  = tyList t+tyList PromotedNilT+  = Just []+tyList (AppT (AppT PromotedConsT ty) rest)+  = (ty :) <$> tyList rest+tyList _ = Nothing++tyD0 :: Name -> Type -> Maybe ()+tyD0 name (SigT t _) = tyD0 name t+tyD0 name (PromotedT c)+  | c == name = Just ()+  | otherwise = Nothing+tyD0 _ _ = Nothing++tyD1 :: Name -> Type -> Maybe Type+tyD1 name (SigT t _) = tyD1 name t+tyD1 name (AppT (PromotedT c) x)+  | c == name = Just x+  | otherwise = Nothing+tyD1 _ _ = Nothing++tyD2 :: Name -> Type -> Maybe (Type, Type)+tyD2 name (SigT t _) = tyD2 name t+tyD2 name (AppT (AppT (PromotedT c) x) y)+  | c == name = Just (x, y)+  | otherwise = Nothing+tyD2 _ _ = Nothing++{-+tyD3 :: Name -> Type -> Maybe (Type, Type, Type)+tyD3 name (SigT t _) = tyD3 name t+tyD3 name (AppT (AppT (AppT (PromotedT c) x) y) z)+  | c == name = Just (x, y, z)+  | otherwise = Nothing+tyD3 _ _ = Nothing+-}
+ src/Mu/Schema/Conversion/TypesToSchema.hs view
@@ -0,0 +1,115 @@+{-# language DataKinds            #-}+{-# language PolyKinds            #-}+{-# language TypeFamilies         #-}+{-# language TypeOperators        #-}+{-# language UndecidableInstances #-}+{-|+Description: From 'Schema' to Haskell types.++Obtains a 'Schema' from a set of Haskell types.++Unfortunately, GHC does not allow type families+to appear in instances, so you cannot use the+resulting type directly. Instead, evaluate it+in an interpreter session using @:kind!@ and+copy the result to the file.+-}+module Mu.Schema.Conversion.TypesToSchema (+  SchemaFromTypes+, FromType(..)+, AsRecord, AsEnum+) where++import           Data.Kind+import           Data.Map             as M+import           Data.SOP+import           GHC.Generics+import           GHC.TypeLits++import           Mu.Schema.Definition++-- | Defines whether to turn each Haskell type+--   into a record or an enumeration.+--   Any type not declared in the given list+--   of 'FromType's is considered primitive.+data FromType tn fn+  = -- | Declares that the type should become a record.+    AsRecord' Type tn (Mappings Symbol fn)+    -- | Declares that the type should become an enumeration.+  | AsEnum'   Type tn (Mappings Symbol fn)++-- | Declares that the type should become a record.+type AsRecord t tn = 'AsRecord' t tn '[]+-- | Declares that the type should become an enumeration.+type AsEnum   t tn = 'AsEnum'   t tn '[]++-- | Convert a set of types into a 'Schema'.+type family SchemaFromTypes (f :: [FromType tn fn]) :: Schema tn fn where+  SchemaFromTypes f = SchemaFromTypes' f f++type family SchemaFromTypes' (all :: [FromType tn fn]) (f :: [FromType tn fn]) :: Schema tn fn where+  SchemaFromTypes' all '[] = '[]+  SchemaFromTypes' all (t ': ts) = TypeDefFromType all t ': SchemaFromTypes' all ts++type family TypeDefFromType (all :: [FromType tn fn]) (info :: FromType tn fn)+  :: TypeDef tn fn where+  TypeDefFromType all ('AsRecord' t name mp) = 'DRecord name (FieldsFromType  all mp (Rep t))+  TypeDefFromType all ('AsEnum'   t name mp) = 'DEnum   name (ChoicesFromType all mp (Rep t))++type family FieldsFromType (all :: [FromType tn fn]) (mp :: Mappings Symbol fn) (f :: * -> *)+  :: [FieldDef tn fn] where+  FieldsFromType all mp (x :+: y)+    = TypeError ('Text "sum types cannot be converted to record schemas")+  FieldsFromType all mp (D1 meta f)+    = FieldsFromType all mp f  -- go through data info+  FieldsFromType all mp (C1 meta f)+    = FieldsFromType all mp f  -- go through constructor info+  FieldsFromType all mp (x :*: y)+    = ConcatList (FieldsFromType all mp x) (FieldsFromType all mp y)+  FieldsFromType all mp (S1 ('MetaSel ('Just x) u ss ds) (K1 i t))+    = '[ 'FieldDef (MappingRight mp x) (ChooseFieldType all t) ]+  FieldsFromType all mp v+    = TypeError ('Text "unsupported conversion from " ':<>: 'ShowType v ':<>: 'Text " to record schema")++type family ConcatList (xs :: [k]) (ys :: [k]) :: [k] where+  ConcatList '[]       ys = ys+  ConcatList (x ': xs) ys = x ': ConcatList xs ys++type family ChooseFieldType (all :: [FromType tn fn]) (t :: Type)+  :: FieldType tn where+  ChooseFieldType all () = 'TNull+  ChooseFieldType all (Maybe t) = 'TOption (ChooseFieldType all t)+  ChooseFieldType all [t] = 'TList (ChooseFieldType all t)+  ChooseFieldType all (M.Map k v) = 'TMap (ChooseFieldType all k) (ChooseFieldType all v)+  ChooseFieldType all (NS I choices) = 'TUnion (ChooseFieldUnion all choices)+  ChooseFieldType all t = ChooseFieldPrimitiveOrSchematic t (FindTypeName all t)++type family ChooseFieldUnion (all :: [FromType tn fn]) (t :: [Type])+  :: [FieldType tn] where+  ChooseFieldUnion all '[] = '[]+  ChooseFieldUnion all (t ': ts) = ChooseFieldType all t ': ChooseFieldUnion all ts++type family FindTypeName (all :: [FromType tn fn]) (t :: Type)+  :: Maybe tn where+  FindTypeName '[] t = 'Nothing+  FindTypeName ('AsRecord' t tn mp ': rest) t = 'Just tn+  FindTypeName ('AsEnum'   t tn mp ': rest) t = 'Just tn+  FindTypeName (other ': rest) t = FindTypeName rest t++type family ChooseFieldPrimitiveOrSchematic (t :: Type) (ref :: Maybe tn)+  :: FieldType tn where+  ChooseFieldPrimitiveOrSchematic t ('Just name) = 'TSchematic name+  ChooseFieldPrimitiveOrSchematic t 'Nothing     = 'TPrimitive t++type family ChoicesFromType (all :: [FromType tn fn]) (mp :: Mappings Symbol fn) (f :: * -> *)+  :: [ChoiceDef fn] where+  ChoicesFromType all mp (D1 meta f)+    = ChoicesFromType all mp f  -- go through data info+  ChoicesFromType all mp (x :+: y)+    = ConcatList (ChoicesFromType all mp x) (ChoicesFromType all mp y)+  ChoicesFromType all mp (C1 ('MetaCons cname p s) U1)+    = '[ 'ChoiceDef (MappingRight mp cname) ]  -- go through constructor info+  ChoicesFromType all mp (C1 ('MetaCons cname p s) f)+    = TypeError ('Text "constructor " ':<>: 'ShowType cname ':<>: 'Text "has fields and cannot be turned into an enumeration schema")+  ChoicesFromType all mp v+    = TypeError ('Text "unsupported conversion from " ':<>: 'ShowType v ':<>: 'Text " to enumeration schema")
+ src/Mu/Schema/Definition.hs view
@@ -0,0 +1,232 @@+{-# language DataKinds            #-}+{-# language FlexibleInstances    #-}+{-# language PolyKinds            #-}+{-# language ScopedTypeVariables  #-}+{-# language TypeApplications     #-}+{-# language TypeFamilies         #-}+{-# language TypeOperators        #-}+{-# language UndecidableInstances #-}+{-|+Description : Definition of schemas++This module gives a set of combinators+to define schemas in the sense of Avro+or Protocol Buffers.++In order to re-use definitions at both+the type and term levels, the actual+constructors are defined in types ending+with @B@, and are parametrized by the type+used to describe identifiers.+The versions without the suffix set this+parameter to 'Type', and are thought as the+API to be used in the type-level.+If you use 'reflectSchema' to obtain a term-+level representation, the parameter is set+to 'TypeRep'.+-}+module Mu.Schema.Definition (+-- * Definition of schemas+  Schema', Schema, SchemaB+, TypeDef, TypeDefB(..)+, ChoiceDef(..)+, FieldDef, FieldDefB(..)+, FieldType, FieldTypeB(..)+, (:/:)+-- * One-to-one mappings+, Mapping(..), Mappings+-- ** Finding correspondences+, MappingRight, MappingLeft+-- * Reflection to term-level+, reflectSchema+, reflectFields, reflectChoices+, reflectFieldTypes, reflectFieldType+-- * Supporting type classes+, KnownName(..)+) where++import           Data.Kind+import           Data.Proxy+import           Data.Typeable+import           GHC.TypeLits++-- | A set of type definitions,+--   where the names of types and fields are+--   defined by type-level strings ('Symbol's).+type Schema' = Schema Symbol Symbol++-- | Type names and field names can be of any+--   kind, but for many uses we need a way+--   to turn them into strings at run-time.+--   This class generalizes 'KnownSymbol'.+class KnownName (a :: k) where+  nameVal :: proxy a -> String+instance KnownSymbol s => KnownName (s :: Symbol) where+  nameVal = symbolVal+instance KnownName 'True where+  nameVal _ = "True"+instance KnownName 'False where+  nameVal _ = "False"+instance KnownNat n => KnownName (n :: Nat) where+  nameVal = show . natVal++-- | A set of type definitions.+--   In general, we can use any kind we want for+--   both type and field names, although in practice+--   you always want to use 'Symbol'.+type Schema typeName fieldName+  = SchemaB Type typeName fieldName+-- | A set of type definitions,+--   parametric on type representations.+type SchemaB builtin typeName fieldName+  = [TypeDefB builtin typeName fieldName]++-- | Defines a type in a schema.+--   Each type can be:+--   * a record: a list of key-value pairs,+--   * an enumeration: an element of a list of choices,+--   * a reference to a primitive type.+type TypeDef = TypeDefB Type+-- | Defines a type in a schema,+--   parametric on type representations.+data TypeDefB builtin typeName fieldName+  = -- | A list of key-value pairs.+    DRecord typeName [FieldDefB builtin typeName fieldName]+    -- | An element of a list of choices.+  | DEnum   typeName [ChoiceDef fieldName]+    -- | A reference to a primitive type.+  | DSimple (FieldTypeB builtin typeName)++-- | Defines each of the choices in an enumeration.+newtype ChoiceDef fieldName+  = -- | One single choice from an enumeration.+    ChoiceDef fieldName++-- | Defines a field in a record+--   by a name and the corresponding type.+type FieldDef = FieldDefB Type+-- | Defines a field in a record,+--   parametric on type representations.+data FieldDefB builtin typeName fieldName+  = -- | One single field in a record.+    FieldDef fieldName (FieldTypeB builtin typeName)++-- | Types of fields of a record.+--   References to other types in the same schema+--   are done via the 'TSchematic' constructor.+type FieldType = FieldTypeB Type+-- | Types of fields of a record,+--   parametric on type representations.+data FieldTypeB builtin typeName+  = -- | Null, as found in Avro.+    TNull+    -- | Reference to a primitive type, such as integers or Booleans.+    --   The set of supported primitive types depends on the protocol.+  | TPrimitive builtin+    -- | Reference to another type in the schema.+  | TSchematic typeName+    -- | Optional value.+  | TOption (FieldTypeB builtin typeName)+    -- | List of values.+  | TList   (FieldTypeB builtin typeName)+    -- | Map of values.+    --   The set of supported key types depends on the protocol.+  | TMap    (FieldTypeB builtin typeName) (FieldTypeB builtin typeName)+    -- | Represents a choice between types.+  | TUnion  [FieldTypeB builtin typeName]++-- | Lookup a type in a schema by its name.+type family (sch :: Schema t f) :/: (name :: t) :: TypeDef t f where+  '[] :/: name = TypeError ('Text "Cannot find type " ':<>: 'ShowType name ':<>: 'Text " in the schema")+  ('DRecord name fields  ': rest) :/: name = 'DRecord name fields+  ('DEnum   name choices ': rest) :/: name = 'DEnum   name choices+  (other                 ': rest) :/: name = rest :/: name++-- | Defines a mapping between two elements.+data Mapping  a b = a :-> b+-- | Defines a set of mappings between elements of @a@ and @b@.+type Mappings a b = [Mapping a b]++-- | Finds the corresponding right value of @v@+--   in a mapping @ms@. When the kinds are 'Symbol',+--   return the same value if not found.+type family MappingRight (ms :: Mappings a b) (v :: a) :: b where+  MappingRight '[] (v :: Symbol) = v+  MappingRight '[] v             = TypeError ('Text "Cannot find value " ':<>: 'ShowType v)+  MappingRight ((x ':-> y) ': rest) x = y+  MappingRight (other      ': rest) x = MappingRight rest x++-- | Finds the corresponding left value of @v@+--   in a mapping @ms@. When the kinds are 'Symbol',+--   return the same value if not found.+type family MappingLeft (ms :: Mappings a b) (v :: b) :: a where+  MappingLeft '[] (v :: Symbol) = v+  MappingLeft '[] v             = TypeError ('Text "Cannot find value " ':<>: 'ShowType v)+  MappingLeft ((x ':-> y) ': rest) y = x+  MappingLeft (other      ': rest) y = MappingLeft rest y++class ReflectSchema (s :: Schema tn fn) where+  -- | Reflect a schema into term-level.+  reflectSchema :: Proxy s -> SchemaB TypeRep String String+instance ReflectSchema '[] where+  reflectSchema _ = []+instance (ReflectFields fields, KnownName name, ReflectSchema s)+         => ReflectSchema ('DRecord name fields ': s) where+  reflectSchema _ = DRecord (nameVal (Proxy @name)) (reflectFields (Proxy @fields))+                  : reflectSchema (Proxy @s)+instance (ReflectChoices choices, KnownName name, ReflectSchema s)+         => ReflectSchema ('DEnum name choices ': s) where+  reflectSchema _ = DEnum (nameVal (Proxy @name)) (reflectChoices (Proxy @choices))+                  : reflectSchema (Proxy @s)+instance (ReflectFieldType ty, ReflectSchema s)+         => ReflectSchema ('DSimple ty ': s) where+  reflectSchema _ = DSimple (reflectFieldType (Proxy @ty))+                  : reflectSchema (Proxy @s)++class ReflectFields (fs :: [FieldDef tn fn]) where+  -- | Reflect a list of fields into term-level.+  reflectFields :: Proxy fs -> [FieldDefB TypeRep String String]+instance ReflectFields '[] where+  reflectFields _ = []+instance (KnownName name, ReflectFieldType ty, ReflectFields fs)+         => ReflectFields ('FieldDef name ty ': fs) where+  reflectFields _ = FieldDef (nameVal (Proxy @name)) (reflectFieldType (Proxy @ty))+                  : reflectFields (Proxy @fs)++class ReflectChoices (cs :: [ChoiceDef fn]) where+  -- | Reflect a list of enumeration choices into term-level.+  reflectChoices :: Proxy cs -> [ChoiceDef String]+instance ReflectChoices '[] where+  reflectChoices _ = []+instance (KnownName name, ReflectChoices cs)+         => ReflectChoices ('ChoiceDef name ': cs) where+  reflectChoices _ = ChoiceDef (nameVal (Proxy @name))+                   : reflectChoices (Proxy @cs)++class ReflectFieldType (ty :: FieldType tn) where+  -- | Reflect a schema type into term-level.+  reflectFieldType :: Proxy ty -> FieldTypeB TypeRep String+instance ReflectFieldType 'TNull where+  reflectFieldType _ = TNull+instance (Typeable ty) => ReflectFieldType ('TPrimitive ty) where+  reflectFieldType _ = TPrimitive (typeRep (Proxy @ty))+instance (KnownName nm) => ReflectFieldType ('TSchematic nm) where+  reflectFieldType _ = TSchematic (nameVal (Proxy @nm))+instance (ReflectFieldType t) => ReflectFieldType ('TOption t) where+  reflectFieldType _ = TOption (reflectFieldType (Proxy @t))+instance (ReflectFieldType t) => ReflectFieldType ('TList t) where+  reflectFieldType _ = TList (reflectFieldType (Proxy @t))+instance (ReflectFieldType k, ReflectFieldType v)+         => ReflectFieldType ('TMap k v) where+  reflectFieldType _ = TMap (reflectFieldType (Proxy @k)) (reflectFieldType (Proxy @v))+instance (ReflectFieldTypes ts) => ReflectFieldType ('TUnion ts) where+  reflectFieldType _ = TUnion (reflectFieldTypes (Proxy @ts))++class ReflectFieldTypes (ts :: [FieldType tn]) where+  -- | Reflect a list of schema types into term-level.+  reflectFieldTypes :: Proxy ts -> [FieldTypeB TypeRep String]+instance ReflectFieldTypes '[] where+  reflectFieldTypes _ = []+instance (ReflectFieldType t, ReflectFieldTypes ts)+         => ReflectFieldTypes (t ': ts) where+  reflectFieldTypes _ = reflectFieldType (Proxy @t) : reflectFieldTypes (Proxy @ts)
+ src/Mu/Schema/Examples.hs view
@@ -0,0 +1,105 @@+{-# language DataKinds             #-}+{-# language DeriveAnyClass        #-}+{-# language DeriveGeneric         #-}+{-# language DerivingVia           #-}+{-# language FlexibleInstances     #-}+{-# language GADTs                 #-}+{-# language MultiParamTypeClasses #-}+{-# language PolyKinds             #-}+{-# language QuasiQuotes           #-}+{-# language StandaloneDeriving    #-}+{-# language TemplateHaskell       #-}+{-# language TypeApplications      #-}+{-# language TypeFamilies          #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+{-|+Description : Examples for schema definitions.++Look at the source code of this module.+-}+module Mu.Schema.Examples where++import qualified Data.Aeson                         as J+import           Data.Functor.Identity+import qualified Data.Text                          as T+import           GHC.Generics++import           Mu.Adapter.Json                    ()+import           Mu.Schema+import           Mu.Schema.Conversion.SchemaToTypes++data Person+  = Person { firstName :: T.Text+           , lastName  :: T.Text+           , age       :: Maybe Int+           , gender    :: Maybe Gender+           , address   :: Address }+  deriving (Eq, Show, Generic)+  deriving (ToSchema Identity ExampleSchema "person", FromSchema Identity ExampleSchema "person")+  deriving (J.ToJSON, J.FromJSON)+    via (WithSchema Identity ExampleSchema "person" Person)++data Address+  = Address { postcode :: T.Text+            , country  :: T.Text }+  deriving (Eq, Show, Generic)+  deriving (ToSchema Identity ExampleSchema "address", FromSchema Identity ExampleSchema "address")+  deriving (J.ToJSON, J.FromJSON)+    via (WithSchema Identity ExampleSchema "address" Address)++type GenderFieldMapping+  = '[ "Male"      ':-> "male"+     , "Female"    ':-> "female"+     , "NonBinary" ':-> "nb" ]++data Gender = Male | Female | NonBinary+  deriving (Eq, Show, Generic)+  deriving (ToSchema f ExampleSchema "gender", FromSchema f ExampleSchema "gender")+    via (CustomFieldMapping "gender" GenderFieldMapping Gender)+  deriving (J.ToJSON, J.FromJSON)+    via (WithSchema Identity ExampleSchema "gender" Gender)++-- Schema for these data types+type ExampleSchema+  = '[ 'DEnum   "gender"+               '[ 'ChoiceDef "male"+                , 'ChoiceDef "female"+                , 'ChoiceDef "nb" ]+     , 'DRecord "address"+               '[ 'FieldDef "postcode" ('TPrimitive T.Text)+                , 'FieldDef "country"  ('TPrimitive T.Text) ]+     , 'DRecord "person"+                '[ 'FieldDef "firstName" ('TPrimitive T.Text)+                 , 'FieldDef "lastName"  ('TPrimitive T.Text)+                 , 'FieldDef "age"       ('TOption ('TPrimitive Int))+                 , 'FieldDef "gender"    ('TOption ('TSchematic "gender"))+                 , 'FieldDef "address"   ('TSchematic "address") ]+     ]++$(generateTypesFromSchema (++"Msg") ''ExampleSchema)++{-+type ExampleSchema2+  = SchemaFromTypes '[ AsRecord Person "person"+                     , AsRecord Address "address"+                     , AsEnum Gender "gender" ]+-}+type ExampleSchema2+  = '[ 'DEnum   "gender"+               '[ 'ChoiceDef "Male"+                , 'ChoiceDef "Female"+                , 'ChoiceDef "NonBinary" ]+     , 'DRecord "address"+               '[ 'FieldDef "postcode" ('TPrimitive T.Text)+                , 'FieldDef "country"  ('TPrimitive T.Text) ]+     , 'DRecord "person"+                '[ 'FieldDef "firstName" ('TPrimitive T.Text)+                 , 'FieldDef "lastName"  ('TPrimitive T.Text)+                 , 'FieldDef "age"       ('TOption ('TPrimitive Int))+                 , 'FieldDef "gender"    ('TOption ('TSchematic "gender"))+                 , 'FieldDef "address"   ('TSchematic "address") ]+     ]++type ExampleRegistry+  = '[ 2 ':-> ExampleSchema2, 1 ':-> ExampleSchema]
+ src/Mu/Schema/Interpretation.hs view
@@ -0,0 +1,259 @@+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language FlexibleInstances     #-}+{-# language GADTs                 #-}+{-# language PolyKinds             #-}+{-# language QuantifiedConstraints #-}+{-# language RankNTypes            #-}+{-# language ScopedTypeVariables   #-}+{-# language TypeApplications      #-}+{-# language TypeFamilies          #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+{-|+Description : Interpretation of schemas++This module defines 'Term's which comply with+a given 'Schema'. These 'Term's are the main+form of values used internally by @mu-haskell@.++This module follows the ideas of+<https://reasonablypolymorphic.com/blog/higher-kinded-data/ higher-kinded data>.+In particular, each interpretation of a 'Field'+wraps its contents into a "wrapper" type @w@,+which may add additional behavior to it.+For example, in Protocol Buffers every field is+optional, and this is expressed by setting+@w@ to 'Maybe'.++In this module we make use of 'NP' and 'NS'+as defined by <https://hackage.haskell.org/package/sop-core sop-core>.+These are the n-ary versions of a pair and+'Either', respectively. In other words, 'NP'+puts together a bunch of values of different+types, 'NS' allows you to choose from a bunch+of types.+-}+module Mu.Schema.Interpretation (+  -- * Interpretation+  Term(..), Field(..), FieldValue(..)+, NS(..), NP(..), Proxy(..)+  -- * Transforming the wrapper type+, transWrap, transWrapNoMaps+  -- ** For internal use only+, transFields, transFieldsNoMaps+, transValue, transValueNoMaps+) where++import           Data.Map+import           Data.Proxy+import           Data.SOP++import           Mu.Schema.Definition++-- | Interpretation of a type in a schema.+data Term w (sch :: Schema typeName fieldName) (t :: TypeDef typeName fieldName) where+  -- | A record given by the value of its fields.+  TRecord :: NP (Field w sch) args -> Term w sch ('DRecord name args)+  -- | An enumeration given by one choice.+  TEnum   :: NS Proxy choices      -> Term w sch ('DEnum name choices)+  -- | A primitive value.+  TSimple :: FieldValue w sch t    -> Term w sch ('DSimple t)++-- | Interpretation of a field.+data Field w (sch :: Schema typeName fieldName) (f :: FieldDef typeName fieldName) where+  -- | A single field. Note that the contents are wrapped in a @w@ type constructor.+  Field :: w (FieldValue w sch t) -> Field w sch ('FieldDef name t)++-- | Interpretation of a field type, by giving a value of that type.+data FieldValue w (sch :: Schema typeName fieldName) (t :: FieldType typeName) where+  -- | Null value, as found in Avro and JSON.+  FNull      :: FieldValue w sch 'TNull+  -- | Value of a primitive type.+  FPrimitive :: t -> FieldValue w sch ('TPrimitive t)+  -- | Term of another type in the schema.+  FSchematic :: Term w sch (sch :/: t)+             -> FieldValue w sch ('TSchematic t)+  -- | Optional value.+  FOption    :: Maybe (FieldValue w sch t)+             -> FieldValue w sch ('TOption t)+  -- | List of values.+  FList      :: [FieldValue w sch t]+             -> FieldValue w sch ('TList   t)+  -- | Dictionary (key-value map) of values.+  FMap       :: Ord (FieldValue w sch k)+             => Map (FieldValue w sch k) (FieldValue w sch v)+             -> FieldValue w sch ('TMap k v)+  -- | One single value of one of the specified types.+  FUnion     :: NS (FieldValue w sch) choices+             -> FieldValue w sch ('TUnion choices)++-- | Change the underlying wrapper of a term.+transWrap+  :: forall tn fn (sch :: Schema tn fn) t u v.+     (Functor u, forall k. Ord (FieldValue u sch k) => Ord (FieldValue v sch k))+  => (forall a. u a -> v a)+  -> Term u sch t -> Term v sch t+transWrap n x = case x of+  TRecord f -> TRecord (transFields n f)+  TEnum   c -> TEnum c+  TSimple v -> TSimple (transValue n v)++-- | Change the underlying wrapper of a term.+--   This version assumes that no field is a map,+--   which allows for a more general type.+--   If a map is found, an exception is raised.+transWrapNoMaps+  :: forall tn fn (sch :: Schema tn fn) t u v.+     (Functor u)+  => (forall a. u a -> v a)+  -> Term u sch t -> Term v sch t+transWrapNoMaps n x = case x of+  TRecord f -> TRecord (transFieldsNoMaps n f)+  TEnum   c -> TEnum c+  TSimple v -> TSimple (transValueNoMaps n v)++-- | Change the underlying wrapper of a list of fields.+transFields+  :: forall tn fn (sch :: Schema tn fn) args u v.+     (Functor u, forall k. Ord (FieldValue u sch k) => Ord (FieldValue v sch k))+  => (forall a. u a -> v a)+  -> NP (Field u sch) args -> NP (Field v sch) args+transFields _ Nil = Nil+transFields n (Field v :* rest)+  = Field (n (fmap (transValue n) v)) :* transFields n rest++-- | Change the underlying wrapper of a list of fields.+--   This version assumes no maps are present as fields.+transFieldsNoMaps+  :: forall tn fn (sch :: Schema tn fn) args u v.+     (Functor u)+  => (forall a. u a -> v a)+  -> NP (Field u sch) args -> NP (Field v sch) args+transFieldsNoMaps _ Nil = Nil+transFieldsNoMaps n (Field v :* rest)+  = Field (n (fmap (transValueNoMaps n) v)) :* transFieldsNoMaps n rest++-- | Change the underlying wrapper of a value.+transValue+  :: forall tn fn (sch :: Schema tn fn) l u v.+     (Functor u, forall k. Ord (FieldValue u sch k) => Ord (FieldValue v sch k))+  => (forall a. u a -> v a)+  -> FieldValue u sch l -> FieldValue v sch l+transValue _ FNull          = FNull+transValue _ (FPrimitive y) = FPrimitive y+transValue n (FSchematic t) = FSchematic (transWrap n t)+transValue n (FOption    o) = FOption (transValue n <$> o)+transValue n (FList      l) = FList (transValue n <$> l)+transValue n (FMap       m) = FMap (mapKeys (transValue n) (transValue n <$> m))+transValue n (FUnion     u) = FUnion (transUnion u)+  where+    transUnion :: NS (FieldValue u sch) us -> NS (FieldValue v sch) us+    transUnion (Z z) = Z (transValue n z)+    transUnion (S s) = S (transUnion s)++-- | Change the underlying wrapper of a value.+--   This version assumes that the value is not a map.+transValueNoMaps+  :: forall tn fn (sch :: Schema tn fn) l u v.+     (Functor u)+  => (forall a. u a -> v a)+  -> FieldValue u sch l -> FieldValue v sch l+transValueNoMaps _ FNull          = FNull+transValueNoMaps _ (FPrimitive y) = FPrimitive y+transValueNoMaps n (FSchematic t) = FSchematic (transWrapNoMaps n t)+transValueNoMaps n (FOption    o) = FOption (transValueNoMaps n <$> o)+transValueNoMaps n (FList      l) = FList (transValueNoMaps n <$> l)+transValueNoMaps _ (FMap       _) = error "this should never happen"+transValueNoMaps n (FUnion     u) = FUnion (transUnion u)+  where+    transUnion :: NS (FieldValue u sch) us -> NS (FieldValue v sch) us+    transUnion (Z z) = Z (transValueNoMaps n z)+    transUnion (S s) = S (transUnion s)++-- ===========================+-- CRAZY EQ AND SHOW INSTANCES+-- ===========================++instance All (Eq `Compose` Field w sch) args+         => Eq (Term w sch ('DRecord name args)) where+  TRecord xs == TRecord ys = xs == ys+instance (KnownName name, All (Show `Compose` Field w sch) args)+         => Show (Term w sch ('DRecord name args)) where+  show (TRecord xs) = "record " ++ nameVal (Proxy @name) ++ " { " ++ printFields xs ++ " }"+    where printFields :: forall fs. All (Show `Compose` Field w sch) fs+                      => NP (Field w sch) fs -> String+          printFields Nil         = ""+          printFields (x :* Nil)  = show x+          printFields (x :* rest) = show x ++ ", " ++ printFields rest+instance All (Eq `Compose` Proxy) choices => Eq (Term w sch ('DEnum name choices)) where+  TEnum x == TEnum y = x == y+instance (KnownName name, All KnownName choices, All (Show `Compose` Proxy) choices)+         => Show (Term w sch ('DEnum name choices)) where+  show (TEnum choice) = "enum " ++ nameVal (Proxy @name) ++ " { " ++ printChoice choice ++ " }"+    where printChoice :: forall cs. All KnownName cs => NS Proxy cs -> String+          printChoice (Z p) = nameVal p+          printChoice (S n) = printChoice n+instance Eq (FieldValue w sch t) => Eq (Term w sch ('DSimple t)) where+  TSimple x == TSimple y = x == y+instance Show (FieldValue w sch t) => Show (Term w sch ('DSimple t)) where+  show (TSimple x) = show x++instance (Eq (w (FieldValue w sch t))) => Eq (Field w sch ('FieldDef name t)) where+  Field x == Field y = x == y+instance (KnownName name, Show (w (FieldValue w sch t)))+         => Show (Field w sch ('FieldDef name t)) where+  show (Field x) = nameVal (Proxy @name) ++ ": " ++ show x++instance Eq (FieldValue w sch 'TNull) where+  _ == _ = True+instance Eq t => Eq (FieldValue w sch ('TPrimitive t)) where+  FPrimitive x == FPrimitive y = x == y+instance Eq (Term w sch (sch :/: t)) => Eq (FieldValue w sch ('TSchematic t)) where+  FSchematic x == FSchematic y = x == y+instance Eq (FieldValue w sch t) => Eq (FieldValue w sch ('TOption t)) where+  FOption x == FOption y = x == y+instance Eq (FieldValue w sch t) => Eq (FieldValue w sch ('TList t)) where+  FList x == FList y = x == y+instance (Eq (FieldValue w sch k), Eq (FieldValue w sch v))+         => Eq (FieldValue w sch ('TMap k v)) where+  FMap x == FMap y = x == y+instance All (Eq `Compose` FieldValue w sch) choices+         => Eq (FieldValue w sch ('TUnion choices)) where+  FUnion x == FUnion y = x == y++instance Ord (FieldValue w sch 'TNull) where+  compare _ _ = EQ+instance Ord t => Ord (FieldValue w sch ('TPrimitive t)) where+  compare (FPrimitive x) (FPrimitive y) = compare x y+instance Ord (Term w sch (sch :/: t)) => Ord (FieldValue w sch ('TSchematic t)) where+  compare (FSchematic x) (FSchematic y) = compare x y+instance Ord (FieldValue w sch t) => Ord (FieldValue w sch ('TOption t)) where+  compare (FOption x) (FOption y) = compare x y+instance Ord (FieldValue w sch t) => Ord (FieldValue w sch ('TList t)) where+  compare (FList x) (FList y) = compare x y+instance (Ord (FieldValue w sch k), Ord (FieldValue w sch v))+         => Ord (FieldValue w sch ('TMap k v)) where+  compare (FMap x) (FMap y) = compare x y+instance ( All (Ord `Compose` FieldValue w sch) choices+         , All (Eq  `Compose` FieldValue w sch) choices )+         => Ord (FieldValue w sch ('TUnion choices)) where+  compare (FUnion x) (FUnion y) = compare x y++instance Show (FieldValue w sch 'TNull) where+  show _ = "null"+instance Show t => Show (FieldValue w sch ('TPrimitive t)) where+  show (FPrimitive x) = show x+instance Show (Term w sch (sch :/: t)) => Show (FieldValue w sch ('TSchematic t)) where+  show (FSchematic x) = show x+instance Show (FieldValue w sch t) => Show (FieldValue w sch ('TOption t)) where+  show (FOption Nothing)  = "none"+  show (FOption (Just x)) = "some(" ++ show x ++ ")"+instance Show (FieldValue w sch t) => Show (FieldValue w sch ('TList t)) where+  show (FList xs) = show xs+instance (Show (FieldValue w sch k), Show (FieldValue w sch v))+         => Show (FieldValue w sch ('TMap k v)) where+  show (FMap x) = show x+instance All (Show `Compose` FieldValue w sch) choices+         => Show (FieldValue w sch ('TUnion choices)) where+  show (FUnion x) = show x
+ src/Mu/Schema/Interpretation/Anonymous.hs view
@@ -0,0 +1,105 @@+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language FlexibleInstances     #-}+{-# language GADTs                 #-}+{-# language MultiParamTypeClasses #-}+{-# language PolyKinds             #-}+{-# language StandaloneDeriving    #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+{-|+Description : Anonymous terms for schema types++This module provides "anonymous terms". These+terms can be used when you don't want to write+your own Haskell type, but simply have a quick+and dirty interpretation for a schema type.+An important limitation is that anonymous terms+may only contain primitive fields.++The names of the types exposed in this module+refer to the amount of fields in the record.+Hence, use 'V0' for empty record, 'V1' for a record+with one field, 'V2' for two, and so forth.+-}+module Mu.Schema.Interpretation.Anonymous where++import           Data.SOP++import           Mu.Schema++-- | Anonymous term for a record with zero fields.+data V0 w sch sty where+  V0 :: (sch :/: sty ~ 'DRecord nm '[])+     => V0 w sch sty++deriving instance Show (V0 w sch sty)+deriving instance Eq   (V0 w sch sty)+deriving instance Ord  (V0 w sch sty)++instance (sch :/: sty ~ 'DRecord nm '[])+         => ToSchema w sch sty (V0 w sch sty) where+  toSchema V0 = TRecord Nil+instance (sch :/: sty ~ 'DRecord nm '[])+         => FromSchema w sch sty (V0 w sch sty) where+  fromSchema (TRecord Nil) = V0++-- | Anonymous term for a record with one field.+data V1 w sch sty where+  V1 :: (sch :/: sty+           ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a) ])+     => w a -> V1 w sch sty++deriving instance (Show (w a), sch :/: sty+                                 ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a) ])+                  => Show (V1 w sch sty)+deriving instance (Eq (w a), sch :/: sty+                               ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a) ])+                  => Eq (V1 w sch sty)+deriving instance (Ord (w a), sch :/: sty+                                ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a) ])+                  => Ord (V1 w sch sty)++instance ( Functor w+         , sch :/: sty ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a) ] )+         => ToSchema w sch sty (V1 w sch sty) where+  toSchema (V1 x) = TRecord (Field (FPrimitive <$> x) :* Nil)+instance ( Functor w+         , sch :/: sty ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a) ] )+         => FromSchema w sch sty (V1 w sch sty) where+  fromSchema (TRecord (Field x :* Nil)) = V1 (unPrimitive <$> x)+    where unPrimitive :: FieldValue w sch ('TPrimitive t) -> t+          unPrimitive (FPrimitive l) = l++-- | Anonymous term for a record with two fields.+data V2 w sch sty where+  V2 :: (sch :/: sty+           ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a)+                          , 'FieldDef g ('TPrimitive b) ])+     => w a -> w b -> V2 w sch sty++deriving instance (Show (w a), Show (w b),+                   sch :/: sty ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a)+                                              , 'FieldDef g ('TPrimitive b) ])+                  => Show (V2 w sch sty)+deriving instance (Eq (w a), Eq (w b),+                   sch :/: sty ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a)+                                              , 'FieldDef g ('TPrimitive b) ])+                  => Eq (V2 w sch sty)+deriving instance (Ord (w a), Ord (w b),+                   sch :/: sty ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a)+                                              , 'FieldDef g ('TPrimitive b) ])+                  => Ord (V2 w sch sty)++instance ( Functor w+         , sch :/: sty ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a)+                                      , 'FieldDef g ('TPrimitive b) ] )+         => ToSchema w sch sty (V2 w sch sty) where+  toSchema (V2 x y) = TRecord (Field (FPrimitive <$> x) :* Field (FPrimitive <$> y) :* Nil)+instance ( Functor w+         , sch :/: sty ~ 'DRecord nm '[ 'FieldDef f ('TPrimitive a)+                                      , 'FieldDef g ('TPrimitive b) ] )+         => FromSchema w sch sty (V2 w sch sty) where+  fromSchema (TRecord (Field x :* Field y :* Nil)) = V2 (unPrimitive <$> x) (unPrimitive <$> y)+    where unPrimitive :: FieldValue w sch ('TPrimitive t) -> t+          unPrimitive (FPrimitive l) = l
+ src/Mu/Schema/Interpretation/Schemaless.hs view
@@ -0,0 +1,235 @@+{-# language AllowAmbiguousTypes   #-}+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language FlexibleInstances     #-}+{-# language GADTs                 #-}+{-# language MultiParamTypeClasses #-}+{-# language PolyKinds             #-}+{-# language ScopedTypeVariables   #-}+{-# language StandaloneDeriving    #-}+{-# language TypeApplications      #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+{-|+Description : Terms without an associated schema++In the edges of your application it's useful to+consider terms for which a type-level schema has+not yet been applied. Think of receiving a JSON+document: you can parse it but checking the schema+is an additional step.+-}+module Mu.Schema.Interpretation.Schemaless (+  -- * Terms without an associated schema+  Term(..), Field(..), FieldValue(..)+  -- * Checking and conversion against a schema+, checkSchema, fromSchemalessTerm+  -- * For deserialization to schemaless terms+, ToSchemalessTerm(..), ToSchemalessValue(..)+  -- * For implementors+, CheckSchema+) where++import           Control.Applicative      ((<|>))+import           Data.List                (find)+import qualified Data.Map                 as M+import           Data.Proxy+import           Data.SOP+import qualified Data.Text                as T+import           Data.Typeable++import           Mu.Schema.Class+import           Mu.Schema.Definition+import qualified Mu.Schema.Interpretation as S++-- | Interpretation of a type in a schema.+data Term (w :: * -> *) where+  -- | A record given by the value of its fields.+  TRecord :: [Field w]    -> Term w+  -- | An enumeration given by one choice.+  TEnum   :: Int          -> Term w+  -- | A primitive value.+  TSimple :: FieldValue w -> Term w++deriving instance Eq   (w (FieldValue w)) => Eq   (Term w)+deriving instance Ord  (w (FieldValue w)) => Ord  (Term w)+deriving instance Show (w (FieldValue w)) => Show (Term w)++-- | Interpretation of a field.+data Field (w :: * -> *) where+  -- | A single field given by its name and its value.+  --   Note that the contents are wrapped in a @w@ type constructor.+  Field :: T.Text -> w (FieldValue w) -> Field w++deriving instance Eq   (w (FieldValue w)) => Eq   (Field w)+deriving instance Ord  (w (FieldValue w)) => Ord  (Field w)+deriving instance Show (w (FieldValue w)) => Show (Field w)++-- | Interpretation of a field type, by giving a value of that type.+data FieldValue (w :: * -> *) where+  FNull      :: FieldValue w+  FPrimitive :: (Typeable t, Eq t, Ord t, Show t) => t -> FieldValue w+  FSchematic :: Term w -> FieldValue w+  FOption    :: Maybe (FieldValue w) -> FieldValue w+  FList      :: [FieldValue w] -> FieldValue w+  FMap       :: M.Map (FieldValue w) (FieldValue w) -> FieldValue w++-- | Checks that a schemaless 'Term' obbeys the+--   restrictions for tyoe @t@ of schema @s@.+--   If successful, returns a 'S.Term' indexed+--   by the corresponding schema and type.+--+--   Use this function to check a schemaless terms+--   at the "borders" of your application.+checkSchema+  :: forall (s :: Schema tn fn) (t :: tn) (w :: * -> *).+     (Traversable w, CheckSchema s (s :/: t))+  => Proxy t -> Term w -> Maybe (S.Term w s (s :/: t))+checkSchema _ = checkSchema'++-- | Converts a schemaless term to a Haskell type+--   by going through the corresponding schema type.+fromSchemalessTerm+  :: forall sch w t sty.+     (Traversable w, FromSchema w sch sty t, CheckSchema sch (sch :/: sty))+  => Term w -> Maybe t+fromSchemalessTerm t = fromSchema @_ @_ @w @sch <$> checkSchema (Proxy @sty) t++-- | Deserialization to schemaless terms.+class ToSchemalessTerm t w where+  -- | Turns a document (such as JSON) into a schemaless term.+  --   This function should handle the "compound" types in that format,+  --   such as records and enumerations.+  toSchemalessTerm  :: t -> Term w+-- | Deserialization to schemaless values.+class ToSchemalessValue t w where+  -- | Turns a document (such as JSON) into a schemaless term.+  --   This function should handle the "primitive" types in that format.+  toSchemalessValue :: t -> FieldValue w++-- | Type class used to define the generic 'checkSchema'.+--+--   Exposed for usage in other modules,+--   in particular 'Mu.Schema.Registry'.+class CheckSchema (s :: Schema tn fn) (t :: TypeDef tn fn) where+  checkSchema' :: Traversable w => Term w -> Maybe (S.Term w s t)+class CheckSchemaFields (s :: Schema tn fn) (fields :: [FieldDef tn fn]) where+  checkSchemaFields :: Traversable w => [Field w] -> Maybe (NP (S.Field w s) fields)+class CheckSchemaEnum (choices :: [ChoiceDef fn]) where+  checkSchemaEnumInt  :: Int -> Maybe (NS Proxy choices)+  checkSchemaEnumText :: T.Text -> Maybe (NS Proxy choices)+class CheckSchemaValue (s :: Schema tn fn) (field :: FieldType tn) where+  checkSchemaValue :: Traversable w => FieldValue w -> Maybe (S.FieldValue w s field)+class CheckSchemaUnion (s :: Schema tn fn) (ts :: [FieldType tn]) where+  checkSchemaUnion :: Traversable w => FieldValue w -> Maybe (NS (S.FieldValue w s) ts)++instance CheckSchemaFields s fields => CheckSchema s ('DRecord nm fields) where+  checkSchema' (TRecord fields) = S.TRecord <$> checkSchemaFields fields+  checkSchema' _                = Nothing+instance CheckSchemaFields s '[] where+  checkSchemaFields _ = pure Nil+instance (KnownName nm, CheckSchemaValue s ty, CheckSchemaFields s rest)+         => CheckSchemaFields s ('FieldDef nm ty ': rest) where+  checkSchemaFields fs+    = do let name = T.pack (nameVal (Proxy @nm))+         Field _ v <- find (\(Field fieldName _) -> fieldName == name) fs+         v' <- traverse checkSchemaValue v+         r' <- checkSchemaFields @_ @_ @s @rest fs+         return (S.Field v' :* r')++instance CheckSchemaEnum choices => CheckSchema s ('DEnum nm choices) where+  checkSchema' (TEnum n) = S.TEnum <$> checkSchemaEnumInt n+  checkSchema' (TSimple (FPrimitive (n :: a)))+    = case (eqT @a @Int, eqT @a @T.Text, eqT @a @String) of+        (Just Refl, _, _) -> S.TEnum <$> checkSchemaEnumInt n+        (_, Just Refl, _) -> S.TEnum <$> checkSchemaEnumText n+        (_, _, Just Refl) -> S.TEnum <$> checkSchemaEnumText (T.pack n)+        _                 -> Nothing+  checkSchema' _ = Nothing+instance CheckSchemaEnum '[] where+  checkSchemaEnumInt  _ = Nothing+  checkSchemaEnumText _ = Nothing+instance (KnownName c, CheckSchemaEnum cs)+         => CheckSchemaEnum ('ChoiceDef c ': cs) where+  checkSchemaEnumInt 0 = Just (Z Proxy)+  checkSchemaEnumInt n = S <$> checkSchemaEnumInt (n-1)+  checkSchemaEnumText t+    | t == T.pack (nameVal (Proxy @c)) = Just (Z Proxy)+    | otherwise                        = S <$> checkSchemaEnumText t++instance CheckSchemaValue s f => CheckSchema s ('DSimple f) where+  checkSchema' (TSimple t) = S.TSimple <$> checkSchemaValue t+  checkSchema' _           = Nothing+instance CheckSchemaValue s 'TNull where+  checkSchemaValue FNull = Just S.FNull+  checkSchemaValue _     = Nothing+instance Typeable t => CheckSchemaValue s ('TPrimitive t) where+  checkSchemaValue (FPrimitive (t :: a))+    = case eqT @a @t of+        Just Refl -> Just (S.FPrimitive t)+        Nothing   -> Nothing+  checkSchemaValue _              = Nothing+-- TODO: handle enums better by an if with typedef+instance (CheckSchema s (s :/: t))+         => CheckSchemaValue s ('TSchematic t) where+  checkSchemaValue (FSchematic t) = S.FSchematic <$> checkSchema' t+  checkSchemaValue _              = Nothing+instance CheckSchemaValue s t => CheckSchemaValue s ('TOption t) where+  checkSchemaValue (FOption x) = S.FOption <$> traverse checkSchemaValue x+  checkSchemaValue _           = Nothing+instance CheckSchemaValue s t => CheckSchemaValue s ('TList t) where+  checkSchemaValue (FList xs) = S.FList <$> traverse checkSchemaValue xs+  checkSchemaValue _          = Nothing+-- TODO: how to deal with maps??+instance CheckSchemaUnion s ts => CheckSchemaValue s ('TUnion ts) where+  checkSchemaValue x = S.FUnion <$> checkSchemaUnion x++instance CheckSchemaUnion s '[] where+  checkSchemaUnion _ = Nothing+instance (CheckSchemaValue s t, CheckSchemaUnion s ts)+         => CheckSchemaUnion s (t ': ts) where+  checkSchemaUnion x = Z <$> checkSchemaValue @_ @_ @s @t x <|> S <$> checkSchemaUnion x++-- Boring instances+deriving instance (Show (w (FieldValue w))) => Show (FieldValue w)+instance (Eq (w (FieldValue w))) => Eq (FieldValue w) where+  FNull == FNull = True+  FPrimitive (x :: a) == FPrimitive (y :: b)+    = case eqT @a @b of+        Nothing   -> False+        Just Refl -> x == y+  FSchematic x == FSchematic y = x == y+  FOption    x == FOption    y = x == y+  FList      x == FList      y = x == y+  FMap       x == FMap       y = x == y+  _            == _            = False+instance (Ord (w (FieldValue w))) => Ord (FieldValue w) where+  FNull <= _ = True+  FPrimitive _ <= FNull = False+  FPrimitive (x :: a) <= FPrimitive (y :: b)+    = case eqT @a @b of+        Nothing   -> typeOf x <= typeOf y+        Just Refl -> x <= y+  FPrimitive _ <= _            = True+  FSchematic _ <= FNull        = False+  FSchematic _ <= FPrimitive _ = False+  FSchematic x <= FSchematic y = x <= y+  FSchematic _ <= _            = True+  FOption    _ <= FNull        = False+  FOption    _ <= FPrimitive _ = False+  FOption    _ <= FSchematic _ = False+  FOption    x <= FOption    y = x <= y+  FOption    _ <= _            = True+  FList      _ <= FNull        = False+  FList      _ <= FPrimitive _ = False+  FList      _ <= FSchematic _ = False+  FList      _ <= FOption    _ = False+  FList      x <= FList      y = x <= y+  FList      _ <= _            = True+  FMap       _ <= FNull        = False+  FMap       _ <= FPrimitive _ = False+  FMap       _ <= FSchematic _ = False+  FMap       _ <= FOption    _ = False+  FMap       _ <= FList      _ = False+  FMap       x <= FMap       y = x <= y+  -- FMap       _ <= _            = True
+ src/Mu/Schema/Registry.hs view
@@ -0,0 +1,68 @@+{-# language AllowAmbiguousTypes   #-}+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language FlexibleInstances     #-}+{-# language MultiParamTypeClasses #-}+{-# language PolyKinds             #-}+{-# language ScopedTypeVariables   #-}+{-# language TypeApplications      #-}+{-# language TypeFamilies          #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+{-|+Description : Registry of schemas++A registry of schemas saves the different schemas+supported by an application. Since messages and+protocols may evolve, it's useful to keep an updated+view of the different shapes of data we can handle.++Examples of registries are found in+<https://docs.confluent.io/current/schema-registry/index.html Kafka>+and <https://github.com/higherkindness/compendium Compendium>.+-}+module Mu.Schema.Registry (+  -- * Registry of schemas+  Registry, fromRegistry+  -- * Terms without an associated schema+, SLess.Term(..), SLess.Field(..), SLess.FieldValue(..)+) where++import           Control.Applicative+import           Data.Kind+import           Data.Proxy+import           GHC.TypeLits++import           Mu.Schema.Class+import           Mu.Schema.Definition+import qualified Mu.Schema.Interpretation.Schemaless as SLess++-- | A 'Registry' is defined as a map from+--   version numbers to type-level schemas.+--+--   /Implementation note/: you __must__+--   write newer schemas at the head of the+--   'Registry'. Otherwise, older schemas+--   take precedence during conversion.+type Registry = Mappings Nat Schema'++-- | Converts a schemaless term into a value+--   by checking all the possible schemas in+--   a 'Registry'.+--+--   /Implementation note/: schemas are checked+--   __in the same order__ in which they appear+--   in the 'Registry' definition.+fromRegistry :: forall r t w. FromRegistry w r t+             => SLess.Term w -> Maybe t+fromRegistry = fromRegistry' (Proxy @r)++class FromRegistry (w :: * -> *) (ms :: Registry) (t :: Type) where+  fromRegistry' :: Proxy ms -> SLess.Term w -> Maybe t++instance FromRegistry w '[] t where+  fromRegistry' _ _ = Nothing+instance ( Traversable w, FromSchema w s sty t+         , SLess.CheckSchema s (s :/: sty), FromRegistry w ms t )+         => FromRegistry w ((n ':-> s) ': ms) t where+  fromRegistry' _ t = SLess.fromSchemalessTerm @s @w t <|> fromRegistry' (Proxy @ms) t