packages feed

mu-graphql (empty) → 0.3.0.0

raw patch · 14 files changed

+3155/−0 lines, 14 filesdep +aesondep +asyncdep +attoparsecsetup-changed

Dependencies added: aeson, async, attoparsec, base, bytestring, conduit, graphql-parser, http-types, list-t, mtl, mu-graphql, mu-rpc, mu-schema, parsers, regex-tdfa, scientific, sop-core, stm, stm-chans, stm-conduit, stm-containers, template-haskell, text, unordered-containers, uuid, wai, wai-extra, wai-websockets, warp, warp-tls, websockets

Files

+ 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
+ exe/Main.hs view
@@ -0,0 +1,103 @@+{-# language CPP                   #-}+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language OverloadedStrings     #-}+{-# language PartialTypeSignatures #-}+{-# language PolyKinds             #-}+{-# language ScopedTypeVariables   #-}+{-# language TemplateHaskell       #-}+{-# language TupleSections         #-}+{-# language TypeApplications      #-}+{-# language TypeOperators         #-}+{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}++module Main where++import           Data.Conduit+import           Data.Conduit.Combinators          (yieldMany)+import           Data.List                         (find)+import           Data.Maybe                        (fromMaybe, listToMaybe)+import           Data.Proxy+import qualified Data.Text                         as T+import           Text.Regex.TDFA                   ((=~))+import           Text.Regex.TDFA.Common            (fst3, snd3, thd3)++import           Network.Wai.Handler.Warp          (run)+import           Network.Wai.Middleware.AddHeaders (addHeaders)++import           Mu.GraphQL.Quasi+import           Mu.GraphQL.Server+import           Mu.Schema+import           Mu.Server++#if __GHCIDE__+graphql "ServiceDefinition" "graphql/exe/schema.graphql"+#else+graphql "ServiceDefinition" "exe/schema.graphql"+#endif++-- GraphQL App++main :: IO ()+main = do+  putStrLn "starting GraphQL server on port 8000"+  let hm = addHeaders [+             ("Access-Control-Allow-Origin", "*")+           , ("Access-Control-Allow-Headers", "Content-Type")+           ]+  run 8000 $ hm $ graphQLApp libraryServer+    (Proxy @('Just "Query"))+    (Proxy @'Nothing)+    (Proxy @('Just "Subscription"))++type ServiceMapping = '[+    "Book"   ':-> (Integer, Integer)+  , "Author" ':-> Integer+  ]++library :: [(Integer, T.Text, [(Integer, T.Text)])]+library+  = [ (1, "Robert Louis Stevenson", [(1, "Treasure Island"), (2, "Strange Case of Dr Jekyll and Mr Hyde")])+    , (2, "Immanuel Kant", [(3, "Critique of Pure Reason")])+    , (3, "Michael Ende", [(4, "The Neverending Story"), (5, "Momo")])+    ]++libraryServer :: forall m. (MonadServer m) => ServerT ServiceMapping ServiceDefinition m _+libraryServer+  = resolver ( object @"Book"   ( field  @"id"      bookId+                                , field  @"title"   bookTitle+                                , field  @"author"  bookAuthor )+             , object @"Author" ( field  @"id"      authorId+                                , field  @"name"    authorName+                                , field  @"books"   authorBooks )+             , object @"Query"  ( method @"author"  findAuthor+                                , method @"book"    findBookTitle+                                , method @"authors" allAuthors+                                , method @"books"   allBooks' )+             , object @"Subscription" ( method @"books" allBooksConduit )+             )+  where+    findBook i = find ((==i) . fst3) library++    bookId (_, bid) = pure bid+    bookTitle (aid, bid) = pure $ maybe "" (fromMaybe "" . lookup bid . thd3) (findBook aid)+    bookAuthor (aid, _) = pure aid++    authorId = pure+    authorName aid = pure $ maybe "" snd3 (findBook aid)+    authorBooks aid = pure $ maybe [] (map ((aid,) . fst) . thd3) (findBook aid)++    findAuthor rx = pure $ listToMaybe+      [aid | (aid, name, _) <- library, name =~ rx]++    findBookTitle rx = pure $ listToMaybe+      [(aid, bid) | (aid, _, books) <- library+                  , (bid, title) <- books+                  , title =~ rx]++    allAuthors = pure $ fst3 <$> library+    allBooks = [(aid, bid) | (aid, _, books) <- library, (bid, _) <- books]+    allBooks' = pure allBooks++    allBooksConduit :: ConduitM (Integer, Integer) Void m () -> m ()+    allBooksConduit sink = runConduit $ yieldMany allBooks .| sink
+ exe/schema.graphql view
@@ -0,0 +1,27 @@+type Book {+  id: Int!+  title: String!+  author: Author!+}++type Author {+  id: Int!+  name: String!+  books: [Book!]!+}++type Query {+  author(name: String! = ".*"): Author+  book(title: String! = ".*"): Book+  authors: [Author!]!+  books: [Book!]!+}++type Subscription {+  books: Book!+}++schema {+  query: Query+  subscription: Subscription+}
+ mu-graphql.cabal view
@@ -0,0 +1,80 @@+name:          mu-graphql+version:       0.3.0.0+synopsis:      GraphQL support for Mu+description:   GraphQL servers and clients for Mu-Haskell+cabal-version: >=1.10+license:       Apache-2.0+license-file:  LICENSE+author:        Alejandro Serrano, Flavio Corpa+maintainer:    alejandro.serrano@47deg.com+copyright:     Copyright © 2020 <http://47deg.com 47 Degrees>+category:      Network+build-type:    Simple+homepage:      https://higherkindness.io/mu-haskell/+bug-reports:   https://github.com/higherkindness/mu-haskell/issues+data-files:    exe/*.graphql++library+  exposed-modules:+    Mu.GraphQL.Annotations+    Mu.GraphQL.Quasi+    Mu.GraphQL.Server++  other-modules:+    Mu.GraphQL.Quasi.LostParser+    Mu.GraphQL.Query.Definition+    Mu.GraphQL.Query.Introspection+    Mu.GraphQL.Query.Parse+    Mu.GraphQL.Query.Run+    Mu.GraphQL.Subscription.Protocol++  -- other-extensions:+  build-depends:+      aeson+    , async+    , attoparsec+    , base                  >=4.12 && <5+    , bytestring+    , conduit+    , graphql-parser+    , http-types+    , list-t+    , mtl+    , mu-rpc+    , mu-schema+    , parsers+    , scientific+    , sop-core+    , stm+    , stm-chans+    , stm-conduit+    , stm-containers+    , template-haskell+    , text+    , unordered-containers+    , uuid+    , wai+    , wai-websockets+    , warp+    , warp-tls+    , websockets++  hs-source-dirs:   src+  default-language: Haskell2010+  ghc-options:      -Wall -fprint-potential-instances++executable library-graphql+  main-is:          Main.hs+  hs-source-dirs:   exe+  default-language: Haskell2010+  ghc-options:      -Wall+  build-depends:+      base        >=4.12    && <5+    , conduit+    , mu-graphql+    , mu-rpc+    , mu-schema+    , regex-tdfa  >=1.3.1.0+    , text+    , wai-extra+    , warp
+ src/Mu/GraphQL/Annotations.hs view
@@ -0,0 +1,116 @@+{-# language DataKinds           #-}+{-# language FlexibleInstances   #-}+{-# language PolyKinds           #-}+{-# language ScopedTypeVariables #-}+{-# language TupleSections       #-}+{-# language TypeApplications    #-}+{-# language TypeOperators       #-}+{-# language ViewPatterns        #-}+{-|+Description : Annotations for GraphQL services++GraphQL schemas may contain some information which+cannot be directly represented in a Mu schema or+service definition. The types in this module+can be used with the annotation mechanism in Mu+to provide this additional information.+-}+module Mu.GraphQL.Annotations (+  ValueConst(..)+, DefaultValue+, ReflectValueConst(..)+, fromGQLValueConst+) where++import           Control.Applicative           (Alternative (..))+import           Data.Coerce+import           Data.Proxy+import qualified Data.Text                     as T+import           GHC.TypeLits+import qualified Language.GraphQL.Draft.Syntax as GQL++-- | Specifies the default value of an argument.+--   To be used as an annotation.+data DefaultValue (v :: ValueConst Nat Symbol)++-- | Type-level GraphQL constant values.+--   Due to limitations in type-level literal values+--   floating point constants cannot be represented.+data ValueConst nat symbol+  = VCInt nat        -- ^ Integer.+  | VCString symbol  -- ^ String.+  | VCBoolean Bool   -- ^ Boolean.+  | VCNull           -- ^ Null.+  | VCEnum symbol    -- ^ Enumeration value.+  | VCList [ValueConst nat symbol]  -- ^ List of constant values.+  | VCObject [(symbol, ValueConst nat symbol)]+      -- ^ Object represented by (key, value) tuples.++-- | Turn a 'GQL.ValueConst' coming from parsing+--   in the annotation data type. Mostly used+--   internally to generate Mu schemas from GraphQL schemas.+fromGQLValueConst :: forall f. Alternative f+                  => GQL.ValueConst -> f (ValueConst Integer String)+fromGQLValueConst (GQL.VCInt n)+  = pure $ VCInt (fromIntegral n)+fromGQLValueConst (GQL.VCString (coerce -> s))+  = pure $ VCString $ T.unpack s+fromGQLValueConst (GQL.VCBoolean b)+  = pure $ VCBoolean b+fromGQLValueConst GQL.VCNull+  = pure VCNull+fromGQLValueConst (GQL.VCEnum (coerce -> s))+  = pure $ VCEnum $ T.unpack s+fromGQLValueConst (GQL.VCList (coerce -> xs))+  = VCList <$> traverse fromGQLValueConst xs+fromGQLValueConst (GQL.VCObject (coerce -> o))+  = VCObject <$> traverse fromGQLField o+  where fromGQLField :: GQL.ObjectFieldG GQL.ValueConst+                     -> f (String, ValueConst Integer String)+        fromGQLField (GQL.ObjectFieldG (coerce -> n) v)+          = (T.unpack n,) <$> fromGQLValueConst v+fromGQLValueConst _ = empty++-- | Obtain the GraphQL constant corresponding+--   to a type-level constant. Inhabited by any+--   'ValueConst', but still required to please+--   the type checker.+class ReflectValueConst (v :: ValueConst nat symbol) where+  -- | Obtain the GraphQL constant corresponding+  --   to a type-level constant.+  reflectValueConst :: proxy v -> GQL.ValueConst+instance KnownNat n => ReflectValueConst ('VCInt n) where+  reflectValueConst _ = GQL.VCInt $ fromInteger $ natVal (Proxy @n)+instance KnownSymbol s => ReflectValueConst ('VCString s) where+  reflectValueConst _ = GQL.VCString $ coerce $ T.pack $ symbolVal (Proxy @s)+instance ReflectValueConst ('VCBoolean 'True) where+  reflectValueConst _ = GQL.VCBoolean True+instance ReflectValueConst ('VCBoolean 'False) where+  reflectValueConst _ = GQL.VCBoolean False+instance ReflectValueConst 'VCNull where+  reflectValueConst _ = GQL.VCNull+instance KnownSymbol e => ReflectValueConst ('VCEnum e) where+  reflectValueConst _ = GQL.VCString $ coerce $ T.pack $ symbolVal (Proxy @e)+instance ReflectValueConstList xs => ReflectValueConst ('VCList xs) where+  reflectValueConst _ = GQL.VCList $ coerce $ reflectValueConstList (Proxy @xs)+instance ReflectValueConstObject xs => ReflectValueConst ('VCObject xs) where+  reflectValueConst _ = GQL.VCObject $ coerce $ reflectValueConstObject (Proxy @xs)++class ReflectValueConstList xs where+  reflectValueConstList :: proxy xs -> [GQL.ValueConst]+instance ReflectValueConstList '[] where+  reflectValueConstList _ = []+instance (ReflectValueConst x, ReflectValueConstList xs)+         => ReflectValueConstList (x ': xs) where+  reflectValueConstList _+    = reflectValueConst (Proxy @x) : reflectValueConstList (Proxy @xs)++class ReflectValueConstObject xs where+  reflectValueConstObject :: proxy xs -> [GQL.ObjectFieldG GQL.ValueConst]+instance ReflectValueConstObject '[] where+  reflectValueConstObject _ = []+instance (KnownSymbol a, ReflectValueConst x, ReflectValueConstObject xs)+         => ReflectValueConstObject ( '(a, x) ': xs) where+  reflectValueConstObject _+    = GQL.ObjectFieldG (coerce $ T.pack $ symbolVal (Proxy @a)) (reflectValueConst (Proxy @x))+      : reflectValueConstObject (Proxy @xs)
+ src/Mu/GraphQL/Quasi.hs view
@@ -0,0 +1,202 @@+{-# language DataKinds         #-}+{-# language OverloadedStrings #-}+{-# language TemplateHaskell   #-}+{-# language ViewPatterns      #-}+{-|+Description : Quasi-quoters for GraphQL schemas++Read @.graphql@ files as a 'Mu.Schema.Definition.Schema'+and 'Package' with one 'Service' per object in the schema.+-}+module Mu.GraphQL.Quasi (+  graphql+, graphql'+) where++import           Control.Monad.IO.Class        (liftIO)+import           Data.Coerce                   (coerce)+import qualified Data.HashMap.Strict           as HM+import           Data.List                     (foldl')+import qualified Data.Text                     as T+import qualified Data.Text.IO                  as TIO+import           Data.UUID                     (UUID)+import qualified Language.GraphQL.Draft.Syntax as GQL+import           Language.Haskell.TH++import           Mu.GraphQL.Annotations+import           Mu.GraphQL.Quasi.LostParser   (parseTypeSysDefinition)+import           Mu.Rpc+import           Mu.Schema.Definition++-- | Imports an GraphQL schema definition from a file.+graphql :: String   -- ^ Name for the 'Package' type, the 'Schema' is derived from it+        -> FilePath -- ^ Route to the file+        -> Q [Dec]+graphql name = graphql' (name <> "Schema") name++-- | Imports an GraphQL schema definition from a file.+graphql' :: String   -- ^ Name for the 'Schema' type+         -> String   -- ^ Name for the 'Package' type+         -> FilePath -- ^ Route to the file+         -> Q [Dec]+graphql' scName svName file = do+  schema <- liftIO $ TIO.readFile file+  case parseTypeSysDefinition schema of+    Left e  -> fail ("could not parse graphql spec: " ++ show e)+    Right p -> graphqlToDecls scName svName p++type TypeMap   = HM.HashMap T.Text GQLType+type SchemaMap = HM.HashMap T.Text GQL.OperationType++data Result =+    GQLScalar+  | GQLSchema Type+  | GQLService Type++data GQLType =+    Enum+  | Object+  | Scalar+  | InputObject+  | Other++classifySchema :: [GQL.SchemaDefinition] -> SchemaMap+classifySchema = foldl' schemaToMap HM.empty+  where+    schemaToMap :: SchemaMap -> GQL.SchemaDefinition -> SchemaMap+    schemaToMap mp (GQL.SchemaDefinition _ ops) = foldl' operationToKeyValue mp ops+    operationToKeyValue :: SchemaMap -> GQL.RootOperationTypeDefinition -> SchemaMap+    operationToKeyValue mp (GQL.RootOperationTypeDefinition opType (coerce -> name)) = HM.insert name opType mp++classify :: [GQL.TypeDefinition] -> TypeMap+classify = HM.fromList . (typeToKeyValue <$>)+  where+    typeToKeyValue :: GQL.TypeDefinition -> (T.Text, GQLType)+    typeToKeyValue (GQL.TypeDefinitionScalar (GQL.ScalarTypeDefinition _ (coerce -> name) _))             = (name, Scalar)+    typeToKeyValue (GQL.TypeDefinitionObject (GQL.ObjectTypeDefinition _ (coerce -> name) _ _ _))         = (name, Object)+    typeToKeyValue (GQL.TypeDefinitionInterface (GQL.InterfaceTypeDefinition _ (coerce -> name) _ _))     = (name, Other)+    typeToKeyValue (GQL.TypeDefinitionUnion (GQL.UnionTypeDefinition _ (coerce -> name) _ _))             = (name, Other)+    typeToKeyValue (GQL.TypeDefinitionEnum (GQL.EnumTypeDefinition _ (coerce -> name) _ _))               = (name, Enum)+    typeToKeyValue (GQL.TypeDefinitionInputObject (GQL.InputObjectTypeDefinition _ (coerce -> name) _ _)) = (name, InputObject)++-- | Constructs the GraphQL tree splitting between Schemas and Services.+graphqlToDecls :: String -> String -> [GQL.TypeSystemDefinition] -> Q [Dec]+graphqlToDecls schemaName serviceName allTypes = do+  let schemaName'  = mkName schemaName+      serviceName' = mkName serviceName+      types        = [t | GQL.TypeSystemDefinitionType t <- allTypes]+      schTypes     = [t | GQL.TypeSystemDefinitionSchema t <- allTypes]+      typeMap      = classify types+      schMap       = classifySchema schTypes+  rs <- traverse (typeToDec schemaName' typeMap schMap) types+  let schemaTypes  = [x | GQLSchema  x <- rs]+      serviceTypes = [x | GQLService x <- rs]+  schemaDec <- tySynD schemaName' [] (pure $ typesToList schemaTypes)+  serviceDec <- tySynD serviceName' []+    [t| 'Package ('Just $(textToStrLit $ T.pack serviceName))+                  $(pure $ typesToList serviceTypes) |]+  pure [schemaDec, serviceDec]++-- | Reads a GraphQL 'TypeDefinition' and returns a 'Result'.+typeToDec :: Name -> TypeMap -> SchemaMap -> GQL.TypeDefinition -> Q Result+typeToDec schemaName tm _ (GQL.TypeDefinitionScalar (GQL.ScalarTypeDefinition _ s _)) =+  GQLScalar <$ gqlTypeToType s tm schemaName+typeToDec schemaName tm sm (GQL.TypeDefinitionObject objs) = objToDec objs+  where+    objToDec :: GQL.ObjectTypeDefinition -> Q Result+    objToDec (GQL.ObjectTypeDefinition _ (coerce -> nm) _ _ flds) =+      GQLService <$> [t| 'Service $(textToStrLit nm) '[]+          $(typesToList <$> traverse (gqlFieldToType nm) flds) |]+    gqlFieldToType :: T.Text -> GQL.FieldDefinition -> Q Type+    gqlFieldToType sn (GQL.FieldDefinition _ (coerce -> fnm) args ftyp _) =+      [t| 'Method $(textToStrLit fnm) '[]+            $(typesToList <$> traverse argToType args)+            $(returnType sn ftyp)|]+    returnType :: T.Text -> GQL.GType -> Q Type+    returnType serviceName typ =+      case HM.lookup serviceName sm of+        Just GQL.OperationTypeSubscription -> [t|'RetStream $(retToType typ)|]+        _                                  -> [t|'RetSingle $(retToType typ)|]+    argToType :: GQL.InputValueDefinition -> Q Type+    argToType (GQL.InputValueDefinition _ (coerce -> aname) atype Nothing) =+      [t| 'ArgSingle ('Just $(textToStrLit aname)) '[] $(retToType atype) |]+    argToType (GQL.InputValueDefinition _ (coerce -> aname) atype (Just defs)) =+      [t| 'ArgSingle ('Just $(textToStrLit aname))+                      '[DefaultValue $( defToVConst defs )] $(retToType atype) |]+    defToVConst :: GQL.DefaultValue -> Q Type+    defToVConst (GQL.VCInt _)                         = [t| 'VCInt |]+    defToVConst (GQL.VCFloat _)                       = fail "floats as default arguments are not supported"+    defToVConst (GQL.VCString (coerce -> s))          = [t| 'VCString $(textToStrLit s) |]+    defToVConst (GQL.VCBoolean _)                     = [t| 'VCBoolean|]+    defToVConst GQL.VCNull                            = [t| 'VCNull |]+    defToVConst (GQL.VCEnum (coerce -> e))            = [t| 'VCEnum $(textToStrLit e) |]+    defToVConst (GQL.VCList (GQL.ListValueG xs))      = [t| 'VCList $(typesToList <$> traverse defToVConst xs) |]+    defToVConst (GQL.VCObject (GQL.ObjectValueG obj)) = [t| 'VCObject $(typesToList <$> traverse fromGQLField obj) |]+    fromGQLField :: GQL.ObjectFieldG GQL.ValueConst -> Q Type+    fromGQLField (GQL.ObjectFieldG (coerce -> n) v)   = [t| ($(textToStrLit n), $(defToVConst v)) |]+    retToType :: GQL.GType -> Q Type+    retToType (GQL.TypeNamed (coerce -> False) (coerce -> a)) =+      [t| $(gqlTypeToType a tm schemaName) |]+    retToType (GQL.TypeNamed (coerce -> True) (coerce -> a)) =+      [t| 'OptionalRef $(gqlTypeToType a tm schemaName) |]+    retToType (GQL.TypeList (coerce -> False) (coerce -> a)) =+      [t| 'ListRef $(retToType a) |]+    retToType (GQL.TypeList (coerce -> True) (coerce -> a)) =+      [t| 'OptionalRef ('ListRef $(retToType a)) |]+    retToType _ = fail "this should not happen, please, file an issue"+typeToDec _ _ _ (GQL.TypeDefinitionInterface _)       = fail "interface types are not supported"+typeToDec _ _ _ (GQL.TypeDefinitionUnion _)           = fail "union types are not supported"+typeToDec _ _ _ (GQL.TypeDefinitionEnum enums)        = enumToDecl enums+  where+    enumToDecl :: GQL.EnumTypeDefinition -> Q Result+    enumToDecl (GQL.EnumTypeDefinition _ (coerce -> name) _ symbols) =+      GQLSchema <$> [t|'DEnum $(textToStrLit name)+                              $(typesToList <$> traverse gqlChoiceToType symbols)|]+    gqlChoiceToType :: GQL.EnumValueDefinition -> Q Type+    gqlChoiceToType (GQL.EnumValueDefinition _ (coerce -> c) _) =+      [t|'ChoiceDef $(textToStrLit c)|]+typeToDec _ _ _ (GQL.TypeDefinitionInputObject inpts) = inputObjToDec inpts+  where+    inputObjToDec :: GQL.InputObjectTypeDefinition -> Q Result+    inputObjToDec (GQL.InputObjectTypeDefinition _ (coerce -> name) _ fields) =+        GQLSchema <$> [t|'DRecord $(textToStrLit name)+                                  $(typesToList <$> traverse gqlFieldToType fields)|]+    gqlFieldToType :: GQL.InputValueDefinition -> Q Type+    gqlFieldToType (GQL.InputValueDefinition _ (coerce -> fname) ftype _) =+      [t|'FieldDef $(textToStrLit fname) $(ginputTypeToType ftype)|]+    ginputTypeToType :: GQL.GType -> Q Type+    ginputTypeToType (GQL.TypeNamed (coerce -> False) (coerce -> a)) =+      [t| $(typeToPrimType a) |]+    ginputTypeToType (GQL.TypeNamed (coerce -> True) (coerce -> a)) =+      [t| 'OptionalRef $(typeToPrimType a) |]+    ginputTypeToType (GQL.TypeList (coerce -> False) (coerce -> a)) =+      [t| 'ListRef $(ginputTypeToType a) |]+    ginputTypeToType (GQL.TypeList (coerce -> True) (coerce -> a)) =+      [t| 'OptionalRef ('ListRef $(ginputTypeToType a)) |]+    ginputTypeToType _ = fail "this should not happen, please, file an issue"+    typeToPrimType :: GQL.Name -> Q Type+    typeToPrimType (GQL.unName -> "Int")     = [t|'TPrimitive Integer|]+    typeToPrimType (GQL.unName -> "Float")   = [t|'TPrimitive Double|]+    typeToPrimType (GQL.unName -> "String")  = [t|'TPrimitive T.Text|]+    typeToPrimType (GQL.unName -> "Boolean") = [t|'TPrimitive Bool|]+    typeToPrimType (GQL.unName -> "ID")      = [t|'TPrimitive UUID|]+    typeToPrimType (coerce -> name)          = [t|'TSchematic $(textToStrLit name)|]++gqlTypeToType :: GQL.Name -> TypeMap -> Name -> Q Type+gqlTypeToType (GQL.unName -> "Int") _  _     = [t|'PrimitiveRef Integer|]+gqlTypeToType (GQL.unName -> "Float") _ _    = [t|'PrimitiveRef Double|]+gqlTypeToType (GQL.unName -> "String") _ _   = [t|'PrimitiveRef T.Text|]+gqlTypeToType (GQL.unName -> "Boolean") _ _  = [t|'PrimitiveRef Bool|]+gqlTypeToType (GQL.unName -> "ID") _ _       = [t|'PrimitiveRef UUID|]+gqlTypeToType (coerce -> name) tm schemaName =+  let schemaRef = [t|'SchemaRef $(conT schemaName) $(textToStrLit name)|]+   in case HM.lookup name tm of+        Just Enum        -> schemaRef+        Just InputObject -> schemaRef+        _                -> [t|'ObjectRef $(textToStrLit name)|]++typesToList :: [Type] -> Type+typesToList = foldr (AppT . AppT PromotedConsT) PromotedNilT++textToStrLit :: T.Text -> Q Type+textToStrLit = litT . strTyLit . T.unpack
+ src/Mu/GraphQL/Quasi/LostParser.hs view
@@ -0,0 +1,38 @@+module Mu.GraphQL.Quasi.LostParser (+  -- * The Lost 'Parser'™️+  parseTypeSysDefinition+) where++import           Control.Applicative           ((<|>))+import           Data.Attoparsec.Text          (Parser, many1)+import qualified Data.Text                     as T+import           Language.GraphQL.Draft.Parser (nameParser, runParser, schemaDocument)+import qualified Language.GraphQL.Draft.Syntax as GQL+import           Text.Parser.Token             (braces, symbol, whiteSpace)++schemaDefinition :: Parser GQL.SchemaDefinition+schemaDefinition = symbol "schema" *> braces (+    GQL.SchemaDefinition+      <$> pure Nothing -- ignore [Directive]+      <*> many1 rootOperationParser+  )++rootOperationParser :: Parser GQL.RootOperationTypeDefinition+rootOperationParser =+  GQL.RootOperationTypeDefinition+    <$> (operationTypeParser <* symbol ":")+    <*> (GQL.NamedType <$> nameParser)++operationTypeParser :: Parser GQL.OperationType+operationTypeParser =+      GQL.OperationTypeQuery <$ symbol "query"+  <|> GQL.OperationTypeMutation <$ symbol "mutation"+  <|> GQL.OperationTypeSubscription <$ symbol "subscription"++typeSystemDefinition :: Parser [GQL.TypeSystemDefinition]+typeSystemDefinition = whiteSpace *> (concat <$> many1 (+      (\(GQL.SchemaDocument d) -> GQL.TypeSystemDefinitionType <$> d) <$> schemaDocument+  <|> (: []) . GQL.TypeSystemDefinitionSchema <$> schemaDefinition ))++parseTypeSysDefinition :: T.Text -> Either T.Text [GQL.TypeSystemDefinition]+parseTypeSysDefinition = runParser typeSystemDefinition
+ src/Mu/GraphQL/Query/Definition.hs view
@@ -0,0 +1,114 @@+{-# language DataKinds           #-}+{-# language GADTs               #-}+{-# language PolyKinds           #-}+{-# language ScopedTypeVariables #-}+{-# language TypeOperators       #-}+module Mu.GraphQL.Query.Definition where++import           Data.SOP.NP+import           Data.SOP.NS+import           Data.Text+import qualified Language.GraphQL.Draft.Syntax as GQL+import           Mu.Rpc+import           Mu.Schema++data Document (p :: Package snm mnm anm)+              (qr :: Maybe snm) (mut :: Maybe snm) (sub :: Maybe snm) where+  QueryDoc+    :: LookupService ss qr ~ 'Service qr qanns qms+    => ServiceQuery ('Package pname ss) (LookupService ss qr)+    -> Document ('Package pname ss) ('Just qr) mut sub+  MutationDoc+    :: LookupService ss mut ~ 'Service mut manns mms+    => ServiceQuery ('Package pname ss) (LookupService ss mut)+    -> Document ('Package pname ss) qr ('Just mut) sub+  SubscriptionDoc+    :: LookupService ss sub ~ 'Service sub manns mms+    => OneMethodQuery ('Package pname ss) (LookupService ss sub)+    -> Document ('Package pname ss) qr mut ('Just sub)++type ServiceQuery (p :: Package snm mnm anm) (s :: Service snm mnm anm)+  = [OneMethodQuery p s]++data OneMethodQuery (p :: Package snm mnm anm) (s :: Service snm mnm anm) where+  OneMethodQuery+    :: Maybe Text+    -> NS (ChosenMethodQuery p) ms+    -> OneMethodQuery p ('Service nm anns ms)+  -- the special '__typename' field+  TypeNameQuery+    :: Maybe Text+    -> OneMethodQuery p ('Service nm anns ms)+  -- introspection fields+  SchemaQuery+    :: Maybe Text+    -> GQL.SelectionSet+    -> OneMethodQuery p ('Service nm anns ms)+  TypeQuery+    :: Maybe Text+    -> Text+    -> GQL.SelectionSet+    -> OneMethodQuery p ('Service nm anns ms)++data ChosenMethodQuery (p :: Package snm mnm anm) (m :: Method snm mnm anm) where+  ChosenMethodQuery+    :: NP (ArgumentValue p) args+    -> ReturnQuery p r+    -> ChosenMethodQuery p ('Method mname anns args r)++data ArgumentValue (p :: Package snm mnm anm) (a :: Argument snm anm) where+  ArgumentValue  :: ArgumentValue' p r+                 -> ArgumentValue p ('ArgSingle aname anns r)+  ArgumentStream :: ArgumentValue' p ('ListRef r)+                 -> ArgumentValue p ('ArgStream aname anns r)++data ArgumentValue' (p :: Package snm mnm anm) (r :: TypeRef snm) where+  ArgPrimitive :: t -> ArgumentValue' p ('PrimitiveRef t)+  ArgSchema    :: Term sch (sch :/: sty)+               -> ArgumentValue' p ('SchemaRef sch sty)+  ArgList      :: [ArgumentValue' p r]+               -> ArgumentValue' p ('ListRef r)+  ArgOptional  :: Maybe (ArgumentValue' p r)+               -> ArgumentValue' p ('OptionalRef r)++data ReturnQuery (p :: Package snm mnm anm) (r :: Return snm) where+  RNothing :: ReturnQuery p 'RetNothing+  RSingle  :: ReturnQuery' p r -> ReturnQuery p ('RetSingle r)+  RStream  :: ReturnQuery' p r -> ReturnQuery p ('RetStream r)++data ReturnQuery' (p :: Package snm mnm anm) (r :: TypeRef snm) where+  RetPrimitive :: ReturnQuery' p ('PrimitiveRef t)+  RetSchema    :: SchemaQuery sch (sch :/: sty)+               -> ReturnQuery' p ('SchemaRef sch sty)+  RetList      :: ReturnQuery' p r -> ReturnQuery' p ('ListRef r)+  RetOptional  :: ReturnQuery' p r -> ReturnQuery' p ('OptionalRef r)+  RetObject    :: ServiceQuery ('Package pname ss) (LookupService ss s)+               -> ReturnQuery' ('Package pname ss) ('ObjectRef s)++data SchemaQuery (sch :: Schema tn fn) (t :: TypeDef tn fn) where+  QueryEnum   :: SchemaQuery sch ('DEnum nm choices)+  QueryRecord :: [OneFieldQuery sch fs]+              -> SchemaQuery sch ('DRecord ty fs)++data OneFieldQuery (sch :: Schema tn fn) (fs :: [FieldDef tn fn]) where+  OneFieldQuery+    :: Maybe Text+    -> NS (ChosenFieldQuery sch) fs+    -> OneFieldQuery sch fs+  TypeNameFieldQuery+    :: Maybe Text+    -> OneFieldQuery sch fs++data ChosenFieldQuery (sch :: Schema tn fn) (f :: FieldDef tn fn) where+  ChosenFieldQuery+    :: ReturnSchemaQuery sch r+    -> ChosenFieldQuery sch ('FieldDef name r)++data ReturnSchemaQuery (sch :: Schema tn fn) (r :: FieldType tn) where+  RetSchPrimitive :: ReturnSchemaQuery sch ('TPrimitive t)+  RetSchSchema    :: SchemaQuery sch (sch :/: sty)+                  -> ReturnSchemaQuery sch ('TSchematic sty)+  RetSchList      :: ReturnSchemaQuery sch r+                  -> ReturnSchemaQuery sch ('TList r)+  RetSchOptional  :: ReturnSchemaQuery sch r+                  -> ReturnSchemaQuery sch ('TOption r)
+ src/Mu/GraphQL/Query/Introspection.hs view
@@ -0,0 +1,356 @@+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language FlexibleInstances     #-}+{-# language MultiParamTypeClasses #-}+{-# language OverloadedStrings     #-}+{-# language ScopedTypeVariables   #-}+{-# language TypeApplications      #-}+{-# language TypeFamilies          #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+module Mu.GraphQL.Query.Introspection where++import           Control.Monad.Writer+import qualified Data.HashMap.Strict  as HM+import qualified Data.HashSet         as S+import           Data.Int             (Int32)+import           Data.Maybe           (catMaybes, fromMaybe)+import           Data.Proxy+import qualified Data.Text            as T+import           GHC.TypeLits+import           Mu.Rpc+import qualified Mu.Schema            as Mu++type TypeMap = HM.HashMap T.Text Type++data Schema+  = Schema { queryType        :: Maybe T.Text+           , mutationType     :: Maybe T.Text+           , subscriptionType :: Maybe T.Text+           , types            :: TypeMap }+  deriving Show++data Type+  = Type+    { kind       :: TypeKind+    , typeName   :: Maybe T.Text+    , fields     :: [Field]+    , enumValues :: [EnumValue]+    , ofType     :: Maybe Type+    }+  | TypeRef { to :: T.Text }+  deriving Show++data Field+  = Field+    { fieldName :: T.Text+    , args      :: [Input]+    , fieldType :: Type+    }+  deriving Show++data Input+  = Input+    { inputName         :: T.Text+    , inputDefaultValue :: Maybe T.Text+    , inputType         :: Type+    }+  deriving Show++newtype EnumValue+  = EnumValue { enumValueName :: T.Text }+  deriving Show++data TypeKind+  = SCALAR+  | OBJECT+  | INTERFACE+  | UNION+  | ENUM+  | INPUT_OBJECT+  | LIST+  | NON_NULL+  deriving Show++tSimple :: T.Text -> Type+tSimple t = Type SCALAR (Just t) [] [] Nothing++tList :: Type -> Type+tList = Type LIST Nothing [] [] . Just++tNonNull :: Type -> Type+tNonNull = Type NON_NULL Nothing [] [] . Just++unwrapNonNull :: Type -> Maybe Type+unwrapNonNull (Type NON_NULL _ _ _ x) = x+unwrapNonNull _                       = Nothing++-- BUILD INTROSPECTION DATA+-- ========================++class Introspect (p :: Package')+                 (qr :: Maybe Symbol)+                 (mut :: Maybe Symbol)+                 (sub :: Maybe Symbol) where+  introspect+    :: Proxy p -> Proxy qr -> Proxy mut -> Proxy sub -> Schema++instance ( IntrospectServices ss sub+         , KnownMaybeSymbol qr+         , KnownMaybeSymbol mut+         , KnownMaybeSymbol sub)+         => Introspect ('Package nm ss) qr mut sub where+  introspect _ _ _ _+    = let (_, ts) = runWriter $+           introspectServices (Proxy @ss) (Proxy @sub) >>+           tell (HM.fromList (+             (\i -> (i, tSimple i)) <$> ["Null", "Int", "Float", "String", "Boolean", "ID"]))+          -- return only reachable types+          qrS  = maybeSymbolVal (Proxy @qr)+          mutS = maybeSymbolVal (Proxy @mut)+          subS = maybeSymbolVal (Proxy @sub)+          initials = S.fromList $ catMaybes [qrS, mutS, subS]+          reach = reachableFrom ts initials+          --+          finalTs = HM.filterWithKey (\k _ -> k `S.member` reach) ts+      in Schema qrS mutS subS finalTs++reachableFrom :: TypeMap -> S.HashSet T.Text -> S.HashSet T.Text+reachableFrom mp tys+  = let tys' = S.toList tys+        fromThis = S.fromList . reachableFromOne <$> tys'+        allReachable = S.unions fromThis+    in if tys == allReachable+          then tys+          else reachableFrom mp allReachable+  where+    reachableFromOne :: T.Text -> [T.Text]+    reachableFromOne t+      = case HM.lookup t mp of+          Just ty@Type {}+            -> t : concatMap reachableFromField (fields ty)+          _ -> error "this should never happen"++    reachableFromField :: Field -> [T.Text]+    reachableFromField f+      = reachableFromType (fieldType f) ++ concatMap reachableFromInput (args f)++    reachableFromInput :: Input -> [T.Text]+    reachableFromInput i = reachableFromType (inputType i)++    reachableFromType :: Type -> [T.Text]+    reachableFromType (TypeRef t) = [t]+    reachableFromType t@Type {}+      = case ofType t of+          Just t' -> reachableFromType t'+          Nothing -> case typeName t of+            Just tn -> [tn]+            Nothing -> []++class KnownMaybeSymbol (s :: Maybe Symbol) where+  maybeSymbolVal :: Proxy s -> Maybe T.Text+instance KnownSymbol s => KnownMaybeSymbol ('Just s) where+  maybeSymbolVal _ = Just $ T.pack $ symbolVal (Proxy @s)+instance KnownMaybeSymbol 'Nothing where+  maybeSymbolVal _ = Nothing++type family IsSub (sname :: Symbol) (sub :: Maybe Symbol) :: Bool where+  IsSub sname 'Nothing      = 'False+  IsSub sname ('Just sname) = 'True+  IsSub sname ('Just other) = 'False++class IntrospectServices (ss :: [Service']) (sub :: Maybe Symbol) where+  introspectServices+    :: Proxy ss -> Proxy sub -> Writer TypeMap ()+instance IntrospectServices '[] sub where+  introspectServices _ _ = pure ()+instance ( KnownSymbol sname+         , IntrospectFields smethods (IsSub sname sub)+         , IntrospectServices ss sub )+         => IntrospectServices ('Service sname sanns smethods ': ss) sub where+  introspectServices _ psub = do+    let name = T.pack $ symbolVal (Proxy @sname)+    fs <- introspectFields (Proxy @smethods) (Proxy @(IsSub sname sub))+    let t = Type OBJECT (Just name) fs [] Nothing+    -- add this one to the mix+    tell (HM.singleton name t)+    -- continue with the rest+    introspectServices (Proxy @ss) psub++class IntrospectFields (fs :: [Method']) (isSub :: Bool) where+  introspectFields+    :: Proxy fs -> Proxy isSub -> Writer TypeMap [Field]+instance IntrospectFields '[] isSub where+  introspectFields _ _ = pure []+instance ( KnownSymbol mname+         , IntrospectInputs margs+         , IntrospectReturn mret isSub+         , IntrospectFields fs isSub)+         => IntrospectFields ('Method mname manns margs mret ': fs) isSub where+  introspectFields _ pIsSub = do+    let name = T.pack $ symbolVal (Proxy @mname)+    inputs <- introspectInputs (Proxy @margs)+    ret <- introspectReturn (Proxy @mret) pIsSub+    let this = Field name inputs ret+    (this :) <$> introspectFields (Proxy @fs) pIsSub++class IntrospectInputs (args :: [Argument']) where+  introspectInputs+    :: Proxy args -> Writer TypeMap [Input]+instance IntrospectInputs '[] where+  introspectInputs _ = pure []+instance ( KnownMaybeSymbol nm+         , IntrospectTypeRef r+         , IntrospectInputs args )+         => IntrospectInputs ('ArgSingle nm anns r ': args) where+  introspectInputs _ = do+    let nm = maybeSymbolVal (Proxy @nm)+    t <- introspectTypeRef (Proxy @r) False+    -- TODO: Find default value+    let this = Input (fromMaybe "arg" nm) Nothing t+    (this :) <$> introspectInputs (Proxy @args)+instance ( KnownMaybeSymbol nm+         , IntrospectTypeRef r+         , IntrospectInputs args )+         => IntrospectInputs ('ArgStream nm anns r ': args) where+  introspectInputs _ = do+    let nm = maybeSymbolVal (Proxy @nm)+    t <- tList <$> introspectTypeRef (Proxy @r) False+    -- TODO: Find default value+    let this = Input (fromMaybe "arg" nm) Nothing t+    (this :) <$> introspectInputs (Proxy @args)++class IntrospectReturn (r :: Return Symbol) (isSub :: Bool) where+  introspectReturn+    :: Proxy r -> Proxy isSub -> Writer TypeMap Type++instance IntrospectReturn 'RetNothing isSub where+  introspectReturn _ _ = pure $ tSimple "Null"+instance IntrospectTypeRef t+         => IntrospectReturn ('RetSingle t) isSub where+  introspectReturn _ _ = introspectTypeRef (Proxy @t) True+instance IntrospectTypeRef t+         => IntrospectReturn ('RetStream t) 'False where+  introspectReturn _ _ = tList <$> introspectTypeRef (Proxy @t) True+instance IntrospectTypeRef t+         => IntrospectReturn ('RetStream t) 'True where+  introspectReturn _ _ = introspectTypeRef (Proxy @t) True++class IntrospectTypeRef (tr :: TypeRef Symbol) where+  introspectTypeRef+    :: Proxy tr -> Bool -> Writer TypeMap Type++instance IntrospectTypeRef ('PrimitiveRef Bool) where+  introspectTypeRef _ _ = pure $ tNonNull $ tSimple "Boolean"+instance IntrospectTypeRef ('PrimitiveRef Int32) where+  introspectTypeRef _ _ = pure $ tNonNull $ tSimple "Int"+instance IntrospectTypeRef ('PrimitiveRef Integer) where+  introspectTypeRef _ _ = pure $ tNonNull $ tSimple "Int"+instance IntrospectTypeRef ('PrimitiveRef Double) where+  introspectTypeRef _ _ = pure $ tNonNull $ tSimple "Float"+instance IntrospectTypeRef ('PrimitiveRef String) where+  introspectTypeRef _ _ = pure $ tNonNull $ tSimple "String"+instance IntrospectTypeRef ('PrimitiveRef T.Text) where+  introspectTypeRef _ _ = pure $ tNonNull $ tSimple "String"++instance (IntrospectTypeRef r)+         => IntrospectTypeRef ('ListRef r) where+  introspectTypeRef _ isRet = tList <$> introspectTypeRef (Proxy @r) isRet+instance (IntrospectTypeRef r)+         => IntrospectTypeRef ('OptionalRef r) where+  introspectTypeRef _ isRet = do+    r <- introspectTypeRef (Proxy @r) isRet+    pure $ fromMaybe r (unwrapNonNull r)++instance (KnownSymbol o)+         => IntrospectTypeRef ('ObjectRef o) where+  introspectTypeRef _ _+    = pure $ TypeRef $ T.pack $ symbolVal (Proxy @o)++instance (IntrospectSchema sch, KnownSymbol t)+         => IntrospectTypeRef ('SchemaRef sch t) where+  introspectTypeRef _ isRet = do+    let (k, suffix) = if isRet then (OBJECT, "R") else (INPUT_OBJECT, "")+    introspectSchema k suffix (Proxy @sch)+    pure $ TypeRef $ T.pack (symbolVal (Proxy @t)) <> suffix++class IntrospectSchema (ts :: [Mu.TypeDef Symbol Symbol]) where+  introspectSchema+    :: TypeKind -> T.Text -> Proxy ts -> Writer TypeMap ()+instance IntrospectSchema '[] where+  introspectSchema _ _ _ = pure ()+instance (KnownSymbol name, IntrospectSchemaFields fields, IntrospectSchema ts)+         => IntrospectSchema ('Mu.DRecord name fields ': ts) where+  introspectSchema k suffix _ = do+    let name = T.pack (symbolVal (Proxy @name)) <> suffix+        fs   = introspectSchemaFields suffix (Proxy @fields)+        t    = Type k (Just name) fs [] Nothing+    -- add this one to the mix+    tell (HM.singleton name t)+    -- continue with the rest+    introspectSchema k suffix (Proxy @ts)+instance (KnownSymbol name, IntrospectSchemaEnum choices, IntrospectSchema ts)+         => IntrospectSchema ('Mu.DEnum name choices ': ts) where+  introspectSchema k suffix _ = do+    let name = T.pack (symbolVal (Proxy @name)) <> suffix+        cs   = introspectSchemaEnum (Proxy @choices)+        t    = Type ENUM (Just name) [] cs Nothing+    -- add this one to the mix+    tell (HM.singleton name t)+    -- continue with the rest+    introspectSchema k suffix (Proxy @ts)++class IntrospectSchemaFields (fs :: [Mu.FieldDef Symbol Symbol]) where+  introspectSchemaFields+    :: T.Text -> Proxy fs -> [Field]+instance IntrospectSchemaFields '[] where+  introspectSchemaFields _ _ = []+instance (KnownSymbol fname,IntrospectSchemaFieldType r, IntrospectSchemaFields fs)+         => IntrospectSchemaFields ('Mu.FieldDef fname r ': fs) where+  introspectSchemaFields suffix _+    = let name = T.pack $ symbolVal (Proxy @fname)+          ret  = introspectSchemaFieldType suffix (Proxy @r)+          this = Field name [] ret+      in this : introspectSchemaFields suffix (Proxy @fs)++class IntrospectSchemaFieldType (t :: Mu.FieldType Symbol) where+  introspectSchemaFieldType+    :: T.Text -> Proxy t -> Type++instance IntrospectSchemaFieldType ('Mu.TPrimitive Bool) where+  introspectSchemaFieldType _ _ = tNonNull $ tSimple "Boolean"+instance IntrospectSchemaFieldType ('Mu.TPrimitive Int32) where+  introspectSchemaFieldType _ _ = tNonNull $ tSimple "Int"+instance IntrospectSchemaFieldType ('Mu.TPrimitive Integer) where+  introspectSchemaFieldType _ _ = tNonNull $ tSimple "Int"+instance IntrospectSchemaFieldType ('Mu.TPrimitive Double) where+  introspectSchemaFieldType _ _ = tNonNull $ tSimple "Float"+instance IntrospectSchemaFieldType ('Mu.TPrimitive String) where+  introspectSchemaFieldType _ _ = tNonNull $ tSimple "String"+instance IntrospectSchemaFieldType ('Mu.TPrimitive T.Text) where+  introspectSchemaFieldType _ _ = tNonNull $ tSimple "String"++instance (IntrospectSchemaFieldType r)+         => IntrospectSchemaFieldType ('Mu.TList r) where+  introspectSchemaFieldType suffix _+    = tList $ introspectSchemaFieldType suffix (Proxy @r)+instance (IntrospectSchemaFieldType r)+         => IntrospectSchemaFieldType ('Mu.TOption r) where+  introspectSchemaFieldType suffix _+    = let r = introspectSchemaFieldType suffix (Proxy @r)+      in fromMaybe r (unwrapNonNull r)++instance (KnownSymbol nm)+         => IntrospectSchemaFieldType ('Mu.TSchematic nm) where+  introspectSchemaFieldType suffix _+    = TypeRef $ T.pack (symbolVal (Proxy @nm)) <> suffix++class IntrospectSchemaEnum (c :: [Mu.ChoiceDef Symbol]) where+  introspectSchemaEnum :: Proxy c -> [EnumValue]+instance IntrospectSchemaEnum '[] where+  introspectSchemaEnum _ = []+instance (KnownSymbol nm, IntrospectSchemaEnum cs)+         => IntrospectSchemaEnum ('Mu.ChoiceDef nm ': cs) where+  introspectSchemaEnum _+    = let this = EnumValue $ T.pack $ symbolVal (Proxy @nm)+      in this : introspectSchemaEnum (Proxy @cs)
+ src/Mu/GraphQL/Query/Parse.hs view
@@ -0,0 +1,768 @@+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language FlexibleInstances     #-}+{-# language GADTs                 #-}+{-# language MultiParamTypeClasses #-}+{-# language OverloadedStrings     #-}+{-# language PolyKinds             #-}+{-# language ScopedTypeVariables   #-}+{-# language TypeApplications      #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+{-# language ViewPatterns          #-}+{-# OPTIONS_GHC -Wincomplete-patterns -fno-warn-orphans #-}++module Mu.GraphQL.Query.Parse where++import           Control.Monad.Except+import qualified Data.Aeson                    as A+import           Data.Coerce                   (coerce)+import qualified Data.Foldable                 as F+import qualified Data.HashMap.Strict           as HM+import           Data.Int                      (Int32)+import           Data.Kind+import           Data.List                     (find)+import           Data.Maybe+import           Data.Proxy+import           Data.Scientific               (floatingOrInteger)+import           Data.SOP.NS+import qualified Data.Text                     as T+import           GHC.TypeLits+import qualified Language.GraphQL.Draft.Syntax as GQL++import           Mu.GraphQL.Annotations+import           Mu.GraphQL.Query.Definition+import           Mu.Rpc+import           Mu.Schema++type VariableMapC = HM.HashMap T.Text GQL.ValueConst+type VariableMap  = HM.HashMap T.Text GQL.Value+type FragmentMap  = HM.HashMap T.Text GQL.FragmentDefinition++instance A.FromJSON GQL.ValueConst where+  parseJSON A.Null       = pure GQL.VCNull+  parseJSON (A.Bool b)   = pure $ GQL.VCBoolean b+  parseJSON (A.String s) = pure $ GQL.VCString $ coerce s+  parseJSON (A.Number n) = pure $ either GQL.VCFloat GQL.VCInt $ floatingOrInteger n+  parseJSON (A.Array xs) = GQL.VCList . GQL.ListValueG . F.toList <$> traverse A.parseJSON xs+  parseJSON (A.Object o) = GQL.VCObject . GQL.ObjectValueG . fmap toObjFld . HM.toList <$> traverse A.parseJSON o+    where+      toObjFld :: (T.Text, GQL.ValueConst) -> GQL.ObjectFieldG GQL.ValueConst+      toObjFld (k, v) = GQL.ObjectFieldG (coerce k) v++parseDoc ::+  forall qr mut sub p f.+  ( MonadError T.Text f, ParseTypedDoc p qr mut sub ) =>+  Maybe T.Text -> VariableMapC ->+  GQL.ExecutableDocument ->+  f (Document p qr mut sub)+-- If there's no operation name, there must be only one query+parseDoc Nothing vmap (GQL.ExecutableDocument defns)+  = case GQL.partitionExDefs defns of+      ([unnamed], [], frs)+        -> parseTypedDocQuery HM.empty (fragmentsToMap frs) unnamed+      ([], [named], frs)+        -> parseTypedDoc vmap (fragmentsToMap frs) named+      ([], [], _) -> throwError "no operation to execute"+      (_,  [], _) -> throwError "more than one unnamed query"+      ([], _, _)  -> throwError "more than one named operation but no 'operationName' given"+      (_,  _, _)  -> throwError "both named and unnamed queries, but no 'operationName' given"+-- If there's an operation name, look in the named queries+parseDoc (Just operationName) vmap (GQL.ExecutableDocument defns)+  = case GQL.partitionExDefs defns of+      (_, named, frs) -> maybe notFound (parseTypedDoc vmap (fragmentsToMap frs)) (find isThis named)+    where isThis (GQL._todName -> Just nm)+            = GQL.unName nm == operationName+          isThis _ = False+          notFound :: MonadError T.Text f => f a+          notFound = throwError $ "operation '" <> operationName <> "' was not found"++fragmentsToMap :: [GQL.FragmentDefinition] -> FragmentMap+fragmentsToMap = HM.fromList . map fragmentToThingy+  where fragmentToThingy :: GQL.FragmentDefinition -> (T.Text, GQL.FragmentDefinition)+        fragmentToThingy f = (GQL.unName $ GQL._fdName f, f)++parseTypedDoc ::+  (MonadError T.Text f, ParseTypedDoc p qr mut sub) =>+  VariableMapC -> FragmentMap ->+  GQL.TypedOperationDefinition ->+  f (Document p qr mut sub)+parseTypedDoc vmap frmap tod+  = let defVmap = parseVariableMap (GQL._todVariableDefinitions tod)+        finalVmap = constToValue <$> HM.union vmap defVmap  -- first one takes precedence+    in case GQL._todType tod of+        GQL.OperationTypeQuery+          -> parseTypedDocQuery finalVmap frmap (GQL._todSelectionSet tod)+        GQL.OperationTypeMutation+          -> parseTypedDocMutation finalVmap frmap (GQL._todSelectionSet tod)+        GQL.OperationTypeSubscription+          -> parseTypedDocSubscription finalVmap frmap (GQL._todSelectionSet tod)++class ParseTypedDoc (p :: Package')+                    (qr :: Maybe Symbol) (mut :: Maybe Symbol) (sub :: Maybe Symbol) where+  parseTypedDocQuery ::+    MonadError T.Text f =>+    VariableMap -> FragmentMap ->+    GQL.SelectionSet ->+    f (Document p qr mut sub)+  parseTypedDocMutation ::+    MonadError T.Text f =>+    VariableMap -> FragmentMap ->+    GQL.SelectionSet ->+    f (Document p qr mut sub)+  parseTypedDocSubscription ::+    MonadError T.Text f =>+    VariableMap -> FragmentMap ->+    GQL.SelectionSet ->+    f (Document p qr mut sub)++instance+  ( p ~ 'Package pname ss,+    LookupService ss qr ~ 'Service qr qanns qmethods,+    KnownName qr, ParseMethod p qmethods,+    LookupService ss mut ~ 'Service mut manns mmethods,+    KnownName mut, ParseMethod p mmethods,+    LookupService ss sub ~ 'Service sub sanns smethods,+    KnownName sub, ParseMethod p smethods+  ) => ParseTypedDoc p ('Just qr) ('Just mut) ('Just sub) where+  parseTypedDocQuery vmap frmap sset+    = QueryDoc <$> parseQuery Proxy Proxy vmap frmap sset+  parseTypedDocMutation vmap frmap sset+    = MutationDoc <$> parseQuery Proxy Proxy vmap frmap sset+  parseTypedDocSubscription vmap frmap sset+    = do q <- parseQuery Proxy Proxy vmap frmap sset+         case q of+           [one] -> pure $ SubscriptionDoc one+           _     -> throwError "subscriptions may only have one field"++instance+  ( p ~ 'Package pname ss,+    LookupService ss qr ~ 'Service qr qanns qmethods,+    KnownName qr, ParseMethod p qmethods,+    LookupService ss mut ~ 'Service mut manns mmethods,+    KnownName mut, ParseMethod p mmethods+  ) => ParseTypedDoc p ('Just qr) ('Just mut) 'Nothing where+  parseTypedDocQuery vmap frmap sset+    = QueryDoc <$> parseQuery Proxy Proxy vmap frmap sset+  parseTypedDocMutation vmap frmap sset+    = MutationDoc <$> parseQuery Proxy Proxy vmap frmap sset+  parseTypedDocSubscription _ _ _+    = throwError "no subscriptions are defined in the schema"++instance+  ( p ~ 'Package pname ss,+    LookupService ss qr ~ 'Service qr qanns qmethods,+    KnownName qr, ParseMethod p qmethods,+    LookupService ss sub ~ 'Service sub sanns smethods,+    KnownName sub, ParseMethod p smethods+  ) => ParseTypedDoc p ('Just qr) 'Nothing ('Just sub) where+  parseTypedDocQuery vmap frmap sset+    = QueryDoc <$> parseQuery Proxy Proxy vmap frmap sset+  parseTypedDocMutation _ _ _+    = throwError "no mutations are defined in the schema"+  parseTypedDocSubscription vmap frmap sset+    = do q <- parseQuery Proxy Proxy vmap frmap sset+         case q of+           [one] -> pure $ SubscriptionDoc one+           _     -> throwError "subscriptions may only have one field"++instance+  ( p ~ 'Package pname ss,+    LookupService ss qr ~ 'Service qr qanns qmethods,+    KnownName qr, ParseMethod p qmethods+  ) => ParseTypedDoc p ('Just qr) 'Nothing 'Nothing where+  parseTypedDocQuery vmap frmap sset+    = QueryDoc <$> parseQuery Proxy Proxy vmap frmap sset+  parseTypedDocMutation _ _ _+    = throwError "no mutations are defined in the schema"+  parseTypedDocSubscription _ _ _+    = throwError "no subscriptions are defined in the schema"++instance+  ( p ~ 'Package pname ss,+    LookupService ss mut ~ 'Service mut manns mmethods,+    KnownName mut, ParseMethod p mmethods,+    LookupService ss sub ~ 'Service sub sanns smethods,+    KnownName sub, ParseMethod p smethods+  ) => ParseTypedDoc p 'Nothing ('Just mut) ('Just sub) where+  parseTypedDocQuery _ _ _+    = throwError "no queries are defined in the schema"+  parseTypedDocMutation vmap frmap sset+    = MutationDoc <$> parseQuery Proxy Proxy vmap frmap sset+  parseTypedDocSubscription vmap frmap sset+    = do q <- parseQuery Proxy Proxy vmap frmap sset+         case q of+           [one] -> pure $ SubscriptionDoc one+           _     -> throwError "subscriptions may only have one field"++instance+  ( p ~ 'Package pname ss,+    LookupService ss mut ~ 'Service mut manns mmethods,+    KnownName mut, ParseMethod p mmethods+  ) => ParseTypedDoc p 'Nothing ('Just mut) 'Nothing where+  parseTypedDocQuery _ _ _+    = throwError "no queries are defined in the schema"+  parseTypedDocMutation vmap frmap sset+    = MutationDoc <$> parseQuery Proxy Proxy vmap frmap sset+  parseTypedDocSubscription _ _ _+    = throwError "no subscriptions are defined in the schema"++instance+  ( p ~ 'Package pname ss,+    LookupService ss sub ~ 'Service sub sanns smethods,+    KnownName sub, ParseMethod p smethods+  ) => ParseTypedDoc p 'Nothing 'Nothing ('Just sub) where+  parseTypedDocQuery _ _ _+    = throwError "no queries are defined in the schema"+  parseTypedDocMutation _ _ _+    = throwError "no mutations are defined in the schema"+  parseTypedDocSubscription vmap frmap sset+    = do q <- parseQuery Proxy Proxy vmap frmap sset+         case q of+           [one] -> pure $ SubscriptionDoc one+           _     -> throwError "subscriptions may only have one field"++instance+  ParseTypedDoc p 'Nothing 'Nothing 'Nothing where+  parseTypedDocQuery _ _ _+    = throwError "no queries are defined in the schema"+  parseTypedDocMutation _ _ _+    = throwError "no mutations are defined in the schema"+  parseTypedDocSubscription _ _ _+    = throwError "no subscriptions are defined in the schema"++parseVariableMap :: [GQL.VariableDefinition] -> VariableMapC+parseVariableMap vmap+  = HM.fromList [(GQL.unName (GQL.unVariable v), def)+                | GQL.VariableDefinition v _ (Just def) <- vmap]++constToValue :: GQL.ValueConst -> GQL.Value+constToValue (GQL.VCInt n)     = GQL.VInt n+constToValue (GQL.VCFloat n)   = GQL.VFloat n+constToValue (GQL.VCString n)  = GQL.VString n+constToValue (GQL.VCBoolean n) = GQL.VBoolean n+constToValue GQL.VCNull        = GQL.VNull+constToValue (GQL.VCEnum n)    = GQL.VEnum n+constToValue (GQL.VCList (GQL.ListValueG n))+  = GQL.VList $ GQL.ListValueG $ constToValue <$> n+constToValue (GQL.VCObject (GQL.ObjectValueG n))+  = GQL.VObject $ GQL.ObjectValueG+      [ GQL.ObjectFieldG a (constToValue v) | GQL.ObjectFieldG a v <- n ]++parseQuery ::+  forall (p :: Package') (s :: Symbol) pname ss sanns methods f.+  ( MonadError T.Text f, p ~ 'Package pname ss,+    LookupService ss s ~ 'Service s sanns methods,+    KnownName s, ParseMethod p methods+  ) =>+  Proxy p ->+  Proxy s ->+  VariableMap -> FragmentMap -> GQL.SelectionSet ->+  f (ServiceQuery p (LookupService ss s))+parseQuery _ _ _ _ [] = pure []+parseQuery pp ps vmap frmap (GQL.SelectionField fld : ss)+  = (++) <$> (maybeToList <$> fieldToMethod fld)+         <*> parseQuery pp ps vmap frmap ss+  where+    fieldToMethod :: GQL.Field -> f (Maybe (OneMethodQuery p ('Service sname sanns methods)))+    fieldToMethod (GQL.Field alias name args dirs sels)+      | any (shouldSkip vmap) dirs+      = pure Nothing+      | GQL.unName name == "__typename"+      = case (args, sels) of+          ([], []) -> pure $ Just $ TypeNameQuery $ GQL.unName . GQL.unAlias <$> alias+          _        -> throwError "__typename does not admit arguments nor selection of subfields"+      | GQL.unName name == "__schema"+      = case args of+          [] -> Just . SchemaQuery (GQL.unName . GQL.unAlias <$> alias) <$> unFragment frmap sels+          _  -> throwError "__schema does not admit selection of subfields"+      | GQL.unName name == "__type"+      = let alias' = GQL.unName . GQL.unAlias <$> alias+            getString (GQL.VString s)   = Just $ coerce s+            getString (GQL.VVariable v) = HM.lookup (coerce v) vmap >>= getString+            getString _                 = Nothing+        in case args of+          [GQL.Argument _ val]+            -> case getString val of+                 Just s -> Just . TypeQuery alias' s <$> unFragment frmap sels+                 _      -> throwError "__type requires a string argument"+          _ -> throwError "__type requires one single argument"+      | otherwise+      = Just . OneMethodQuery (GQL.unName . GQL.unAlias <$> alias)+         <$> selectMethod (T.pack $ nameVal (Proxy @s)) vmap frmap name args sels+parseQuery pp ps vmap frmap (GQL.SelectionFragmentSpread (GQL.FragmentSpread nm dirs) : ss)+  | Just fr <- HM.lookup (GQL.unName nm) frmap+  = if not (any (shouldSkip vmap) dirs) && not (any (shouldSkip vmap) $ GQL._fdDirectives fr)+       then (++) <$> parseQuery pp ps vmap frmap (GQL._fdSelectionSet fr)+                 <*> parseQuery pp ps vmap frmap ss+       else parseQuery pp ps vmap frmap ss+  | otherwise  -- the fragment definition was not found+  = throwError $ "fragment '" <> GQL.unName nm <> "' was not found"+parseQuery _ _ _ _ (_ : _)  -- Inline fragments are not yet supported+  = throwError "inline fragments are not (yet) supported"++shouldSkip :: VariableMap -> GQL.Directive -> Bool+shouldSkip vmap (GQL.Directive (GQL.unName -> nm) [GQL.Argument (GQL.unName -> ifn) v])+  | nm == "skip", ifn == "if"+  = case valueParser' @'[] @('TPrimitive Bool) vmap "" v of+      Right (FPrimitive b) -> b+      _                    -> False+  | nm == "include", ifn == "if"+  = case valueParser' @'[] @('TPrimitive Bool) vmap "" v of+      Right (FPrimitive b) -> not b+      _                    -> False+shouldSkip _ _ = False++unFragment :: MonadError T.Text f+           => FragmentMap -> GQL.SelectionSet -> f GQL.SelectionSet+unFragment _ [] = pure []+unFragment frmap (GQL.SelectionFragmentSpread (GQL.FragmentSpread nm _) : ss)+  | Just fr <- HM.lookup (GQL.unName nm) frmap+  = (++) <$> unFragment frmap (GQL._fdSelectionSet fr)+         <*> unFragment frmap ss+  | otherwise  -- the fragment definition was not found+  = throwError $ "fragment '" <> GQL.unName nm <> "' was not found"+unFragment frmap (GQL.SelectionField (GQL.Field al nm args dir innerss) : ss)+  = (:) <$> (GQL.SelectionField . GQL.Field al nm args dir <$> unFragment frmap innerss)+        <*> unFragment frmap ss+unFragment _ _+  = throwError "inline fragments are not (yet) supported"++class ParseMethod (p :: Package') (ms :: [Method']) where+  selectMethod ::+    MonadError T.Text f =>+    T.Text ->+    VariableMap ->+    FragmentMap ->+    GQL.Name ->+    [GQL.Argument] ->+    GQL.SelectionSet ->+    f (NS (ChosenMethodQuery p) ms)++instance ParseMethod p '[] where+  selectMethod tyName _ _ (GQL.unName -> wanted) _ _+    = throwError $ "field '" <> wanted <> "' was not found on type '" <> tyName <> "'"+instance+  (KnownSymbol mname, ParseMethod p ms, ParseArgs p args, ParseDifferentReturn p r) =>+  ParseMethod p ('Method mname manns args r ': ms)+  where+  selectMethod tyName vmap frmap w@(GQL.unName -> wanted) args sels+    | wanted == mname+    = Z <$> (ChosenMethodQuery <$> parseArgs vmap args+                               <*> parseDiffReturn vmap frmap wanted sels)+    | otherwise+    = S <$> selectMethod tyName vmap frmap w args sels+    where+      mname = T.pack $ nameVal (Proxy @mname)++class ParseArgs (p :: Package') (args :: [Argument']) where+  parseArgs :: MonadError T.Text f+            => VariableMap+            -> [GQL.Argument]+            -> f (NP (ArgumentValue p) args)++instance ParseArgs p '[] where+  parseArgs _ _ = pure Nil+-- one single argument without name+instance ParseArg p a+         => ParseArgs p '[ 'ArgSingle 'Nothing anns a ] where+  parseArgs vmap [GQL.Argument _ x]+    = (\v -> ArgumentValue v :* Nil) <$> parseArg' vmap "arg" x+  parseArgs _ _+    = throwError "this field receives one single argument"+instance ParseArg p a+         => ParseArgs p '[ 'ArgStream 'Nothing anns a ] where+  parseArgs vmap [GQL.Argument _ x]+    = (\v -> ArgumentStream v :* Nil) <$> parseArg' vmap "arg" x+  parseArgs _ _+    = throwError "this field receives one single argument"+-- more than one argument+instance (KnownName aname, ParseArg p a, ParseArgs p as, FindDefaultArgValue aanns)+         => ParseArgs p ('ArgSingle ('Just aname) aanns a ': as) where+  parseArgs vmap args+    = let aname = T.pack $ nameVal (Proxy @aname)+      in case find ((== nameVal (Proxy @aname)) . T.unpack . GQL.unName . GQL._aName) args of+        Just (GQL.Argument _ x)+          -> (:*) <$> (ArgumentValue <$> parseArg' vmap aname x)+                  <*> parseArgs vmap args+        Nothing+          -> do x <- findDefaultArgValue (Proxy @aanns) aname+                (:*) <$> (ArgumentValue <$> parseArg' vmap aname (constToValue x))+                     <*> parseArgs vmap args+instance (KnownName aname, ParseArg p a, ParseArgs p as, FindDefaultArgValue aanns)+         => ParseArgs p ('ArgStream ('Just aname) aanns a ': as) where+  parseArgs vmap args+    = let aname = T.pack $ nameVal (Proxy @aname)+      in case find ((== nameVal (Proxy @aname)) . T.unpack . GQL.unName . GQL._aName) args of+        Just (GQL.Argument _ x)+          -> (:*) <$> (ArgumentStream <$> parseArg' vmap aname x)+                  <*> parseArgs vmap args+        Nothing+          -> do x <- findDefaultArgValue (Proxy @aanns) aname+                (:*) <$> (ArgumentStream <$> parseArg' vmap aname (constToValue x))+                     <*> parseArgs vmap args++class FindDefaultArgValue (vs :: [Type]) where+  findDefaultArgValue :: MonadError T.Text f+                      => Proxy vs+                      -> T.Text+                      -> f GQL.ValueConst+instance FindDefaultArgValue '[] where+  findDefaultArgValue _ aname+    = throwError $ "argument '" <> aname <> "' was not given a value, and has no default one"+instance {-# OVERLAPPABLE #-} FindDefaultArgValue xs+         => FindDefaultArgValue (x ': xs) where+  findDefaultArgValue _ = findDefaultArgValue (Proxy @xs)+instance {-# OVERLAPS #-} ReflectValueConst v+         => FindDefaultArgValue (DefaultValue v ': xs) where+  findDefaultArgValue _ _ = pure $ reflectValueConst (Proxy @v)++parseArg' :: (ParseArg p a, MonadError T.Text f)+          => VariableMap+          -> T.Text+          -> GQL.Value+          -> f (ArgumentValue' p a)+parseArg' vmap aname (GQL.VVariable (GQL.unName . GQL.unVariable -> x))+  = case HM.lookup x vmap of+      Nothing -> throwError $ "variable '" <> x <> "' was not found"+      Just v  -> parseArg vmap aname v+parseArg' vmap aname v = parseArg vmap aname v++class ParseArg (p :: Package') (a :: TypeRef Symbol) where+  parseArg :: MonadError T.Text f+           => VariableMap+           -> T.Text+           -> GQL.Value+           -> f (ArgumentValue' p a)++instance (ParseArg p r) => ParseArg p ('ListRef r) where+  parseArg vmap aname (GQL.VList (GQL.ListValueG xs))+    = ArgList <$> traverse (parseArg' vmap aname) xs+  parseArg _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance ParseArg p ('PrimitiveRef Bool) where+  parseArg _ _ (GQL.VBoolean b)+    = pure (ArgPrimitive b)+  parseArg _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance ParseArg p ('PrimitiveRef Int32) where+  parseArg _ _ (GQL.VInt b)+    = pure (ArgPrimitive b)+  parseArg _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance ParseArg p ('PrimitiveRef Integer) where+  parseArg _ _ (GQL.VInt b)+    = pure $ ArgPrimitive $ fromIntegral b+  parseArg _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance ParseArg p ('PrimitiveRef Double) where+  parseArg _ _ (GQL.VFloat b)+    = pure (ArgPrimitive b)+  parseArg _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance ParseArg p ('PrimitiveRef T.Text) where+  parseArg _ _ (GQL.VString (GQL.StringValue b))+    = pure $ ArgPrimitive b+  parseArg _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance ParseArg p ('PrimitiveRef String) where+  parseArg _ _ (GQL.VString (GQL.StringValue b))+    = pure $ ArgPrimitive $ T.unpack b+  parseArg _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance ParseArg p ('PrimitiveRef ()) where+  parseArg _ _ GQL.VNull = pure $ ArgPrimitive ()+  parseArg _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance (ObjectOrEnumParser sch (sch :/: sty))+         => ParseArg p ('SchemaRef sch sty) where+  parseArg vmap aname v+    = ArgSchema <$> parseObjectOrEnum' vmap aname v++parseObjectOrEnum' :: (ObjectOrEnumParser sch t, MonadError T.Text f)+          => VariableMap+          -> T.Text+          -> GQL.Value+          -> f (Term sch t)+parseObjectOrEnum' vmap aname (GQL.VVariable (GQL.unName . GQL.unVariable -> x))+  = case HM.lookup x vmap of+      Nothing -> throwError $ "variable '" <> x <> "' was not found"+      Just v  -> parseObjectOrEnum vmap aname v+parseObjectOrEnum' vmap aname v+  = parseObjectOrEnum vmap aname v++class ObjectOrEnumParser (sch :: Schema') (t :: TypeDef Symbol Symbol) where+  parseObjectOrEnum :: MonadError T.Text f+                    => VariableMap+                    -> T.Text+                    -> GQL.Value+                    -> f (Term sch t)++instance (ObjectParser sch args, KnownName name)+         => ObjectOrEnumParser sch ('DRecord name args) where+  parseObjectOrEnum vmap _ (GQL.VObject (GQL.ObjectValueG vs))+    = TRecord <$> objectParser vmap (T.pack $ nameVal (Proxy @name)) vs+  parseObjectOrEnum _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"+instance (EnumParser choices, KnownName name)+         => ObjectOrEnumParser sch ('DEnum name choices) where+  parseObjectOrEnum _ _ (GQL.VEnum (GQL.EnumValue nm))+    = TEnum <$> enumParser (T.pack $ nameVal (Proxy @name)) nm+  parseObjectOrEnum _ aname _+    = throwError $ "argument '" <> aname <> "' was not of right type"++class ObjectParser (sch :: Schema') (args :: [FieldDef Symbol Symbol]) where+  objectParser :: MonadError T.Text f+               => VariableMap+               -> T.Text+               -> [GQL.ObjectFieldG GQL.Value]+               -> f (NP (Field sch) args)++instance ObjectParser sch '[] where+  objectParser _ _ _ = pure Nil+instance+  (ObjectParser sch args, ValueParser sch v, KnownName nm) =>+  ObjectParser sch ('FieldDef nm v ': args)+  where+  objectParser vmap tyName args+    = let wanted = T.pack $ nameVal (Proxy @nm)+      in case find ((== wanted) . GQL.unName . GQL._ofName) args of+        Just (GQL.ObjectFieldG _ v)+          -> (:*) <$> (Field <$> valueParser' vmap wanted v) <*> objectParser vmap tyName args+        Nothing -> throwError $ "field '" <> wanted <> "' was not found on type '" <> tyName <> "'"++class EnumParser (choices :: [ChoiceDef Symbol]) where+  enumParser :: MonadError T.Text f+             => T.Text -> GQL.Name+             -> f (NS Proxy choices)++instance EnumParser '[] where+  enumParser tyName (GQL.unName -> wanted)+    = throwError $ "value '" <> wanted <> "' was not found on enum '" <> tyName <> "'"+instance (KnownName name, EnumParser choices)+         => EnumParser ('ChoiceDef name ': choices) where+  enumParser tyName w@(GQL.unName -> wanted)+    | wanted == mname = pure (Z Proxy)+    | otherwise = S <$> enumParser tyName w+    where+      mname = T.pack $ nameVal (Proxy @name)++valueParser' :: (ValueParser sch v, MonadError T.Text f)+             => VariableMap+             -> T.Text+             -> GQL.Value+             -> f (FieldValue sch v)+valueParser' vmap aname (GQL.VVariable (GQL.unName . GQL.unVariable -> x))+  = case HM.lookup x vmap of+      Nothing -> throwError $ "variable '" <> x <> "' was not found"+      Just v  -> valueParser vmap aname v+valueParser' vmap aname v = valueParser vmap aname v++class ValueParser (sch :: Schema') (v :: FieldType Symbol) where+  valueParser :: MonadError T.Text f+              => VariableMap+              -> T.Text+              -> GQL.Value+              -> f (FieldValue sch v)++instance ValueParser sch 'TNull where+  valueParser _ _ GQL.VNull = pure FNull+  valueParser _ fname _+    = throwError $ "field '" <> fname <> "' was not of right type"+instance ValueParser sch ('TPrimitive Bool) where+  valueParser _ _ (GQL.VBoolean b) = pure (FPrimitive b)+  valueParser _ fname _+    = throwError $ "field '" <> fname <> "' was not of right type"+instance ValueParser sch ('TPrimitive Int32) where+  valueParser _ _ (GQL.VInt b) = pure (FPrimitive b)+  valueParser _ fname _+    = throwError $ "field '" <> fname <> "' was not of right type"+instance ValueParser sch ('TPrimitive Integer) where+  valueParser _ _ (GQL.VInt b) = pure $ FPrimitive $ fromIntegral b+  valueParser _ fname _+    = throwError $ "field '" <> fname <> "' was not of right type"+instance ValueParser sch ('TPrimitive Double) where+  valueParser _ _ (GQL.VFloat b) = pure (FPrimitive b)+  valueParser _ fname _+    = throwError $ "field '" <> fname <> "' was not of right type"+instance ValueParser sch ('TPrimitive T.Text) where+  valueParser _ _ (GQL.VString (GQL.StringValue b))+    = pure $ FPrimitive b+  valueParser _ fname _+    = throwError $ "field '" <> fname <> "' was not of right type"+instance ValueParser sch ('TPrimitive String) where+  valueParser _ _ (GQL.VString (GQL.StringValue b))+    = pure $ FPrimitive $ T.unpack b+  valueParser _ fname _+    = throwError $ "field '" <> fname <> "' was not of right type"+instance (ValueParser sch r) => ValueParser sch ('TList r) where+  valueParser vmap fname (GQL.VList (GQL.ListValueG xs))+    = FList <$> traverse (valueParser' vmap fname) xs+  valueParser _ fname _+    = throwError $ "field '" <> fname <> "' was not of right type"+instance (ValueParser sch r) => ValueParser sch ('TOption r) where+  valueParser _ _ GQL.VNull+    = pure $ FOption Nothing+  valueParser vmap fname v+    = FOption . Just <$> valueParser' vmap fname v+instance (ObjectOrEnumParser sch (sch :/: sty), KnownName sty)+         => ValueParser sch ('TSchematic sty) where+  valueParser vmap _ v+    = FSchematic <$> parseObjectOrEnum' vmap (T.pack $ nameVal (Proxy @sty)) v++class ParseDifferentReturn (p :: Package') (r :: Return Symbol) where+  parseDiffReturn :: MonadError T.Text f+                  => VariableMap+                  -> FragmentMap+                  -> T.Text+                  -> GQL.SelectionSet+                  -> f (ReturnQuery p r)+instance ParseDifferentReturn p 'RetNothing where+  parseDiffReturn _ _ _ [] = pure RNothing+  parseDiffReturn _ _ fname _+    = throwError $ "field '" <> fname <> "' should not have a selection of subfields"+instance ParseReturn p r => ParseDifferentReturn p ('RetSingle r) where+  parseDiffReturn vmap frmap fname s+    = RSingle <$> parseReturn vmap frmap fname s+instance ParseReturn p r => ParseDifferentReturn p ('RetStream r) where+  parseDiffReturn vmap frmap fname s+    = RStream <$> parseReturn vmap frmap fname s++class ParseReturn (p :: Package') (r :: TypeRef Symbol) where+  parseReturn :: MonadError T.Text f+              => VariableMap+              -> FragmentMap+              -> T.Text+              -> GQL.SelectionSet+              -> f (ReturnQuery' p r)++instance ParseReturn p ('PrimitiveRef t) where+  parseReturn _ _ _ []+    = pure RetPrimitive+  parseReturn _ _ fname _+    = throwError $ "field '" <> fname <> "' should not have a selection of subfields"+instance (ParseSchema sch (sch :/: sty))+         => ParseReturn p ('SchemaRef sch sty) where+  parseReturn vmap frmap fname s+    = RetSchema <$> parseSchema vmap frmap fname s+instance ParseReturn p r+         => ParseReturn p ('ListRef r) where+  parseReturn vmap frmap fname s+    = RetList <$> parseReturn vmap frmap fname s+instance ParseReturn p r+         => ParseReturn p ('OptionalRef r) where+  parseReturn vmap frmap fname s+    = RetOptional <$> parseReturn vmap frmap fname s+instance ( p ~ 'Package pname ss,+           LookupService ss s ~ 'Service s sanns methods,+           KnownName s, ParseMethod p methods+         ) => ParseReturn p ('ObjectRef s) where+  parseReturn vmap frmap _ s+    = RetObject <$> parseQuery (Proxy @p) (Proxy @s) vmap frmap s++class ParseSchema (s :: Schema') (t :: TypeDef Symbol Symbol) where+  parseSchema :: MonadError T.Text f+              => VariableMap+              -> FragmentMap+              -> T.Text+              -> GQL.SelectionSet+              -> f (SchemaQuery s t)+instance ParseSchema sch ('DEnum name choices) where+  parseSchema _ _ _ []+    = pure QueryEnum+  parseSchema _ _ fname _+    = throwError $ "field '" <> fname <> "' should not have a selection of subfields"+instance (KnownSymbol name, ParseField sch fields)+         => ParseSchema sch ('DRecord name fields) where+  parseSchema vmap frmap _ s+    = QueryRecord <$> parseSchemaQuery (Proxy @sch) (Proxy @('DRecord name fields)) vmap frmap s++parseSchemaQuery ::+  forall (sch :: Schema') t (rname :: Symbol) fields f.+  ( MonadError T.Text f+  , t ~  'DRecord rname fields+  , KnownSymbol rname+  , ParseField sch fields ) =>+  Proxy sch ->+  Proxy t ->+  VariableMap -> FragmentMap -> GQL.SelectionSet ->+  f [OneFieldQuery sch fields]+parseSchemaQuery _ _ _ _ [] = pure []+parseSchemaQuery pp ps vmap frmap (GQL.SelectionField fld : ss)+  = (++) <$> (maybeToList <$> fieldToMethod fld)+         <*> parseSchemaQuery pp ps vmap frmap ss+  where+    fieldToMethod :: GQL.Field -> f (Maybe (OneFieldQuery sch fields))+    fieldToMethod (GQL.Field alias name args dirs sels)+      | any (shouldSkip vmap) dirs+      = pure Nothing+      | GQL.unName name == "__typename"+      = case (args, sels) of+          ([], []) -> pure $ Just $ TypeNameFieldQuery $ GQL.unName . GQL.unAlias <$> alias+          _        -> throwError "__typename does not admit arguments nor selection of subfields"+      | _:_ <- args+      = throwError "this field does not support arguments"+      | otherwise+      = Just . OneFieldQuery (GQL.unName . GQL.unAlias <$> alias)+         <$> selectField (T.pack $ nameVal (Proxy @rname)) vmap frmap name sels+parseSchemaQuery pp ps vmap frmap (GQL.SelectionFragmentSpread (GQL.FragmentSpread nm dirs) : ss)+  | Just fr <- HM.lookup (GQL.unName nm) frmap+  = if not (any (shouldSkip vmap) dirs) && not (any (shouldSkip vmap) $ GQL._fdDirectives fr)+       then (++) <$> parseSchemaQuery pp ps vmap frmap (GQL._fdSelectionSet fr)+                 <*> parseSchemaQuery pp ps vmap frmap ss+       else parseSchemaQuery pp ps vmap frmap ss+  | otherwise  -- the fragment definition was not found+  = throwError $ "fragment '" <> GQL.unName nm <> "' was not found"+parseSchemaQuery _ _ _ _ (_ : _)  -- Inline fragments are not yet supported+  = throwError "inline fragments are not (yet) supported"++class ParseField (sch :: Schema') (fs :: [FieldDef Symbol Symbol]) where+  selectField ::+    MonadError T.Text f =>+    T.Text ->+    VariableMap ->+    FragmentMap ->+    GQL.Name ->+    GQL.SelectionSet ->+    f (NS (ChosenFieldQuery sch) fs)++instance ParseField sch '[] where+  selectField tyName _ _ (GQL.unName -> wanted) _+    = throwError $ "field '" <> wanted <> "' was not found on type '" <> tyName <> "'"+instance+  (KnownSymbol fname, ParseField sch fs, ParseSchemaReturn sch r) =>+  ParseField sch ('FieldDef fname r ': fs)+  where+  selectField tyName vmap frmap w@(GQL.unName -> wanted) sels+    | wanted == mname+    = Z <$> (ChosenFieldQuery <$> parseSchemaReturn vmap frmap wanted sels)+    | otherwise+    = S <$> selectField tyName vmap frmap w sels+    where+      mname = T.pack $ nameVal (Proxy @fname)++class ParseSchemaReturn (sch :: Schema') (r :: FieldType Symbol) where+  parseSchemaReturn :: MonadError T.Text f+                    => VariableMap+                    -> FragmentMap+                    -> T.Text+                    -> GQL.SelectionSet+                    -> f (ReturnSchemaQuery sch r)++instance ParseSchemaReturn sch ('TPrimitive t) where+  parseSchemaReturn _ _ _ []+    = pure RetSchPrimitive+  parseSchemaReturn _ _ fname _+    = throwError $ "field '" <> fname <> "' should not have a selection of subfields"+instance ( ParseSchema sch (sch :/: sty) )+         => ParseSchemaReturn sch ('TSchematic sty) where+  parseSchemaReturn vmap frmap fname s+    = RetSchSchema <$> parseSchema vmap frmap fname s+instance ParseSchemaReturn sch r+         => ParseSchemaReturn sch ('TList r) where+  parseSchemaReturn vmap frmap fname s+    = RetSchList <$> parseSchemaReturn vmap frmap fname s+instance ParseSchemaReturn sch r+         => ParseSchemaReturn sch ('TOption r) where+  parseSchemaReturn vmap frmap fname s+    = RetSchOptional <$> parseSchemaReturn vmap frmap fname s
+ src/Mu/GraphQL/Query/Run.hs view
@@ -0,0 +1,771 @@+{-# language ConstraintKinds       #-}+{-# language DataKinds             #-}+{-# language FlexibleContexts      #-}+{-# language FlexibleInstances     #-}+{-# language GADTs                 #-}+{-# language MultiParamTypeClasses #-}+{-# language OverloadedLists       #-}+{-# language OverloadedStrings     #-}+{-# language PolyKinds             #-}+{-# language RankNTypes            #-}+{-# language ScopedTypeVariables   #-}+{-# language TupleSections         #-}+{-# language TypeApplications      #-}+{-# language TypeOperators         #-}+{-# language UndecidableInstances  #-}+{-# language ViewPatterns          #-}+{-# OPTIONS_GHC -fprint-explicit-foralls #-}+module Mu.GraphQL.Query.Run (+  GraphQLApp+, runPipeline+, runSubscriptionPipeline+, runDocument+, runQuery+, runSubscription+-- * Typeclass to be able to run query handlers+, RunQueryFindHandler+) where++import           Control.Concurrent.STM.TMQueue+import           Control.Monad.Except           (MonadError, runExceptT)+import           Control.Monad.Writer+import qualified Data.Aeson                     as Aeson+import qualified Data.Aeson.Types               as Aeson+import           Data.Conduit+import           Data.Conduit.Combinators       (sinkList, yieldMany)+import           Data.Conduit.TQueue+import qualified Data.HashMap.Strict            as HM+import           Data.Maybe+import qualified Data.Text                      as T+import           GHC.TypeLits+import qualified Language.GraphQL.Draft.Syntax  as GQL++import           Data.Coerce                    (coerce)+import           Mu.GraphQL.Query.Definition+import qualified Mu.GraphQL.Query.Introspection as Intro+import           Mu.GraphQL.Query.Parse+import           Mu.Rpc+import           Mu.Schema+import           Mu.Server++data GraphQLError+  = GraphQLError ServerError [T.Text]++type GraphQLApp p qr mut sub m chn hs+  = (ParseTypedDoc p qr mut sub, RunDocument p qr mut sub m chn hs)++runPipeline+  :: forall qr mut sub p m chn hs. GraphQLApp p qr mut sub m chn hs+  => (forall a. m a -> ServerErrorIO a)+  -> ServerT chn p m hs+  -> Proxy qr -> Proxy mut -> Proxy sub+  -> Maybe T.Text -> VariableMapC -> GQL.ExecutableDocument+  -> IO Aeson.Value+runPipeline f svr _ _ _ opName vmap doc+  = case parseDoc @qr @mut @sub opName vmap doc of+      Left e -> pure $ singleErrValue e+      Right (d :: Document p qr mut sub) -> do+        (data_, errors) <- runWriterT (runDocument f svr d)+        case errors of+          [] -> pure $ Aeson.object [ ("data", data_) ]+          _  -> pure $ Aeson.object [ ("data", data_), ("errors", Aeson.listValue errValue errors) ]++runSubscriptionPipeline+  :: forall qr mut sub p m chn hs. GraphQLApp p qr mut sub m chn hs+  => (forall a. m a -> ServerErrorIO a)+  -> ServerT chn p m hs+  -> Proxy qr -> Proxy mut -> Proxy sub+  -> Maybe T.Text -> VariableMapC -> GQL.ExecutableDocument+  -> ConduitT Aeson.Value Void IO ()+  -> IO ()+runSubscriptionPipeline f svr _ _ _ opName vmap doc sink+  = case parseDoc @qr @mut @sub opName vmap doc of+      Left e+        -> yieldSingleError e sink+      Right (d :: Document p qr mut sub)+        -> runDocumentSubscription f svr d sink++singleErrValue :: T.Text -> Aeson.Value+singleErrValue e+  = Aeson.object [ ("errors", Aeson.Array [+                       Aeson.object [ ("message", Aeson.String e) ] ])]++errValue :: GraphQLError -> Aeson.Value+errValue (GraphQLError (ServerError _ msg) path)+  = Aeson.object [+      ("message", Aeson.String $ T.pack msg)+    , ("path", Aeson.toJSON path)+    ]++yieldSingleError :: Monad m+                 => T.Text -> ConduitM Aeson.Value Void m () -> m ()+yieldSingleError e sink =+  runConduit $ yieldMany ([singleErrValue e] :: [Aeson.Value]) .| sink++yieldError :: Monad m+           => ServerError -> [T.Text]+           -> ConduitM Aeson.Value Void m () -> m ()+yieldError e path sink = do+  let val = Aeson.object [ ("errors", Aeson.listValue errValue [GraphQLError e path]) ]+  runConduit $ yieldMany ([val] :: [Aeson.Value]) .| sink++class RunDocument (p :: Package')+                  (qr :: Maybe Symbol)+                  (mut :: Maybe Symbol)+                  (sub :: Maybe Symbol)+                  m chn hs where+  runDocument ::+       (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m hs+    -> Document p qr mut sub+    -> WriterT [GraphQLError] IO Aeson.Value+  runDocumentSubscription ::+       (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m hs+    -> Document p qr mut sub+    -> ConduitT Aeson.Value Void IO ()+    -> IO ()++instance+  ( p ~ 'Package pname ss+  , KnownSymbol qr+  , RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs+  , MappingRight chn qr ~ ()+  , KnownSymbol mut+  , RunQueryFindHandler m p hs chn ss (LookupService ss mut) hs+  , MappingRight chn mut ~ ()+  , KnownSymbol sub+  , RunQueryFindHandler m p hs chn ss (LookupService ss sub) hs+  , MappingRight chn sub ~ ()+  , Intro.Introspect p ('Just qr) ('Just mut) ('Just sub)+  ) => RunDocument p ('Just qr) ('Just mut) ('Just sub) m chn hs where+  runDocument f svr d+    = let i = Intro.introspect (Proxy @p) (Proxy @('Just qr)) (Proxy @('Just mut)) (Proxy @('Just sub))+      in case d of+           QueryDoc q+             -> runQuery f i svr [] () q+           MutationDoc q+             -> runQuery f i svr [] () q+           SubscriptionDoc _+             -> pure $ singleErrValue "cannot execute subscriptions in this wire"+  runDocumentSubscription f svr (SubscriptionDoc d)+    = runSubscription f svr [] () d+  runDocumentSubscription f svr d = yieldDocument f svr d++instance+  ( p ~ 'Package pname ss+  , KnownSymbol qr+  , RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs+  , MappingRight chn qr ~ ()+  , KnownSymbol mut+  , RunQueryFindHandler m p hs chn ss (LookupService ss mut) hs+  , MappingRight chn mut ~ ()+  , Intro.Introspect p ('Just qr) ('Just mut) 'Nothing+  ) => RunDocument p ('Just qr) ('Just mut) 'Nothing m chn hs where+  runDocument f svr d+    = let i = Intro.introspect (Proxy @p) (Proxy @('Just qr)) (Proxy @('Just mut)) (Proxy @'Nothing)+      in case d of+           QueryDoc q+             -> runQuery f i svr [] () q+           MutationDoc q+             -> runQuery f i svr [] () q+  runDocumentSubscription = yieldDocument++instance+  ( p ~ 'Package pname ss+  , KnownSymbol qr+  , RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs+  , MappingRight chn qr ~ ()+  , KnownSymbol sub+  , RunQueryFindHandler m p hs chn ss (LookupService ss sub) hs+  , MappingRight chn sub ~ ()+  , Intro.Introspect p ('Just qr) 'Nothing ('Just sub)+  ) => RunDocument p ('Just qr) 'Nothing ('Just sub) m chn hs where+  runDocument f svr d+    = let i = Intro.introspect (Proxy @p) (Proxy @('Just qr)) (Proxy @'Nothing) (Proxy @('Just sub))+      in case d of+           QueryDoc q+             -> runQuery f i svr [] () q+           SubscriptionDoc _+             -> pure $ singleErrValue "cannot execute subscriptions in this wire"+  runDocumentSubscription f svr (SubscriptionDoc d)+    = runSubscription f svr [] () d+  runDocumentSubscription f svr d = yieldDocument f svr d++instance+  ( p ~ 'Package pname ss+  , KnownSymbol qr+  , RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs+  , MappingRight chn qr ~ ()+  , Intro.Introspect p ('Just qr) 'Nothing 'Nothing+  ) => RunDocument p ('Just qr) 'Nothing 'Nothing m chn hs where+  runDocument f svr d+    = let i = Intro.introspect (Proxy @p) (Proxy @('Just qr)) (Proxy @'Nothing) (Proxy @'Nothing)+      in case d of+           QueryDoc q+             -> runQuery f i svr [] () q+  runDocumentSubscription = yieldDocument++instance+  ( TypeError ('Text "you need to have a query in your schema")+  ) => RunDocument p 'Nothing mut sub m chn hs where+  runDocument _ = error "this should never be called"+  runDocumentSubscription _ = error "this should never be called"++yieldDocument ::+     forall p qr mut sub m chn hs.+     RunDocument p qr mut sub m chn hs+  => (forall a. m a -> ServerErrorIO a)+  -> ServerT chn p m hs+  -> Document p qr mut sub+  -> ConduitT Aeson.Value Void IO ()+  -> IO ()+yieldDocument f svr doc sink = do+  (data_, errors) <- runWriterT (runDocument @p @qr @mut @sub @m @chn @hs f svr doc)+  let (val :: Aeson.Value)+        = case errors of+            [] -> Aeson.object [ ("data", data_) ]+            _  -> Aeson.object [ ("data", data_), ("errors", Aeson.listValue errValue errors) ]+  runConduit $ yieldMany ([val] :: [Aeson.Value]) .| sink++runQuery+  :: forall m p s pname ss hs sname sanns ms chn inh.+     ( RunQueryFindHandler m p hs chn ss s hs+     , p ~ 'Package pname ss+     , s ~ 'Service sname sanns ms+     , inh ~ MappingRight chn sname )+  => (forall a. m a -> ServerErrorIO a)+  -> Intro.Schema -> ServerT chn p m hs+  -> [T.Text]+  -> inh+  -> ServiceQuery p s+  -> WriterT [GraphQLError] IO Aeson.Value+runQuery f sch whole@(Services ss) path = runQueryFindHandler f sch whole path ss++runSubscription+  :: forall m p s pname ss hs sname sanns ms chn inh.+     ( RunQueryFindHandler m p hs chn ss s hs+     , p ~ 'Package pname ss+     , s ~ 'Service sname sanns ms+     , inh ~ MappingRight chn sname )+  => (forall a. m a -> ServerErrorIO a)+  -> ServerT chn p m hs+  -> [T.Text]+  -> inh+  -> OneMethodQuery p s+  -> ConduitT Aeson.Value Void IO ()+  -> IO ()+runSubscription f whole@(Services ss) path+  = runSubscriptionFindHandler f whole path ss++class RunQueryFindHandler m p whole chn ss s hs where+  runQueryFindHandler+    :: ( p ~  'Package pname wholess+       , s ~ 'Service sname sanns ms+       , inh ~ MappingRight chn sname )+    => (forall a. m a -> ServerErrorIO a)+    -> Intro.Schema -> ServerT chn p m whole+    -> [T.Text]+    -> ServicesT chn ss m hs+    -> inh+    -> ServiceQuery p s+    -> WriterT [GraphQLError] IO Aeson.Value+  runSubscriptionFindHandler+    :: ( p ~  'Package pname wholess+       , s ~ 'Service sname sanns ms+       , inh ~ MappingRight chn sname )+    => (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m whole+    -> [T.Text]+    -> ServicesT chn ss m hs+    -> inh+    -> OneMethodQuery p s+    -> ConduitT Aeson.Value Void IO ()+    -> IO ()++instance TypeError ('Text "Could not find handler for " ':<>: 'ShowType s)+         => RunQueryFindHandler m p whole chn '[] s '[] where+  runQueryFindHandler _ = error "this should never be called"+  runSubscriptionFindHandler _ = error "this should never be called"+instance {-# OVERLAPPABLE #-}+         RunQueryFindHandler m p whole chn ss s hs+         => RunQueryFindHandler m p whole chn (other ': ss) s (h ': hs) where+  runQueryFindHandler f sch whole path (_ :<&>: that)+    = runQueryFindHandler f sch whole path that+  runSubscriptionFindHandler f whole path (_ :<&>: that)+    = runSubscriptionFindHandler f whole path that+instance {-# OVERLAPS #-} (s ~ 'Service sname sanns ms, KnownName sname, RunMethod m p whole chn sname ms h)+         => RunQueryFindHandler m p whole chn (s ': ss) s (h ': hs) where+  runQueryFindHandler f sch whole path (this :<&>: _) inh queries+    = Aeson.object . catMaybes <$> mapM runOneQuery queries+    where+      -- if we include the signature we have to write+      -- an explicit type signature for 'runQueryFindHandler'+      runOneQuery (OneMethodQuery nm args)+        = runMethod f whole (Proxy @sname) path nm inh this args+      -- handle __typename+      runOneQuery (TypeNameQuery nm)+        = let realName = fromMaybe "__typename" nm+          in pure $ Just (realName, Aeson.String $ T.pack $ nameVal (Proxy @sname))+      -- handle __schema+      runOneQuery (SchemaQuery nm ss)+        = do let realName = fromMaybe "__schema" nm+             Just . (realName, ) <$> runIntroSchema path sch ss+      -- handle __type+      runOneQuery (TypeQuery nm ty ss)+        = do let realName = fromMaybe "__schema" nm+             res <- runIntroType path sch (Intro.TypeRef ty) ss+             case res of+               Just val -> pure $ Just (realName, val)+               Nothing  -> do tell [GraphQLError+                                     (ServerError Invalid+                                       $ "cannot find type '" <> T.unpack ty <> "'")+                                    path]+                              pure $ Just (realName, Aeson.Null)+  -- subscriptions should only have one element+  runSubscriptionFindHandler f whole path (this :<&>: _) inh (OneMethodQuery nm args) sink+    = runMethodSubscription f whole (Proxy @sname) path nm inh this args sink+  runSubscriptionFindHandler _ _ _ _ _ (TypeNameQuery nm) sink+    = let realName = fromMaybe "__typename" nm+          o = Aeson.object [(realName, Aeson.String $ T.pack $ nameVal (Proxy @sname))]+      in runConduit $ yieldMany ([o] :: [Aeson.Value]) .| sink+  runSubscriptionFindHandler _ _ _ _ _ _ sink+    = runConduit $ yieldMany+                   ([singleErrValue "__schema and __type are not supported in subscriptions"]+                      :: [Aeson.Value])+                   .| sink++class RunMethod m p whole chn sname ms hs where+  runMethod+    :: ( p ~ 'Package pname wholess+       , inh ~ MappingRight chn sname )+    => (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m whole+    -> Proxy sname -> [T.Text] -> Maybe T.Text -> inh+    -> HandlersT chn inh ms m hs+    -> NS (ChosenMethodQuery p) ms+    -> WriterT [GraphQLError] IO (Maybe (T.Text, Aeson.Value))+  runMethodSubscription+    :: ( p ~ 'Package pname wholess+       , inh ~ MappingRight chn sname )+    => (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m whole+    -> Proxy sname -> [T.Text] -> Maybe T.Text -> inh+    -> HandlersT chn inh ms m hs+    -> NS (ChosenMethodQuery p) ms+    -> ConduitT Aeson.Value Void IO ()+    -> IO ()++instance RunMethod m p whole chn s '[] '[] where+  runMethod _ = error "this should never be called"+  runMethodSubscription _ = error "this should never be called"+instance (RunMethod m p whole chn s ms hs, KnownName mname, RunHandler m p whole chn args r h)+         => RunMethod m p whole chn s ('Method mname anns args r ': ms) (h ': hs) where+  -- handle normal methods+  runMethod f whole _ path nm inh (h :<||>: _) (Z (ChosenMethodQuery args ret))+    = ((realName ,) <$>) <$> runHandler f whole (path ++ [realName]) (h inh) args ret+    where realName = fromMaybe (T.pack $ nameVal (Proxy @mname)) nm+  runMethod f whole p path nm inh (_ :<||>: r) (S cont)+    = runMethod f whole p path nm inh r cont+  -- handle subscriptions+  runMethodSubscription f whole _ path nm inh (h :<||>: _) (Z (ChosenMethodQuery args ret)) sink+    = runHandlerSubscription f whole (path ++ [realName]) (h inh) args ret sink+    where realName = fromMaybe (T.pack $ nameVal (Proxy @mname)) nm+  runMethodSubscription f whole p path nm inh (_ :<||>: r) (S cont) sink+    = runMethodSubscription f whole p path nm inh r cont sink++class Handles chn args r m h+      => RunHandler m p whole chn args r h where+  runHandler+    :: (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m whole+    -> [T.Text]+    -> h+    -> NP (ArgumentValue p) args+    -> ReturnQuery p r+    -> WriterT [GraphQLError] IO (Maybe Aeson.Value)+  runHandlerSubscription+    :: (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m whole+    -> [T.Text]+    -> h+    -> NP (ArgumentValue p) args+    -> ReturnQuery p r+    -> ConduitT Aeson.Value Void IO ()+    -> IO ()++instance (ArgumentConversion chn ref t, RunHandler m p whole chn rest r h)+         => RunHandler m p whole chn ('ArgSingle aname aanns ref ': rest) r (t -> h) where+  runHandler f whole path h (ArgumentValue one :* rest)+    = runHandler f whole path (h (convertArg (Proxy @chn) one)) rest+  runHandlerSubscription f whole path h (ArgumentValue one :* rest)+    = runHandlerSubscription f whole path (h (convertArg (Proxy @chn) one)) rest+instance ( MonadError ServerError m+         , FromRef chn ref t+         , ArgumentConversion chn ('ListRef ref) [t]+         , RunHandler m p whole chn rest r h )+         => RunHandler m p whole chn ('ArgStream aname aanns ref ': rest) r (ConduitT () t m () -> h) where+  runHandler f whole path h (ArgumentStream lst :* rest)+    = let converted :: [t] = convertArg (Proxy @chn) lst+      in runHandler f whole path (h (yieldMany converted)) rest+  runHandlerSubscription f whole path h (ArgumentStream lst :* rest) sink+    = let converted :: [t] = convertArg (Proxy @chn) lst+      in runHandlerSubscription f whole path (h (yieldMany converted)) rest sink+instance (MonadError ServerError m)+         => RunHandler m p whole chn '[] 'RetNothing (m ()) where+  runHandler f _ path h Nil _ = do+    res <- liftIO $ runExceptT (f h)+    case res of+      Right _ -> pure $ Just Aeson.Null+      Left e  -> tell [GraphQLError e path] >> pure Nothing+  runHandlerSubscription f _ path h Nil _ sink = do+    res <- liftIO $ runExceptT (f h)+    case res of+      Right _ -> runConduit $ yieldMany ([] :: [Aeson.Value]) .| sink+      Left e  -> yieldError e path sink+instance (MonadError ServerError m, ResultConversion m p whole chn r l)+         => RunHandler m p whole chn '[] ('RetSingle r) (m l) where+  runHandler f whole path h Nil (RSingle q) = do+    res <- liftIO $ runExceptT (f h)+    case res of+      Right v -> convertResult f whole path q v+      Left e  -> tell [GraphQLError e path] >> pure Nothing+  runHandlerSubscription f whole path h Nil (RSingle q) sink = do+    res <- liftIO $ runExceptT (f h)+    val <- case res of+      Right v -> do+        (data_, errors) <- runWriterT (convertResult f whole path q v)+        case errors of+          [] -> pure $ Aeson.object [ ("data", fromMaybe Aeson.Null data_) ]+          _  -> pure $ Aeson.object [ ("data", fromMaybe Aeson.Null data_)+                                    , ("errors", Aeson.listValue errValue errors) ]+      Left e -> pure $ Aeson.object [ ("errors", Aeson.listValue errValue [GraphQLError e path]) ]+    runConduit $ yieldMany ([val] :: [Aeson.Value]) .| sink+instance (MonadIO m, MonadError ServerError m, ResultConversion m p whole chn r l)+         => RunHandler m p whole chn '[] ('RetStream r) (ConduitT l Void m () -> m ()) where+  runHandler f whole path h Nil (RStream q) = do+    queue <- liftIO newTMQueueIO+    res <- liftIO $ runExceptT $ f $ h (sinkTMQueue queue)+    case res of+      Right _ -> do+        info <- runConduit $ sourceTMQueue queue .| sinkList+        Just . Aeson.toJSON . catMaybes <$> traverse (convertResult f whole path q) info+      Left e  -> tell [GraphQLError e []] >> pure Nothing+  runHandlerSubscription f whole path h Nil (RStream q) sink = do+    res <- liftIO $ runExceptT $ f $ h+      (transPipe liftIO (mapInputM convert (error "this should not be called") sink))+    case res of+      Right _ -> return ()+      Left e  -> yieldError e path sink+    where+      convert :: l -> IO Aeson.Value+      convert v = do+        (data_, errors) <- runWriterT (convertResult f whole path q v)+        case errors of+          [] -> pure $ Aeson.object [ ("data", fromMaybe Aeson.Null data_) ]+          _  -> pure $ Aeson.object [ ("data", fromMaybe Aeson.Null data_)+                                    , ("errors", Aeson.listValue errValue errors) ]++class FromRef chn ref t+      => ArgumentConversion chn ref t where+  convertArg :: Proxy chn -> ArgumentValue' p ref -> t+instance ArgumentConversion chn ('PrimitiveRef s) s where+  convertArg _ (ArgPrimitive x) = x+instance FromSchema sch sty t+         => ArgumentConversion chn ('SchemaRef sch sty) t where+  convertArg _ (ArgSchema x) = fromSchema x+instance ArgumentConversion chn ref t+         => ArgumentConversion chn ('ListRef ref) [t] where+  convertArg p (ArgList x) = convertArg p <$> x+instance ArgumentConversion chn ref t+         => ArgumentConversion chn ('OptionalRef ref) (Maybe t) where+  convertArg p (ArgOptional x) = convertArg p <$> x++class ToRef chn r l => ResultConversion m p whole chn r l where+  convertResult :: (forall a. m a -> ServerErrorIO a)+                -> ServerT chn p m whole+                -> [T.Text]+                -> ReturnQuery' p r+                -> l -> WriterT [GraphQLError] IO (Maybe Aeson.Value)++instance Aeson.ToJSON t => ResultConversion m p whole chn ('PrimitiveRef t) t where+  convertResult _ _ _ RetPrimitive = pure . Just . Aeson.toJSON+instance ( ToSchema sch l r+         , RunSchemaQuery sch (sch :/: l) )+         => ResultConversion m p whole chn ('SchemaRef sch l) r where+  convertResult _ _ _ (RetSchema r) t+    = pure $ Just $ runSchemaQuery (toSchema' @_ @_ @sch @r t) r+instance ( MappingRight chn ref ~ t+         , MappingRight chn sname ~ t+         , LookupService ss ref ~ 'Service sname sanns ms+         , RunQueryFindHandler m ('Package pname ss) whole chn ss ('Service sname sanns ms) whole)+         => ResultConversion m ('Package pname ss) whole chn ('ObjectRef ref) t where+  convertResult f whole path (RetObject q) h+    = Just <$> runQuery @m @('Package pname ss) @(LookupService ss ref) f+                        (error "cannot inspect schema inside a field")+                        whole path h q+instance ResultConversion m p whole chn r s+        => ResultConversion m p whole chn ('OptionalRef r) (Maybe s) where+  convertResult _ _ _ _ Nothing+    = pure Nothing+  convertResult f whole path (RetOptional q) (Just x)+    = convertResult f whole path q x+instance ResultConversion m p whole chn r s+        => ResultConversion m p whole chn ('ListRef r) [s] where+  convertResult f whole path (RetList q) xs+    = Just . Aeson.toJSON . catMaybes <$> mapM (convertResult f whole path q) xs++class RunSchemaQuery sch r where+  runSchemaQuery+    :: Term sch r+    -> SchemaQuery sch r+    -> Aeson.Value+instance ( Aeson.ToJSON (Term sch ('DEnum name choices)) )+         => RunSchemaQuery sch ('DEnum name choices) where+  runSchemaQuery t _ = Aeson.toJSON t+instance ( KnownName rname, RunSchemaField sch fields )+         => RunSchemaQuery sch ('DRecord rname fields) where+  runSchemaQuery (TRecord args) (QueryRecord rs)+    = Aeson.object $ mapMaybe runOneQuery rs+    where+      runOneQuery (OneFieldQuery nm choice)+        = let (val, fname) = runSchemaField args choice+              realName = fromMaybe fname nm+          in (realName,) <$> val+      runOneQuery (TypeNameFieldQuery nm)+        = let realName = fromMaybe "__typename" nm+          -- add the 'R' because it's on return position+          in pure (realName, Aeson.String $ T.pack $ nameVal (Proxy @rname) ++ "R")+++class RunSchemaField sch args where+  runSchemaField+    :: NP (Field sch) args+    -> NS (ChosenFieldQuery sch) args+    -> (Maybe Aeson.Value, T.Text)++instance RunSchemaField sch '[] where+  runSchemaField = error "this should never be called"+instance (KnownName fname, RunSchemaType sch t, RunSchemaField sch fs)+         => RunSchemaField sch ('FieldDef fname t ': fs) where+  runSchemaField (Field x :* _) (Z (ChosenFieldQuery c))+    = (runSchemaType x c, T.pack $ nameVal (Proxy @fname))+  runSchemaField (_ :* xs) (S rest)+    = runSchemaField xs rest++class RunSchemaType sch t where+  runSchemaType+    :: FieldValue sch t+    -> ReturnSchemaQuery sch t+    -> Maybe Aeson.Value+instance ( Aeson.ToJSON t )+         => RunSchemaType sch ('TPrimitive t) where+  runSchemaType (FPrimitive x) _+    = Just $ Aeson.toJSON x+instance RunSchemaType sch r+         => RunSchemaType sch ('TList r) where+  runSchemaType (FList xs) (RetSchList r)+    = Just . Aeson.toJSON $ mapMaybe (`runSchemaType` r) xs+instance RunSchemaType sch r+         => RunSchemaType sch ('TOption r) where+  runSchemaType (FOption xs) (RetSchOptional r)+    = xs >>= flip runSchemaType r+instance RunSchemaQuery sch (sch :/: l)+         => RunSchemaType sch ('TSchematic l) where+  runSchemaType (FSchematic t) (RetSchSchema r)+    = Just $ runSchemaQuery t r+++runIntroSchema+  :: [T.Text] -> Intro.Schema -> GQL.SelectionSet+  -> WriterT [GraphQLError] IO Aeson.Value+runIntroSchema path s@(Intro.Schema qr mut sub ts) ss+  = do things <- catMaybes <$> traverse runOne ss+       pure $ Aeson.object things+  where+    runOne (GQL.SelectionField (GQL.Field (coerce -> alias) (coerce -> nm) _ _ innerss))+      = let realName :: T.Text = fromMaybe nm alias+            path' = path ++ [realName]+        in fmap (realName,) <$> case nm of+             "description"+               -> pure $ Just Aeson.Null+             "directives"+               -> pure $ Just $ Aeson.Array []+             "queryType"+               -> case qr >>= flip HM.lookup ts of+                    Nothing -> pure Nothing+                    Just ty -> runIntroType path' s ty innerss+             "mutationType"+               -> case mut >>= flip HM.lookup ts of+                    Nothing -> pure Nothing+                    Just ty -> runIntroType path' s ty innerss+             "subscriptionType"+               -> case sub >>= flip HM.lookup ts of+                    Nothing -> pure Nothing+                    Just ty -> runIntroType path' s ty innerss+             "types"+               -> do tys <- catMaybes <$> mapM (\t -> runIntroType path' s t innerss) (HM.elems ts)+                     pure $ Just $ Aeson.toJSON tys+             _ -> do tell [GraphQLError+                             (ServerError Invalid+                               $ "field '" <> T.unpack nm <> "' was not found on type '__Schema'")+                             path]+                     pure Nothing+    -- we do not support spreads here+    runOne _ = pure Nothing++runIntroType+  :: [T.Text] -> Intro.Schema -> Intro.Type -> GQL.SelectionSet+  -> WriterT [GraphQLError] IO (Maybe Aeson.Value)+runIntroType path s@(Intro.Schema _ _ _ ts) (Intro.TypeRef t) ss+  = case HM.lookup t ts of+      Nothing -> pure Nothing+      Just ty -> runIntroType path s ty ss+runIntroType path s (Intro.Type k tnm fs vals ofT) ss+  = do things <- catMaybes <$> traverse runOne ss+       pure $ Just $ Aeson.object things+  where+    runOne (GQL.SelectionField (GQL.Field (coerce -> alias) (coerce -> nm) _ _ innerss))+      = let realName :: T.Text = fromMaybe nm alias+            path' = path ++ [realName]+        in fmap (realName,) <$> case (nm, innerss) of+             ("kind", [])+               -> pure $ Just $ Aeson.String $ T.pack (show k)+             ("name", [])+               -> pure $ Just $ maybe Aeson.Null Aeson.String tnm+             ("description", [])+               -> pure $ Just Aeson.Null++             ("fields", _)+               -> case k of+                    Intro.OBJECT+                      -> do things <- mapM (\f -> runIntroFields path' f innerss) fs+                            pure $ Just $ Aeson.toJSON things+                    _ -> pure $ Just Aeson.Null+             ("inputFields", _)+               -> case k of+                    Intro.INPUT_OBJECT+                      -> do things <- mapM (\f -> runIntroFields path' f innerss) fs+                            pure $ Just $ Aeson.toJSON things+                    _ -> pure $ Just Aeson.Null+             ("enumValues", _)+               -> do things <- mapM (\e -> runIntroEnums path' e innerss) vals+                     pure $ Just $ Aeson.toJSON things++             ("ofType", _)+               -> case ofT of+                    Nothing -> pure $ Just Aeson.Null+                    Just o  -> runIntroType path' s o innerss++             -- unions and interfaces are not supported+             ("interfaces", _)+               -> pure $ Just $ Aeson.Array []+             ("possibleTypes", _)+               -> pure $ Just $ Aeson.Array []++             _ -> do tell [GraphQLError+                             (ServerError Invalid+                               $ "field '" <> T.unpack nm <> "' was not found on type '__Type'")+                             path]+                     pure Nothing+    -- we do not support spreads here+    runOne _ = pure Nothing++    runIntroFields+      :: [T.Text] -> Intro.Field -> GQL.SelectionSet+      -> WriterT [GraphQLError] IO (Maybe Aeson.Value)+    runIntroFields fpath fld fss+      = do things <- catMaybes <$> traverse (runIntroField fpath fld) fss+           pure $ Just $ Aeson.object things++    runIntroField fpath (Intro.Field fnm fargs fty)+                  (GQL.SelectionField (GQL.Field (coerce -> alias) (coerce -> nm) _ _ innerss))+      = let realName :: T.Text = fromMaybe nm alias+            fpath' = fpath ++ [realName]+        in fmap (realName,) <$> case (nm, innerss) of+          ("name", [])+            -> pure $ Just $ Aeson.String fnm+          ("description", [])+            -> pure $ Just Aeson.Null+          ("isDeprecated", [])+            -> pure $ Just $ Aeson.Bool False+          ("deprecationReason", [])+            -> pure $ Just Aeson.Null++          -- this is used by __InputValue,+          -- which is required when the field+          -- is inside an INPUT_OBJECT+          ("defaultValue", [])+            -> pure $ Just Aeson.Null++          ("type", _)+            -> runIntroType fpath' s fty innerss+          ("args", _)+               -> do things <- mapM (\i -> runIntroInputs fpath' i innerss) fargs+                     pure $ Just $ Aeson.toJSON things++          _ -> do tell [GraphQLError+                             (ServerError Invalid+                               $ "field '" <> T.unpack nm <> "' was not found on type '__Field'")+                             fpath]+                  pure Nothing+    -- we do not support spreads here+    runIntroField _ _ _ = pure Nothing++    runIntroEnums+      :: [T.Text] -> Intro.EnumValue -> GQL.SelectionSet+      -> WriterT [GraphQLError] IO (Maybe Aeson.Value)+    runIntroEnums epath enm ess+      = do things <- catMaybes <$> traverse (runIntroEnum epath enm) ess+           pure $ Just $ Aeson.object things++    runIntroEnum epath (Intro.EnumValue enm)+                 (GQL.SelectionField (GQL.Field (coerce -> alias) (coerce -> nm) _ _ innerss))+      = let realName :: T.Text = fromMaybe nm alias+        in fmap (realName,) <$> case (nm, innerss) of+          ("name", [])+            -> pure $ Just $ Aeson.String enm+          ("description", [])+            -> pure $ Just Aeson.Null+          ("isDeprecated", [])+            -> pure $ Just $ Aeson.Bool False+          ("deprecationReason", [])+            -> pure $ Just Aeson.Null++          _ -> do tell [GraphQLError+                             (ServerError Invalid+                               $ "field '" <> T.unpack nm <> "' was not found on type '__EnumValue'")+                             epath]+                  pure Nothing+    -- we do not support spreads here+    runIntroEnum _ _ _ = pure Nothing++    runIntroInputs+      :: [T.Text] -> Intro.Input -> GQL.SelectionSet+      -> WriterT [GraphQLError] IO (Maybe Aeson.Value)+    runIntroInputs ipath inm iss+      = do things <- catMaybes <$> traverse (runIntroInput ipath inm) iss+           pure $ Just $ Aeson.object things++    runIntroInput ipath (Intro.Input inm def ty)+                 (GQL.SelectionField (GQL.Field (coerce -> alias) (coerce -> nm) _ _ innerss))+      = let realName :: T.Text = fromMaybe nm alias+            ipath' = ipath ++ [realName]+        in fmap (realName,) <$> case (nm, innerss) of+          ("name", [])+            -> pure $ Just $ Aeson.String inm+          ("description", [])+            -> pure $ Just Aeson.Null+          ("defaultValue", [])+            -> pure $ Just $ maybe Aeson.Null Aeson.String def++          ("type", _)+            -> runIntroType ipath' s ty innerss++          _ -> do tell [GraphQLError+                             (ServerError Invalid+                               $ "field '" <> T.unpack nm <> "' was not found on type '__Field'")+                             ipath]+                  pure Nothing+    -- we do not support spreads here+    runIntroInput _ _ _ = pure Nothing
+ src/Mu/GraphQL/Server.hs view
@@ -0,0 +1,218 @@+{-# language DataKinds           #-}+{-# language FlexibleContexts    #-}+{-# language GADTs               #-}+{-# language OverloadedLists     #-}+{-# language OverloadedStrings   #-}+{-# language RankNTypes          #-}+{-# language ScopedTypeVariables #-}+{-# language TypeApplications    #-}+{-|+Description : Execute a Mu 'Server' using GraphQL++This module allows you to server a Mu 'Server'+as a WAI 'Application' using GraphQL.++The simples way is to use 'runGraphQLAppQuery'+(if you only provide GraphQL queries) or+'runGraphQLApp' (if you also have mutations+or subscriptions). All other variants provide+more control over the settings.+-}+module Mu.GraphQL.Server (+    GraphQLApp+  -- * Run an GraphQL resolver directly+  , runGraphQLApp+  , runGraphQLAppSettings+  , runGraphQLAppQuery+  , runGraphQLAppTrans+  -- * Build a WAI 'Application'+  , graphQLApp+  , graphQLAppQuery+  , graphQLAppTrans+  , graphQLAppTransQuery+) where++import           Control.Applicative              ((<|>))+import           Control.Monad                    (join)+import qualified Data.Aeson                       as A+import           Data.Aeson.Text                  (encodeToLazyText)+import           Data.ByteString.Lazy             (fromStrict, toStrict)+import qualified Data.HashMap.Strict              as HM+import           Data.Proxy+import qualified Data.Text                        as T+import           Data.Text.Encoding               (decodeUtf8)+import qualified Data.Text.Lazy.Encoding          as T+import           Language.GraphQL.Draft.Parser    (parseExecutableDoc)+import           Network.HTTP.Types.Header        (hContentType)+import           Network.HTTP.Types.Method        (StdMethod (..), parseMethod)+import           Network.HTTP.Types.Status        (ok200)+import           Network.Wai+import           Network.Wai.Handler.Warp         (Port, Settings, run, runSettings)+import qualified Network.Wai.Handler.WebSockets   as WS+import qualified Network.WebSockets               as WS++import           Mu.GraphQL.Query.Parse+import           Mu.GraphQL.Query.Run+import           Mu.GraphQL.Subscription.Protocol+import           Mu.Server++data GraphQLInput = GraphQLInput T.Text VariableMapC (Maybe T.Text)++instance A.FromJSON GraphQLInput where+  parseJSON = A.withObject "GraphQLInput" $+     \v -> GraphQLInput+      <$> v A..: "query"+      <*> (v A..: "variables" <|> pure HM.empty)+      <*> v A..:? "operationName"++-- | Turn a Mu GraphQL 'Server' into a WAI 'Application'.+--   Use this version when your server has not only+--   queries, but also mutations or subscriptions.+graphQLApp ::+    ( GraphQLApp p qr mut sub ServerErrorIO chn hs )+    => ServerT chn p ServerErrorIO hs+    -> Proxy qr+    -> Proxy mut+    -> Proxy sub+    -> Application+graphQLApp = graphQLAppTrans id++-- | Turn a Mu GraphQL 'Server' into a WAI 'Application'.+--   Use this version when your server has only queries.+graphQLAppQuery ::+    forall qr p chn hs.+    ( GraphQLApp p ('Just qr) 'Nothing 'Nothing ServerErrorIO chn hs )+    => ServerT chn p ServerErrorIO hs+    -> Proxy qr+    -> Application+graphQLAppQuery svr _+  = graphQLApp svr (Proxy @('Just qr)) (Proxy @'Nothing) (Proxy @'Nothing)++-- | Turn a Mu GraphQL 'Server' into a WAI 'Application'+--   using a combined transformer stack.+--   See also documentation for 'graphQLAppQuery'.+graphQLAppTransQuery ::+    forall qr m p chn hs.+    ( GraphQLApp p ('Just qr) 'Nothing 'Nothing m chn hs )+    => (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m hs+    -> Proxy qr+    -> Application+graphQLAppTransQuery f svr _+  = graphQLAppTrans f svr (Proxy @('Just qr)) (Proxy @'Nothing) (Proxy @'Nothing)++-- | Turn a Mu GraphQL 'Server' into a WAI 'Application'+--   using a combined transformer stack.+--   See also documentation for 'graphQLApp'.+graphQLAppTrans ::+    ( GraphQLApp p qr mut sub m chn hs )+    => (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m hs+    -> Proxy qr+    -> Proxy mut+    -> Proxy sub+    -> Application+graphQLAppTrans f server q m s+  = WS.websocketsOr WS.defaultConnectionOptions+                    (wsGraphQLAppTrans f server q m s)+                    (httpGraphQLAppTrans f server q m s)++httpGraphQLAppTrans ::+    ( GraphQLApp p qr mut sub m chn hs )+    => (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m hs+    -> Proxy qr+    -> Proxy mut+    -> Proxy sub+    -> Application+httpGraphQLAppTrans f server q m s req res =+  case parseMethod (requestMethod req) of+    Left err   -> toError $ decodeUtf8 err+    Right GET  -> do+      let qst = queryString req+          opN = decodeUtf8 <$> join (lookup "operationName" qst)+      case (fmap decodeUtf8 <$> lookup "query" qst, lookup "variables" qst) of+        (Just (Just qry), Just (Just vars)) ->+          case A.eitherDecode $ fromStrict vars of+            Left err  -> toError $ T.pack err+            Right vrs -> execQuery opN vrs qry+        (Just (Just qry), _)                -> execQuery opN HM.empty qry+        _                                   -> toError "Error parsing query"+    Right POST -> do+      body <- strictRequestBody req+      case lookup hContentType $ requestHeaders req of+        Just "application/json"    ->+          case A.eitherDecode body of+            Left err                             -> toError $ T.pack err+            Right (GraphQLInput qry vars opName) -> execQuery opName vars qry+        Just "application/graphql" ->+          execQuery Nothing HM.empty (decodeUtf8 $ toStrict body)+        _                          -> toError "No `Content-Type` header found!"+    _          -> toError "Unsupported method"+  where+    execQuery :: Maybe T.Text -> VariableMapC -> T.Text -> IO ResponseReceived+    execQuery opn vals qry =+      case parseExecutableDoc qry of+        Left err  -> toError err+        Right doc -> runPipeline f server q m s opn vals doc >>= toResponse+    toError :: T.Text -> IO ResponseReceived+    toError err = toResponse $ A.object [ ("errors", A.Array [ A.object [ ("message", A.String err) ] ])]+    toResponse :: A.Value -> IO ResponseReceived+    toResponse = res . responseBuilder ok200 [] . T.encodeUtf8Builder . encodeToLazyText++wsGraphQLAppTrans+    :: ( GraphQLApp p qr mut sub m chn hs )+    => (forall a. m a -> ServerErrorIO a)+    -> ServerT chn p m hs+    -> Proxy qr+    -> Proxy mut+    -> Proxy sub+    -> WS.ServerApp+wsGraphQLAppTrans f server q m s conn+  = do conn' <- WS.acceptRequest conn+       flip protocol conn' $ runSubscriptionPipeline f server q m s++-- | Run a Mu 'graphQLApp' using the given 'Settings'.+--+--   Go to 'Network.Wai.Handler.Warp' to declare 'Settings'.+runGraphQLAppSettings ::+  ( GraphQLApp p qr mut sub ServerErrorIO chn hs )+  => Settings+  -> ServerT chn p ServerErrorIO hs+  -> Proxy qr+  -> Proxy mut+  -> Proxy sub+  -> IO ()+runGraphQLAppSettings st svr q m s = runSettings st (graphQLApp svr q m s)++-- | Run a Mu 'graphQLApp' on the given port.+runGraphQLApp ::+  ( GraphQLApp p qr mut sub ServerErrorIO chn hs )+  => Port+  -> ServerT chn p ServerErrorIO hs+  -> Proxy qr+  -> Proxy mut+  -> Proxy sub+  -> IO ()+runGraphQLApp port svr q m s = run port (graphQLApp svr q m s)++-- | Run a Mu 'graphQLApp' on a transformer stack on the given port.+runGraphQLAppTrans ::+  ( GraphQLApp p qr mut sub m chn hs )+  => Port+  -> (forall a. m a -> ServerErrorIO a)+  -> ServerT chn p m hs+  -> Proxy qr+  -> Proxy mut+  -> Proxy sub+  -> IO ()+runGraphQLAppTrans port f svr q m s = run port (graphQLAppTrans f svr q m s)++-- | Run a query-only Mu 'graphQLApp' on the given port.+runGraphQLAppQuery ::+  ( GraphQLApp p ('Just qr) 'Nothing 'Nothing ServerErrorIO chn hs )+  => Port+  -> ServerT chn p ServerErrorIO hs+  -> Proxy qr+  -> IO ()+runGraphQLAppQuery port svr q = run port (graphQLAppQuery svr q)
+ src/Mu/GraphQL/Subscription/Protocol.hs view
@@ -0,0 +1,158 @@+{-# language FlexibleContexts    #-}+{-# language OverloadedStrings   #-}+{-# language ScopedTypeVariables #-}+{-+This module implements the protocol as specified in+https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md+-}+module Mu.GraphQL.Subscription.Protocol where++import           Control.Applicative+import           Control.Concurrent+import           Control.Concurrent.Async+import           Control.Concurrent.STM+import           Control.Monad                 (forM_)+import           Control.Monad.IO.Class        (MonadIO (liftIO))+import           Data.Aeson                    ((.:), (.:?), (.=))+import qualified Data.Aeson                    as A+import           Data.Conduit+import qualified Data.HashMap.Strict           as HM+import qualified Data.Text                     as T+import           Language.GraphQL.Draft.Parser (parseExecutableDoc)+import           Language.GraphQL.Draft.Syntax (ExecutableDocument)+import qualified ListT                         as L+import           Network.WebSockets+import qualified StmContainers.Map             as M++import           Mu.GraphQL.Query.Parse++protocol :: ( Maybe T.Text -> VariableMapC -> ExecutableDocument+              -> ConduitT A.Value Void IO ()+              -> IO () )+         -> Connection -> IO ()+protocol f conn = start+  where+    -- listen for GQL_CONNECTION_INIT+    start = do+      msg <- receiveJSON conn+      case msg of+        Just (GQLConnectionInit _)+          -> do -- send GQL_CONNECTION_ACK+                sendJSON conn GQLConnectionAck+                vars <- M.newIO+                -- send GQL_KEEP_ALIVE each 1s.+                withAsync keepAlive $ \ka ->+                -- start listening for incoming messages+                  listen ka vars+        _ -> start  -- Keep waiting+    -- keep-alive+    keepAlive = do+      sendJSON conn GQLKeepAlive+      threadDelay 1000000+      keepAlive+    -- listen for messages from client+    listen ka vars = do+      msg <- receiveJSON conn+      case msg of+        Just (GQLStart i q v o)  -- start handling+          -> withAsync (handle i q v o >> atomically (M.delete i vars)) $ \t -> do+             atomically $ M.insert t i vars+             listen ka vars+        Just (GQLStop i)  -- stop with handling that query+          -> do r <- atomically $ M.lookup i vars+                case r of+                  Nothing -> return ()+                  Just a  -> do cancel a+                                atomically $ M.delete i vars+                listen ka vars+        Just GQLTerminate  -- terminate all queries+          -> do cancelAll ka vars+                sendClose conn ("GraphQL session terminated" :: T.Text)+        _ -> listen ka vars  -- Keep going+    -- Handle a single query+    handle i q v o+      = case parseExecutableDoc q of+          Left err -> sendJSON conn (GQLError i (A.toJSON err))+          Right d  -> do+            f o v d (cndt i)+            sendJSON conn (GQLComplete i)+    -- Conduit which sends the results via the wire+    cndt i = do+      msg <- await+      case msg of+        Nothing -> return ()+        Just v  -> do liftIO $ sendJSON conn (GQLData i v)+                      cndt i+    -- Cancel all pending subscriptions+    cancelAll ka vars+      = do cancel ka+           vs <- atomically $ L.toList $ M.listT vars+           forM_ (map snd vs) cancel++receiveJSON :: A.FromJSON a => Connection -> IO (Maybe a)+receiveJSON conn = do+  d <- receiveData conn+  return $ A.decode d++sendJSON :: A.ToJSON a => Connection -> a -> IO ()+sendJSON conn v+  = sendTextData conn (A.encode v)++data ClientMessage+  = GQLConnectionInit { initPayload :: Maybe A.Value }+  | GQLStart { clientMsgId   :: T.Text+             , query         :: T.Text+             , variables     :: VariableMapC+             , operationName :: Maybe T.Text}+  | GQLStop { clientMsgId :: T.Text }+  | GQLTerminate+  deriving Show++data ServerMessage+  = GQLConnectionError { errorPayload :: Maybe A.Value }+  | GQLConnectionAck+  | GQLData     { serverMsgId :: T.Text+                , payload     :: A.Value }+  | GQLError    { serverMsgId :: T.Text+                , payload     :: A.Value }+  | GQLComplete { serverMsgId :: T.Text}+  | GQLKeepAlive+  deriving Show++instance A.FromJSON ClientMessage where+  parseJSON = A.withObject "ClientMessage" $ \v -> do+     ty :: String <- v .: "type"+     case ty of+       "connection_init"+         -> GQLConnectionInit <$> v .:? "payload"+       "start"+         -> do i <- v .: "id"+               (q,vrs,opN) <- v .: "payload" >>= parsePayload+               pure $ GQLStart i q vrs opN+       "stop"+         -> GQLStop <$> v .: "id"+       "terminate"+         -> pure GQLTerminate+       _ -> empty+    where+      parsePayload = A.withObject "ClientMessage/GQL_START" $+        \v -> (,,) <$> v .: "query"+                   <*> (v .: "variables" <|> pure HM.empty)+                   <*> v .:? "operationName"++theType :: (A.KeyValue kv) => T.Text -> kv+theType t = "type" .= t++instance A.ToJSON ServerMessage where+  toJSON (GQLConnectionError e)+    = A.object [theType "connection_error", "payload" .= e]+  toJSON GQLConnectionAck+    = A.object [theType "connection_acl"]+  toJSON (GQLData i p)+    = A.object [theType "data", "id" .= i, "payload" .= p]+  toJSON (GQLError i p)+    = A.object [theType "error", "id" .= i, "payload" .= p]+  toJSON (GQLComplete i)+    = A.object [theType "complete", "id" .= i]+  toJSON GQLKeepAlive+    = A.object [theType "connection_keep_alive"]