Unify examples for Avro, Protobuf and GraphQL (#139)

This commit is contained in:
Alejandro Serrano 2020-03-13 10:07:56 +01:00 committed by GitHub
parent 234dc1477e
commit 12da3fb688
48 changed files with 270 additions and 994 deletions

View File

@ -6,11 +6,9 @@ packages: compendium-client/
adapter/protobuf/
adapter/persistent/
adapter/kafka/
examples/health-check/avro/
examples/health-check/protobuf/
examples/health-check/
examples/route-guide/
examples/seed/avro/
examples/seed/protobuf/
examples/seed/
examples/todolist/
examples/with-persistent/
grpc/common/

View File

@ -10,11 +10,9 @@ let
in {
compendium-client = hnPkgs.compendium-client.components.library;
mu-avro = hnPkgs.mu-avro.components.all;
mu-example-health-check-avro = hnPkgs.mu-example-health-check-avro.components.all;
mu-example-health-check-protobuf = hnPkgs.mu-example-health-check-protobuf.components.all;
mu-example-health-check = hnPkgs.mu-example-health-check.components.all;
mu-example-route-guide = hnPkgs.mu-example-route-guide.components.all;
mu-example-seed-avro = hnPkgs.mu-example-seed-avro.components.all;
mu-example-seed-protobuf = hnPkgs.mu-example-seed-protobuf.components.all;
mu-example-seed = hnPkgs.mu-example-seed.components.all;
mu-example-todolist = hnPkgs.mu-example-todolist.components.all;
mu-example-with-persistent = hnPkgs.mu-example-with-persistent.components.all;
mu-graphql = hnPkgs.mu-graphql.components.library;

View File

@ -1 +0,0 @@
cradle: { stack: { component: "mu-example-health-check-avro:exe:health-server-avro" } }

View File

@ -1,67 +0,0 @@
name: mu-example-health-check-avro
version: 0.3.0.0
synopsis:
Example health-check project from mu-scala (with avro) ported to mu-haskell
description:
Example health-check project from mu-scala (with avro) ported to mu-haskell.
license: Apache-2.0
license-file: LICENSE
author: Alejandro Serrano, Flavio Corpa
maintainer: alejandro.serrano@47deg.com
copyright: Copyright © 2020 47 Degrees. <http://47deg.com>
category: Network
build-type: Simple
cabal-version: >=1.10
data-files: healthcheck.avdl
bug-reports: https://github.com/higherkindness/mu-haskell/issues
executable health-server-avro
main-is: Server.hs
other-modules: Definition
build-depends:
base >=4.12 && <5
, deferred-folds
, mu-avro >=0.2.0
, mu-grpc-server >=0.2.0
, mu-rpc >=0.2.0
, mu-schema >=0.2.0
, stm
, stm-conduit
, stm-containers
, text
hs-source-dirs: src
default-language: Haskell2010
ghc-options: -Wall
executable health-client-tyapps-avro
main-is: ClientTyApps.hs
other-modules: Definition
build-depends:
base >=4.12 && <5
, mu-avro >=0.2.0
, mu-grpc-client >=0.2.0
, mu-rpc >=0.2.0
, mu-schema >=0.2.0
, text
hs-source-dirs: src
default-language: Haskell2010
ghc-options: -Wall
executable health-client-record-avro
main-is: ClientRecord.hs
other-modules: Definition
build-depends:
base >=4.12 && <5
, mu-avro >=0.2.0
, mu-grpc-client >=0.2.0
, mu-rpc >=0.2.0
, mu-schema >=0.2.0
, text
hs-source-dirs: src
default-language: Haskell2010
ghc-options: -Wall

View File

@ -1,64 +0,0 @@
{-# language AllowAmbiguousTypes #-}
{-# language DataKinds #-}
{-# language DeriveGeneric #-}
{-# language FlexibleContexts #-}
{-# language OverloadedStrings #-}
{-# language ScopedTypeVariables #-}
{-# language TypeApplications #-}
{-# language TypeOperators #-}
module Main where
import qualified Data.Text as T
import GHC.Generics (Generic)
import System.Environment
import Mu.GRpc.Client.Record
import Definition
data HealthCall = HealthCall
{ setStatus :: HealthStatusMsg -> IO (GRpcReply ())
, check :: HealthCheckMsg -> IO (GRpcReply ServerStatusMsg)
, clearStatus :: HealthCheckMsg -> IO (GRpcReply ())
, checkAll :: IO (GRpcReply AllStatusMsg)
, cleanAll :: IO (GRpcReply ())
-- , watch :: HealthCheckMsg -> IO (ConduitT () (GRpcReply ServerStatusMsg) IO ())
} deriving (Generic)
buildHealthCall :: GrpcClient -> HealthCall
buildHealthCall = buildService @'MsgAvro @HealthCheckService @"HealthCheckServiceFS2" @""
main :: IO ()
main = do -- Setup the client
let config = grpcClientConfigSimple "127.0.0.1" 50051 False
Right grpcClient <- setupGrpcClient' config
let client = buildHealthCall grpcClient
-- Execute command
args <- getArgs
case args of
-- ["watch" , who] -> watching client who
["simple", who] -> simple client who
["update", who] -> update client who "SERVING"
["update", who, newstatus] -> update client who newstatus
_ -> putStrLn "unknown command"
simple :: HealthCall -> String -> IO ()
simple client who = do
let hcm = HealthCheckMsg (T.pack who)
putStrLn ("UNARY: Is there some server named " <> who <> "?")
rknown <- check client hcm
putStrLn ("UNARY: Actually the status is " <> show rknown)
update client who "SERVING"
r <- clearStatus client hcm
putStrLn ("UNARY: Was clearing successful? " <> show r)
runknown <- check client hcm
putStrLn ("UNARY: Current status of " <> who <> ": " <> show runknown)
update :: HealthCall -> String -> String -> IO ()
update client who newstatus = do
let hcm = HealthCheckMsg (T.pack who)
putStrLn ("UNARY: Setting " <> who <> " service to " <> newstatus)
r <- setStatus client (HealthStatusMsg hcm (ServerStatusMsg (T.pack newstatus)))
putStrLn ("UNARY: Was setting successful? " <> show r)
rstatus <- check client hcm
putStrLn ("UNARY: Checked the status of " <> who <> ". Obtained: " <> show rstatus)

View File

@ -1,52 +0,0 @@
{-# language AllowAmbiguousTypes #-}
{-# language DataKinds #-}
{-# language FlexibleContexts #-}
{-# language OverloadedStrings #-}
{-# language ScopedTypeVariables #-}
{-# language TypeApplications #-}
{-# language TypeOperators #-}
module Main where
import qualified Data.Text as T
import System.Environment
import Mu.GRpc.Client.TyApps
import Definition
main :: IO ()
main = do -- Setup the client
let config = grpcClientConfigSimple "127.0.0.1" 50051 False
Right client <- setupGrpcClient' config
-- Execute command
args <- getArgs
case args of
["simple", who] -> simple client who
["update", who] -> update client who "SERVING"
["update", who, newstatus] -> update client who newstatus
_ -> putStrLn "unknown command"
simple :: GrpcClient -> String -> IO ()
simple client who = do
let hcm = HealthCheckMsg (T.pack who)
putStrLn ("UNARY: Is there some server named " <> who <> "?")
rknown :: GRpcReply ServerStatusMsg
<- gRpcCall @'MsgAvro @HealthCheckService @"HealthCheckServiceFS2" @"check" client hcm
putStrLn ("UNARY: Actually the status is " <> show rknown)
update client who "SERVING"
r <- gRpcCall @'MsgAvro @HealthCheckService @"HealthCheckServiceFS2" @"clearStatus" client hcm
putStrLn ("UNARY: Was clearing successful? " <> show r)
runknown :: GRpcReply ServerStatusMsg
<- gRpcCall @'MsgAvro @HealthCheckService @"HealthCheckServiceFS2" @"check" client hcm
putStrLn ("UNARY: Current status of " <> who <> ": " <> show runknown)
update :: GrpcClient -> String -> String -> IO ()
update client who newstatus = do
let hcm = HealthCheckMsg (T.pack who)
putStrLn ("UNARY: Setting " <> who <> " service to " <> newstatus)
r <- gRpcCall @'MsgAvro @HealthCheckService @"HealthCheckServiceFS2" @"setStatus" client
(HealthStatusMsg hcm (ServerStatusMsg (T.pack newstatus)))
putStrLn ("UNARY: Was setting successful? " <> show r)
rstatus :: GRpcReply ServerStatusMsg
<- gRpcCall @'MsgAvro @HealthCheckService @"HealthCheckServiceFS2" @"check" client hcm
putStrLn ("UNARY: Checked the status of " <> who <> ". Obtained: " <> show rstatus)

View File

@ -1,47 +0,0 @@
{-# language CPP #-}
{-# language DataKinds #-}
{-# language DeriveAnyClass #-}
{-# language DeriveGeneric #-}
{-# language DuplicateRecordFields #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language MultiParamTypeClasses #-}
{-# language OverloadedLabels #-}
{-# language PolyKinds #-}
{-# language TemplateHaskell #-}
{-# language TypeFamilies #-}
{-# language TypeOperators #-}
module Definition where
import Data.Text as T
import GHC.Generics
import Mu.Quasi.Avro
import Mu.Schema
#if __GHCIDE__
avdl "HealthCheckSchema" "HealthCheckService" "examples/health-check/avro" "healthcheck.avdl"
#else
avdl "HealthCheckSchema" "HealthCheckService" "." "healthcheck.avdl"
#endif
newtype HealthCheckMsg
= HealthCheckMsg { nameService :: T.Text }
deriving ( Eq, Show, Ord, Generic
, ToSchema HealthCheckSchema "HealthCheck"
, FromSchema HealthCheckSchema "HealthCheck" )
newtype ServerStatusMsg
= ServerStatusMsg { status :: T.Text }
deriving ( Eq, Show, Ord, Generic
, ToSchema HealthCheckSchema "ServerStatus"
, FromSchema HealthCheckSchema "ServerStatus" )
data HealthStatusMsg
= HealthStatusMsg { hc :: HealthCheckMsg, status :: ServerStatusMsg }
deriving ( Eq, Show, Ord, Generic
, ToSchema HealthCheckSchema "HealthStatus"
, FromSchema HealthCheckSchema "HealthStatus" )
newtype AllStatusMsg
= AllStatusMsg { all :: [HealthStatusMsg] }
deriving ( Eq, Show, Ord, Generic
, ToSchema HealthCheckSchema "AllStatus"
, FromSchema HealthCheckSchema "AllStatus" )

View File

@ -1,87 +0,0 @@
{-# language OverloadedStrings #-}
{-# language PartialTypeSignatures #-}
{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}
module Main where
import Control.Concurrent.STM
import Data.Conduit.TMChan
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import DeferredFolds.UnfoldlM
import qualified StmContainers.Map as M
import Mu.GRpc.Server
import Mu.Server
import Definition
main :: IO ()
main = do
m <- M.newIO
upd <- newTBMChanIO 100
putStrLn "running health check application"
runGRpcApp msgAvro 50051 (server m upd)
-- Server implementation
-- https://github.com/higherkindness/mu/blob/master/modules/health-check-unary/src/main/scala/higherkindness/mu/rpc/healthcheck/unary/handler/HealthServiceImpl.scala
type StatusMap = M.Map T.Text T.Text
type StatusUpdates = TBMChan HealthStatusMsg
server :: StatusMap -> StatusUpdates -> ServerIO HealthCheckService _
server m upd = Server (
checkH_ m :<|>:
checkAll_ m :<|>:
cleanAll_ m :<|>:
clearStatus_ m :<|>:
setStatus_ m upd :<|>:
{- watch_ upd :<|>: -} H0)
setStatus_ :: StatusMap -> StatusUpdates -> HealthStatusMsg -> ServerErrorIO ()
setStatus_ m upd s@(HealthStatusMsg (HealthCheckMsg nm) (ServerStatusMsg ss))
= alwaysOk $ do
putStr "setStatus: " >> print (nm, ss)
atomically $ do
M.insert ss nm m
writeTBMChan upd s
print =<< atomically (M.lookup nm m)
checkH_ :: StatusMap -> HealthCheckMsg -> ServerErrorIO ServerStatusMsg
checkH_ m (HealthCheckMsg nm) = alwaysOk $ do
putStr "check: " >> print nm
ss <- atomically $ M.lookup nm m
print ss
pure $ ServerStatusMsg (fromMaybe "<unknown>" ss)
clearStatus_ :: StatusMap -> HealthCheckMsg -> ServerErrorIO ()
clearStatus_ m (HealthCheckMsg nm) = alwaysOk $ do
putStr "clearStatus: " >> print nm
atomically $ M.delete nm m
checkAll_ :: StatusMap -> ServerErrorIO AllStatusMsg
checkAll_ m = alwaysOk $ do
putStrLn "checkAll"
AllStatusMsg <$> atomically (consumeValues kvToStatus (M.unfoldlM m))
where
consumeValues :: Monad m => (k -> v -> a) -> UnfoldlM m (k,v) -> m [a]
consumeValues f = foldlM' (\xs (x,y) -> pure (f x y:xs)) []
kvToStatus k v = HealthStatusMsg (HealthCheckMsg k) (ServerStatusMsg v)
cleanAll_ :: StatusMap -> ServerErrorIO ()
cleanAll_ m = alwaysOk $ do
putStrLn "cleanAll"
atomically $ M.reset m
{- Note: no "streams" in avro
watch_ :: StatusUpdates
-> HealthCheckMsg
-> ConduitT ServerStatusMsg Void ServerErrorIO ()
-> ServerErrorIO ()
watch_ upd hcm@(HealthCheckMsg nm) sink = do
alwaysOk (putStr "watch: " >> print nm)
runConduit $ sourceTBMChan upd
.| C.filter (\(HealthStatusMsg c _) -> hcm == c)
.| C.map (\(HealthStatusMsg _ s) -> s)
.| sink
-}

View File

@ -0,0 +1 @@
cradle: { stack: { component: "mu-example-health-check:exe:health-server" } }

View File

@ -1,4 +1,4 @@
name: mu-example-health-check-protobuf
name: mu-example-health-check
version: 0.3.0.0
synopsis:
Example health-check project from mu-scala (with protobuf) ported to mu-haskell
@ -17,27 +17,31 @@ cabal-version: >=1.10
data-files: healthcheck.proto
bug-reports: https://github.com/higherkindness/mu-haskell/issues
executable health-server-protobuf
executable health-server
main-is: Server.hs
other-modules: Definition
build-depends:
base >=4.12 && <5
, conduit
, deferred-folds
, mu-grpc-server >=0.2.0
, mu-protobuf >=0.2.0
, mu-rpc >=0.2.0
, mu-schema >=0.2.0
, mu-graphql
, mu-grpc-server >=0.3.0
, mu-protobuf >=0.3.0
, mu-rpc >=0.3.0
, mu-schema >=0.3.0
, stm
, stm-conduit
, stm-containers
, text
, wai
, wai-route
, warp
hs-source-dirs: src
default-language: Haskell2010
ghc-options: -Wall
executable health-client-tyapps-protobuf
executable health-client-tyapps
main-is: ClientTyApps.hs
other-modules: Definition
build-depends:
@ -53,7 +57,7 @@ executable health-client-tyapps-protobuf
default-language: Haskell2010
ghc-options: -Wall
executable health-client-record-protobuf
executable health-client-record
main-is: ClientRecord.hs
other-modules: Definition
build-depends:

View File

@ -1 +0,0 @@
cradle: { stack: { component: "mu-example-health-check-protobuf:exe:health-server-protobuf" } }

View File

@ -1,5 +1,7 @@
{-# language DataKinds #-}
{-# language OverloadedStrings #-}
{-# language PartialTypeSignatures #-}
{-# language TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}
module Main where
@ -9,10 +11,14 @@ import Data.Conduit
import qualified Data.Conduit.Combinators as C
import Data.Conduit.TMChan
import Data.Maybe (fromMaybe)
import Data.Proxy
import qualified Data.Text as T
import DeferredFolds.UnfoldlM
import Network.Wai.Handler.Warp
import Network.Wai.Route
import qualified StmContainers.Map as M
import Mu.GraphQL.Server
import Mu.GRpc.Server
import Mu.Server
@ -23,7 +29,15 @@ main = do
m <- M.newIO
upd <- newTBMChanIO 100
putStrLn "running health check application"
runGRpcApp msgProtoBuf 50051 (server m upd)
let s = server m upd
run 50051 $ flip route app404 $ compileRoutes
[ defRoute (str "proto" ./ end) $
\_ -> gRpcApp msgProtoBuf s
, defRoute (str "avro" ./ end) $
\_ -> gRpcApp msgAvro s
, defRoute (str "graphql" ./ end) $
\_ -> graphQLApp s (Proxy @"HealthCheckServiceFS2") (Proxy @"HealthCheckServiceFS2")
]
-- Server implementation
-- https://github.com/higherkindness/mu/blob/master/modules/health-check-unary/src/main/scala/higherkindness/mu/rpc/healthcheck/unary/handler/HealthServiceImpl.scala

View File

@ -1,202 +0,0 @@
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.

View File

@ -1,18 +0,0 @@
# Seed RPC example with Avro!
## Execution
Running the server:
```bash
stack run mu-example-seed-avro
```
[comment]: # (Start Copyright)
# Copyright
Mu is designed and developed by 47 Degrees
Copyright © 2020 47 Degrees. <http://47deg.com>
[comment]: # (End Copyright)

View File

@ -1 +0,0 @@
cradle: { stack: { component: "mu-example-seed-avro:exe:seed-server" } }

View File

@ -1,39 +0,0 @@
name: mu-example-seed-avro
version: 0.3.0.0
synopsis:
Example seed project from mu-scala (with avro) ported to mu-haskell
description:
Example seed project from mu-scala (with avro) ported to mu-haskell.
license: Apache-2.0
license-file: LICENSE
author: Flavio Corpa, Alejandro Serrano
maintainer: flavio.corpa@47deg.com
copyright: Copyright © 2020 47 Degrees. <http://47deg.com>
category: Web
build-type: Simple
cabal-version: >=1.10
extra-source-files: README.md
data-files: seed.avdl
homepage:
https://github.com/higherkindness/mu-haskell/examples/seed/avro#readme
bug-reports: https://github.com/higherkindness/mu-haskell/issues
executable mu-example-seed-avro
hs-source-dirs: src
main-is: Main.hs
other-modules: Schema
default-language: Haskell2010
build-depends:
base >=4.12 && <5
, monad-logger
, mu-avro >=0.2.0
, mu-grpc-server >=0.2.0
, mu-optics >=0.2.0
, mu-rpc >=0.2.0
, mu-schema >=0.2.0
, text
ghc-options: -Wall -fprint-potential-instances

View File

@ -1,40 +0,0 @@
{-# language DataKinds #-}
{-# language DuplicateRecordFields #-}
{-# language FlexibleContexts #-}
{-# language OverloadedLabels #-}
{-# language OverloadedStrings #-}
{-# language PartialTypeSignatures #-}
{-# language TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}
module Main where
import Control.Monad.Logger
import Data.Int
import Mu.GRpc.Server
import Mu.Schema
import Mu.Schema.Optics
import Mu.Server
import Schema
type Person = Term SeedSchema (SeedSchema :/: "Person")
type PeopleRequest = Term SeedSchema (SeedSchema :/: "PeopleRequest")
type PeopleResponse = Term SeedSchema (SeedSchema :/: "PeopleResponse")
main :: IO ()
main = do
putStrLn "running seed application"
runGRpcAppTrans msgAvro 8080 runStderrLoggingT server
-- Server implementation
-- https://github.com/higherkindness/mu/blob/master/modules/examples/seed/server/modules/process/src/main/scala/example/seed/server/process/ProtoPeopleServiceHandler.scala
server :: (MonadServer m, MonadLogger m) => SingleServerT PeopleService m _
server = Server (getPerson :<|>: H0)
evolvePerson :: PeopleRequest -> PeopleResponse
evolvePerson req = record1 (review _U0 $ record (req ^. #name, 18 :: Int32))
getPerson :: Monad m => PeopleRequest -> m PeopleResponse
getPerson = pure . evolvePerson

View File

@ -1,21 +0,0 @@
{-# language CPP #-}
{-# language DataKinds #-}
{-# language DeriveAnyClass #-}
{-# language DeriveGeneric #-}
{-# language DuplicateRecordFields #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language MultiParamTypeClasses #-}
{-# language PolyKinds #-}
{-# language TemplateHaskell #-}
{-# language TypeFamilies #-}
{-# language TypeOperators #-}
module Schema where
import Mu.Quasi.Avro
#if __GHCIDE__
avdl "SeedSchema" "PeopleService" "examples/seed/avro" "seed.avdl"
#else
avdl "SeedSchema" "PeopleService" "." "seed.avdl"
#endif

View File

@ -1,17 +0,0 @@
resolver: lts-14.22
allow-newer: true
extra-deps:
# mu
- mu-schema-0.1.0.0
- mu-rpc-0.1.0.0
- mu-optics-0.1.0.0
- mu-avro-0.1.0.0
- mu-grpc-server-0.1.0.1
- compendium-client-0.1.0.1
# dependencies of mu
- http2-client-0.9.0.0
- http2-grpc-types-0.5.0.0
- http2-grpc-proto3-wire-0.1.0.0
- warp-grpc-0.3.0.0
- proto3-wire-1.1.0
- language-protobuf-1.0.1

1
examples/seed/hie.yaml Normal file
View File

@ -0,0 +1 @@
cradle: { stack: { component: "mu-example-seed:exe:seed-server" } }

View File

@ -1,4 +1,4 @@
name: mu-example-seed-protobuf
name: mu-example-seed
version: 0.3.0.0
synopsis:
Example seed project from mu-scala (with protobuf) ported to mu-haskell
@ -30,12 +30,16 @@ executable seed-server
base >=4.12 && <5
, conduit
, monad-logger
, mu-grpc-server >=0.2.0
, mu-protobuf >=0.2.0
, mu-rpc >=0.2.0
, mu-schema >=0.2.0
, mu-graphql
, mu-grpc-server >=0.3.0
, mu-protobuf >=0.3.0
, mu-rpc >=0.3.0
, mu-schema >=0.3.0
, stm
, text
, wai
, wai-route
, warp
executable seed-server-optics
hs-source-dirs: src

View File

@ -1,3 +0,0 @@
.stack-work/
stack*.yaml.lock
*~

View File

@ -1,202 +0,0 @@
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.

View File

@ -1,2 +0,0 @@
import Distribution.Simple
main = defaultMain

View File

@ -1 +0,0 @@
cradle: { stack: { component: "mu-example-seed-protobuf:exe:seed-server" } }

View File

@ -5,6 +5,7 @@
{-# language FlexibleContexts #-}
{-# language OverloadedStrings #-}
{-# language PartialTypeSignatures #-}
{-# language TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}
module Main where
@ -17,9 +18,13 @@ import Data.Conduit.Combinators as C
import Data.Int
import Data.Text as T
import GHC.Generics
import Mu.GraphQL.Server
import Mu.GRpc.Server
import Mu.Schema
import Mu.Server
import Network.Wai
import Network.Wai.Handler.Warp
import Network.Wai.Route
import Schema
@ -45,7 +50,15 @@ newtype PeopleResponse = PeopleResponse
main :: IO ()
main = do
putStrLn "running seed application"
runGRpcAppTrans msgProtoBuf 8080 runStderrLoggingT server
run 8080 $ flip route app404 $ compileRoutes
[ defRoute (str "proto" ./ end) $
\_ -> gRpcAppTrans msgProtoBuf runStderrLoggingT server
, defRoute (str "avro" ./ end) $
\_ -> gRpcAppTrans msgAvro runStderrLoggingT server
, defRoute (str "graphql" ./ end) $
\_ -> graphQLAppTrans runStderrLoggingT server (Proxy @"PeopleService") (Proxy @"PeopleService")
]
-- Server implementation
-- https://github.com/higherkindness/mu/blob/master/modules/examples/seed/server/modules/process/src/main/scala/example/seed/server/process/ProtoPeopleServiceHandler.scala

View File

@ -26,6 +26,7 @@ library
aeson
, base >=4.12 && <5
, bytestring
, conduit
, graphql-parser
, http-types
, mtl
@ -33,6 +34,8 @@ library
, mu-schema
, scientific
, sop-core
, stm-chans
, stm-conduit
, text
, unordered-containers
, wai

View File

@ -32,10 +32,13 @@ data ChosenMethodQuery (p :: Package snm mnm anm) (m :: Method snm mnm anm) wher
ChosenMethodQuery
:: NP (ArgumentValue p) args
-> ReturnQuery p r
-> ChosenMethodQuery p ('Method mname anns args ('RetSingle 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)
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)
@ -46,14 +49,19 @@ data ArgumentValue' (p :: Package snm mnm anm) (r :: TypeRef snm) where
ArgOptional :: Maybe (ArgumentValue' p r)
-> ArgumentValue' p ('OptionalRef r)
data ReturnQuery (p :: Package snm mnm anm) (r :: TypeRef snm) where
RetPrimitive :: ReturnQuery p ('PrimitiveRef t)
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)
-> 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)
-> ReturnQuery' ('Package pname ss) ('ObjectRef s)
data SchemaQuery (sch :: Schema tn fn) (t :: TypeDef tn fn) where
QueryEnum :: SchemaQuery sch ('DEnum nm choices)

View File

@ -170,13 +170,13 @@ 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, ParseReturn p r) =>
ParseMethod p ('Method mname manns args ('RetSingle r) ': ms)
(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
<*> parseReturn vmap frmap wanted sels)
<*> parseDiffReturn vmap frmap wanted sels)
| otherwise
= S <$> selectMethod tyName vmap frmap w args sels
where
@ -190,6 +190,20 @@ class ParseArgs (p :: Package') (args :: [Argument']) where
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
@ -202,6 +216,18 @@ instance (KnownName aname, ParseArg p a, ParseArgs p as, FindDefaultArgValue aan
-> 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
@ -401,18 +427,41 @@ instance (ValueParser sch r) => ValueParser sch ('TList r) where
= 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)
-> f (ReturnQuery' p r)
instance ParseReturn p ('PrimitiveRef t) where
parseReturn _ _ _ []

View File

@ -7,6 +7,7 @@
{-# language OverloadedLists #-}
{-# language OverloadedStrings #-}
{-# language PolyKinds #-}
{-# language RankNTypes #-}
{-# language ScopedTypeVariables #-}
{-# language TupleSections #-}
{-# language TypeApplications #-}
@ -22,14 +23,18 @@ module Mu.GraphQL.Query.Run (
, RunQueryFindHandler
) where
import Control.Monad.Except (runExceptT)
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 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 Data.Maybe
import qualified Data.Text as T
import qualified Data.Text as T
import GHC.TypeLits
import qualified Language.GraphQL.Draft.Syntax as GQL
import qualified Language.GraphQL.Draft.Syntax as GQL
import Mu.GraphQL.Query.Definition
import Mu.GraphQL.Query.Parse
@ -40,14 +45,14 @@ import Mu.Server
data GraphQLError
= GraphQLError ServerError [T.Text]
type GraphQLApp p pname ss qmethods mmethods hs chn qr mut qanns manns =
type GraphQLApp m p pname ss qmethods mmethods hs chn qr mut qanns manns =
( p ~ 'Package pname ss
, KnownName qr
, ParseMethod p qmethods
, KnownName mut
, ParseMethod p mmethods
, RunQueryFindHandler p hs chn ss (LookupService ss qr) hs
, RunQueryFindHandler p hs chn ss (LookupService ss mut) hs
, RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs
, RunQueryFindHandler m p hs chn ss (LookupService ss mut) hs
, MappingRight chn qr ~ ()
, LookupService ss qr ~ 'Service qr qanns qmethods
, LookupService ss mut ~ 'Service mut manns mmethods
@ -55,13 +60,14 @@ type GraphQLApp p pname ss qmethods mmethods hs chn qr mut qanns manns =
)
runPipeline
:: forall qr mut (p :: Package') pname ss hs chn qanns qmethods manns mmethods.
( GraphQLApp p pname ss qmethods mmethods hs chn qr mut qanns manns )
=> ServerT chn p ServerErrorIO hs
:: forall m qr mut (p :: Package') pname ss hs chn qanns qmethods manns mmethods.
( GraphQLApp m p pname ss qmethods mmethods hs chn qr mut qanns manns )
=> (forall a. m a -> ServerErrorIO a)
-> ServerT chn p m hs
-> Proxy qr -> Proxy mut
-> Maybe T.Text -> VariableMapC -> GQL.ExecutableDocument
-> IO Aeson.Value
runPipeline svr _ _ opName vmap doc
runPipeline f svr _ _ opName vmap doc
= case parseDoc opName vmap doc of
Left e ->
pure $
@ -69,7 +75,7 @@ runPipeline svr _ _ opName vmap doc
("errors", Aeson.Array [
Aeson.object [ ("message", Aeson.String e) ] ])]
Right (d :: Document p qr mut) -> do
(data_, errors) <- runWriterT (runDocument svr d)
(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) ]
@ -83,58 +89,61 @@ runPipeline svr _ _ opName vmap doc
runDocument
:: ( p ~ 'Package pname ss
, RunQueryFindHandler p hs chn ss (LookupService ss qr) hs
, RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs
, MappingRight chn qr ~ ()
, RunQueryFindHandler p hs chn ss (LookupService ss mut) hs
, RunQueryFindHandler m p hs chn ss (LookupService ss mut) hs
, MappingRight chn mut ~ ()
)
=> ServerT chn p ServerErrorIO hs
=> (forall a. m a -> ServerErrorIO a)
-> ServerT chn p m hs
-> Document p qr mut
-> WriterT [GraphQLError] IO Aeson.Value
runDocument svr (QueryDoc q)
= runQuery svr () q
runDocument svr (MutationDoc q)
= runQuery svr () q
runDocument f svr (QueryDoc q)
= runQuery f svr () q
runDocument f svr (MutationDoc q)
= runQuery f svr () q
runQuery
:: forall p s pname ss hs sname sanns ms chn inh.
( RunQueryFindHandler p hs chn ss s hs
:: 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 )
=> ServerT chn p ServerErrorIO hs
=> (forall a. m a -> ServerErrorIO a)
-> ServerT chn p m hs
-> inh
-> ServiceQuery p s
-> WriterT [GraphQLError] IO Aeson.Value
runQuery whole@(Services ss) = runQueryFindHandler whole ss
runQuery f whole@(Services ss) = runQueryFindHandler f whole ss
class RunQueryFindHandler p whole chn ss s hs where
class RunQueryFindHandler m p whole chn ss s hs where
runQueryFindHandler
:: ( p ~  'Package pname wholess
, s ~ 'Service sname sanns ms
, inh ~ MappingRight chn sname )
=> ServerT chn p ServerErrorIO whole
-> ServicesT chn ss ServerErrorIO hs
=> (forall a. m a -> ServerErrorIO a)
-> ServerT chn p m whole
-> ServicesT chn ss m hs
-> inh
-> ServiceQuery p s
-> WriterT [GraphQLError] IO Aeson.Value
instance TypeError ('Text "Could not find handler for " ':<>: 'ShowType s)
=> RunQueryFindHandler p whole chn '[] s '[] where
runQueryFindHandler = error "this should never be called"
=> RunQueryFindHandler m p whole chn '[] s '[] where
runQueryFindHandler _ = error "this should never be called"
instance {-# OVERLAPPABLE #-}
RunQueryFindHandler p whole chn ss s hs
=> RunQueryFindHandler p whole chn (other ': ss) s (h ': hs) where
runQueryFindHandler whole (_ :<&>: that) = runQueryFindHandler whole that
instance {-# OVERLAPS #-} (s ~ 'Service sname sanns ms, RunMethod p whole chn sname ms h)
=> RunQueryFindHandler p whole chn (s ': ss) s (h ': hs) where
runQueryFindHandler whole (this :<&>: _) inh queries
RunQueryFindHandler m p whole chn ss s hs
=> RunQueryFindHandler m p whole chn (other ': ss) s (h ': hs) where
runQueryFindHandler f whole (_ :<&>: that) = runQueryFindHandler f whole that
instance {-# OVERLAPS #-} (s ~ 'Service sname sanns ms, RunMethod m p whole chn sname ms h)
=> RunQueryFindHandler m p whole chn (s ': ss) s (h ': hs) where
runQueryFindHandler f whole (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)
= pass (do (val, methodName) <- runMethod whole (Proxy @sname) inh this args
= pass (do (val, methodName) <- runMethod f whole (Proxy @sname) inh this args
let realName = fromMaybe methodName nm
-- choose between given name,
-- or fallback to method name
@ -144,43 +153,70 @@ instance {-# OVERLAPS #-} (s ~ 'Service sname sanns ms, RunMethod p whole chn s
updateErrs :: T.Text -> GraphQLError -> GraphQLError
updateErrs methodName (GraphQLError err loc) = GraphQLError err (methodName : loc)
class RunMethod p whole chn sname ms hs where
class RunMethod m p whole chn sname ms hs where
runMethod
:: ( p ~ 'Package pname wholess
, inh ~ MappingRight chn sname )
=> ServerT chn p ServerErrorIO whole
=> (forall a. m a -> ServerErrorIO a)
-> ServerT chn p m whole
-> Proxy sname -> inh
-> HandlersT chn inh ms ServerErrorIO hs
-> HandlersT chn inh ms m hs
-> NS (ChosenMethodQuery p) ms
-> WriterT [GraphQLError] IO (Maybe Aeson.Value, T.Text)
instance RunMethod p whole chn s '[] '[] where
runMethod = error "this should never be called"
instance (RunMethod p whole chn s ms hs, KnownName mname, RunHandler p whole chn args r h)
=> RunMethod p whole chn s ('Method mname anns args ('RetSingle r) ': ms) (h ': hs) where
runMethod whole _ inh (h :<||>: _) (Z (ChosenMethodQuery args ret))
= (, T.pack $ nameVal (Proxy @mname)) <$> runHandler whole (h inh) args ret
runMethod whole p inh (_ :<||>: r) (S cont)
= runMethod whole p inh r cont
instance RunMethod m p whole chn s '[] '[] where
runMethod _ = 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
runMethod f whole _ inh (h :<||>: _) (Z (ChosenMethodQuery args ret))
= (, T.pack $ nameVal (Proxy @mname)) <$> runHandler f whole (h inh) args ret
runMethod f whole p inh (_ :<||>: r) (S cont)
= runMethod f whole p inh r cont
class Handles chn args ('RetSingle r) ServerErrorIO h
=> RunHandler p whole chn args r h where
runHandler :: ServerT chn p ServerErrorIO whole
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
-> h
-> NP (ArgumentValue p) args
-> ReturnQuery p r
-> WriterT [GraphQLError] IO (Maybe Aeson.Value)
instance (ArgumentConversion chn ref t, RunHandler p whole chn rest r h)
=> RunHandler p whole chn ('ArgSingle aname aanns ref ': rest) r (t -> h) where
runHandler whole h (ArgumentValue one :* rest)
= runHandler whole (h (convertArg (Proxy @chn) one)) rest
instance (ResultConversion p whole chn r l)
=> RunHandler p whole chn '[] r (ServerErrorIO l) where
runHandler whole h Nil q = do
res <- liftIO $ runExceptT h
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 h (ArgumentValue one :* rest)
= runHandler f whole (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 h (ArgumentStream lst :* rest)
= let converted :: [t] = convertArg (Proxy @chn) lst
in runHandler f whole (h (yieldMany converted)) rest
instance (MonadError ServerError m)
=> RunHandler m p whole chn '[] 'RetNothing (m ()) where
runHandler f _ h Nil _ = do
res <- liftIO $ runExceptT (f h)
case res of
Right v -> convertResult whole q v
Right _ -> pure $ Just Aeson.Null
Left e -> tell [GraphQLError e []] >> pure Nothing
instance (MonadError ServerError m, ResultConversion m p whole chn r l)
=> RunHandler m p whole chn '[] ('RetSingle r) (m l) where
runHandler f whole h Nil (RSingle q) = do
res <- liftIO $ runExceptT (f h)
case res of
Right v -> convertResult f whole q v
Left e -> tell [GraphQLError e []] >> pure Nothing
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 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 q) info
Left e -> tell [GraphQLError e []] >> pure Nothing
class FromRef chn ref t
@ -198,35 +234,36 @@ 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 p whole chn r l where
convertResult :: ServerT chn p ServerErrorIO whole
-> ReturnQuery p r
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
-> ReturnQuery' p r
-> l -> WriterT [GraphQLError] IO (Maybe Aeson.Value)
instance Aeson.ToJSON t => ResultConversion p whole chn ('PrimitiveRef t) t where
convertResult _ RetPrimitive = pure . Just . Aeson.toJSON
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 p whole chn ('SchemaRef sch l) r where
convertResult _ (RetSchema r) t
=> 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 ('Package pname ss) whole chn ss ('Service sname sanns ms) whole)
=> ResultConversion ('Package pname ss) whole chn ('ObjectRef ref) t where
convertResult whole (RetObject q) h
= Just <$> runQuery @('Package pname ss) @(LookupService ss ref) whole h q
instance ResultConversion p whole chn r s
=> ResultConversion p whole chn ('OptionalRef r) (Maybe s) where
convertResult _ _ Nothing
, 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 (RetObject q) h
= Just <$> runQuery @m @('Package pname ss) @(LookupService ss ref) f whole 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 whole (RetOptional q) (Just x)
= convertResult whole q x
instance ResultConversion p whole chn r s
=> ResultConversion p whole chn ('ListRef r) [s] where
convertResult whole (RetList q) xs
= Just . Aeson.toJSON . catMaybes <$> mapM (convertResult whole q) xs
convertResult f whole (RetOptional q) (Just x)
= convertResult f whole q x
instance ResultConversion m p whole chn r s
=> ResultConversion m p whole chn ('ListRef r) [s] where
convertResult f whole (RetList q) xs
= Just . Aeson.toJSON . catMaybes <$> mapM (convertResult f whole q) xs
class RunSchemaQuery sch r where
runSchemaQuery

View File

@ -3,13 +3,15 @@
{-# language GADTs #-}
{-# language OverloadedLists #-}
{-# language OverloadedStrings #-}
{-# language RankNTypes #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Mu.GraphQL.Server (
GraphQLApp
, graphQLApp
, runGraphQLApp
, runGraphQLAppSettings
, graphQLApp
, graphQLAppTrans
) where
import Control.Applicative ((<|>))
@ -62,12 +64,21 @@ instance A.FromJSON ValueConst where
-- for example, @wai-routes@, or you can add middleware
-- from @wai-extra@, among others.
graphQLApp ::
( GraphQLApp p pname ss qmethods mmethods hs chn qr mut qanns manns )
( GraphQLApp ServerErrorIO p pname ss qmethods mmethods hs chn qr mut qanns manns )
=> ServerT chn p ServerErrorIO hs
-> Proxy qr
-> Proxy mut
-> Application
graphQLApp server q m req res =
graphQLApp = graphQLAppTrans id
graphQLAppTrans ::
( GraphQLApp m p pname ss qmethods mmethods hs chn qr mut qanns manns )
=> (forall a. m a -> ServerErrorIO a)
-> ServerT chn p m hs
-> Proxy qr
-> Proxy mut
-> Application
graphQLAppTrans f server q m req res =
case parseMethod (requestMethod req) of
Left err -> toError $ decodeUtf8 err
Right GET -> do
@ -96,7 +107,7 @@ graphQLApp server q m req res =
execQuery opn vals qry =
case parseExecutableDoc qry of
Left err -> toError err
Right doc -> runPipeline server q m opn vals doc >>= toResponse
Right doc -> runPipeline f server q m 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
@ -106,7 +117,7 @@ graphQLApp server q m req res =
--
-- Go to 'Network.Wai.Handler.Warp' to declare 'Settings'.
runGraphQLAppSettings ::
( GraphQLApp p pname ss qmethods mmethods hs chn qr mut qanns manns )
( GraphQLApp ServerErrorIO p pname ss qmethods mmethods hs chn qr mut qanns manns )
=> Settings
-> ServerT chn p ServerErrorIO hs
-> Proxy qr
@ -116,7 +127,7 @@ runGraphQLAppSettings st svr q m = runSettings st (graphQLApp svr q m)
-- | Run a Mu 'graphQLApp' on the given port.
runGraphQLApp ::
( GraphQLApp p pname ss qmethods mmethods hs chn qr mut qanns manns )
( GraphQLApp ServerErrorIO p pname ss qmethods mmethods hs chn qr mut qanns manns )
=> Port
-> ServerT chn p ServerErrorIO hs
-> Proxy qr

View File

@ -28,7 +28,7 @@ module Mu.GRpc.Server
, runGRpcAppSettings, Settings
, runGRpcAppTLS, TLSSettings
-- * Convert a 'Server' into a WAI application
, gRpcApp
, gRpcApp, gRpcAppTrans
-- * Raise errors as exceptions in IO
, raiseErrors, liftServerConduit
-- * Re-export useful instances

View File

@ -13,11 +13,9 @@ packages:
- grpc/client
- grpc/server
- graphql
- examples/health-check/avro
- examples/health-check/protobuf
- examples/health-check
- examples/route-guide
- examples/seed/avro
- examples/seed/protobuf
- examples/seed
- examples/todolist
- examples/with-persistent
- compendium-client
@ -37,3 +35,5 @@ extra-deps:
- HasBigDecimal-0.1.1
- git: https://github.com/hasura/graphql-parser-hs.git
commit: 1380495a7b3269b70a7ab3081d745a5f54171a9c
- wai-route-1.0.0
- pattern-trie-0.1.0

View File

@ -13,11 +13,9 @@ packages:
- grpc/client
- grpc/server
- graphql
- examples/health-check/avro
- examples/health-check/protobuf
- examples/health-check
- examples/route-guide
- examples/seed/avro
- examples/seed/protobuf
- examples/seed
- examples/todolist
- examples/with-persistent
- compendium-client
@ -37,6 +35,8 @@ extra-deps:
- HasBigDecimal-0.1.1
- git: https://github.com/hasura/graphql-parser-hs.git
commit: 1380495a7b3269b70a7ab3081d745a5f54171a9c
- wai-route-1.0.0
- pattern-trie-0.1.0
# missing in the current LTS
- primitive-0.7.0.0
- primitive-extras-0.8