Skip to content

Instantly share code, notes, and snippets.

@srobertson
Created May 11, 2020 23:48
Show Gist options
  • Save srobertson/4b38b927202cd44975c4dae15fce4b63 to your computer and use it in GitHub Desktop.
Save srobertson/4b38b927202cd44975c4dae15fce4b63 to your computer and use it in GitHub Desktop.
string :: Name -> ResModel o e m
string = ResScalar . Data.Morpheus.Types.String
prompt :: String -> IO Text
prompt msg =
putStr (msg ++ ": ")
>> hFlush stdout -- Send output to terminal now
>> pack <$> getLine -- Get string from User and convert to Text
getSchema :: Monad m => ResponseStream e m Data.Morpheus.Types.Internal.AST.Schema
getSchema =
schemaFromTypeDefinitions
[dsl|
type Query {
deity(name: String): Deity!
}
type Deity {
name: String!
power: [String!]!
}
|]
resolver :: MonadIO m => RootResModel e m
resolver =
RootResModel
{ query =
pure $
ResObject
( ObjectResModel
{ __typename = "Query",
objectFields =
[ ( "deity",
pure
$ ResObject
$ ObjectResModel
{ __typename = "Deity",
objectFields =
[ ( "name",
liftIO $ prompt "Name" >>= pure . string
),
( "power",
pure $
ResList
[string "Shapeshifting"]
)
]
}
)
]
}
),
mutation = pure ResNull,
subscription = pure ResNull
}
main :: IO ()
main = Web.scotty 3000 $ Web.post "/api" $ Web.raw =<< (liftIO . api =<< Web.body)
apiD :: MonadIO m => GQLRequest -> m GQLResponse
apiD req =
renderResponse <$> runResultT app
where
app = do
schema <- getSchema
runApi schema resolver req
api :: L.ByteString -> IO L.ByteString
api = mapAPI apiD
@srobertson
Copy link
Author

What I did was take the example from Spec.hs and hooked it up to Scotty. Then to simulate
IO prompted someone at the console to type in a name at the console whenever
name was requested I.e.

query {
    diety {
        name
    }
}

Obviously not a scalable solution for the API but fun none the less.

Observations/Findings/Questions:

  1. I was expecting a function like interpreter to get from
    my derived resolver to a ByteString -> IO ByteString. Something
    equivalent to the examples from the main ReadME
api :: ByteString -> IO ByteString
api = interpreter rootResolver

The closest I was able to get was following the lead in statelessResolver

apiD ::   MonadIO m => GQLRequest -> m GQLResponse
apiD req =
    renderResponse <$> runResultT app
    where
        app = do
            schema <- getSchema
            runApi schema resolver req

api :: L.ByteString -> IO L.ByteString
api = mapAPI apiD

Ideally it would be nice if interpreter TypeClass could be refactored to
work with runApi

  1. It felt weird to have to have to declare
    resolver :: MonadIO m => RootResModel e m but that's the only way
    I could figure out how to use IO in the resolver. Was this the right
    thing to do?

  2. It was not immediately clear how to create a Schema by hand.
    So I just followed the implementation you had using the quasiquoter
    in Spec.hs. Which had the undesired effect of being in a Monad when
    I think for the real use case I should be able to programmatically
    build up a Schema directly. But I didn't spend much time here.

  3. After succeeding in getting Scotty to server these queries and do some IO
    I tried to figure out how I would go about using arguments to return a different
    result when diety(name="Bob") is called. My assumption is that with this
    approach I'd have to interpret the GQLRequest directly and return
    a different (RootResModel e m). Am I on the right track or do you imagine
    a different way of doing this?

  4. The final piece of the puzzle is figure out a way to mix derived
    resolvers perhaps with the help of deriveModel with this approach.
    Is this doable? Or would I be better of deriving everything using
    the method I'm currently working with?

@nalchevanidze
Copy link

  1. I was expecting a function like interpreter to get from
    my derived resolver to a ByteString -> IO ByteString. Something
    equivalent to the examples from the main ReadME. Ideally it would be nice if interpreter TypeClass could be refactored to
    work with runApi

feel free to open PR

  1. It felt weird to have to have to declare
    resolver :: MonadIO m => RootResModel e m but that's the only way
    I could figure out how to use IO in the resolver. Was this the right
    thing to do?

yes.

MonadIO m => RootResModel e m

if you want pass IO into monad and still support custom Monad is right way to go.

  1. It was not immediately clear how to create a Schema by hand.
    So I just followed the implementation you had using the quasiquoter
    in Spec.hs. Which had the undesired effect of being in a Monad when
    I think for the real use case I should be able to programmatically
    build up a Schema directly. But I didn't spend much time here

anyway you are generating schema from swager.
but i think you should use schema validator from Morpheus.

  1. After succeeding in getting Scotty to server these queries and do some IO
    I tried to figure out how I would go about using arguments to return a different
    result when diety(name="Bob") is called. My assumption is that with this
    approach I'd have to interpret the GQLRequest directly and return
    a different (RootResModel e m). Am I on the right track or do you imagine
    a different way of doing this?

your assumption is right. you provide different ResModel for diffenet arguments.

  1. The final piece of the puzzle is figure out a way to mix derived
    resolvers perhaps with the help of deriveModel with this approach.
    Is this doable? Or would I be better of deriving everything using
    the method I'm currently working with?

don't understand what do you mean?

@srobertson
Copy link
Author

srobertson commented May 13, 2020

@nalchevanidze I think I answered my own question when I went to elaborate. Looks like what I was looking for is objectResolvers :: and possibly other Helpers ... that being said. Here's a more detailed account of what I'm trying to do:

Ideally It would be nice to blend things derived by Generics/Morpheus with the swagger gateway I'm creating

I'm building a gateway that blends data from microservices (that expose either Swagger or Graphql endpoints) with some custom logic

Imagine the gateway expose the combined schema below:

type Query {
  deity(name: String!): Deity!
  demigod(name: String!): Demigod
}

"""
Description for Deity
"""
type Deity {
  """
  Description for name
  """
  name: String!
  power: String @deprecated(reason: "some reason for")
}

type Demigod {
  name: String!
  patron: Diety
}

deity() -> Deity comes the above RootResModel done in the example I posted in this gist. Where as demigod() -> Demigod may come from code that looks like the traditional way of working with Morpheus: Something like:

data Demigod = Demigod
  { name :: Text         -- Non-Nullable Field
  , patron    ::  Text   -- Nullable Field
  } deriving (Generic,GQLType)

data DemiGodArgs = DemiGodArgs
  { name      :: Text        -- Required Argument
  } deriving (Generic)

So the question (or task for me to determine) is can I have the best of both worlds.

@nalchevanidze
Copy link

Do you mean like: https://www.apollographql.com/docs/apollo-server/federation/introduction/

Where you can derive resolvers and schema from swagger and merge with api of motpheus?

Yeah. I am considering that too.

morpheusDerivedApi <:> swaggerApi.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment