Skip to content

A simple GraphQL client on top of Sangria, Akka HTTP and Circe.

License

Notifications You must be signed in to change notification settings

Jarlakxen/drunk

Repository files navigation

Drunk

A simple GraphQL client on top ofSangria,Akka HTTPandCirce.

Quickstart

Add the following dependency:

resolvers+=Resolver.bintrayRepo("jarlakxen","maven")

"com.github.jarlakxen"%%"drunk"%"2.5.0"

Then, import:

importcom.github.jarlakxen.drunk._
importio.circe._,io.circe.generic.semiauto._
importsangria.macros._

There are three ways to create aGraphQLClient:

  1. As Akka Https flow connection
import akka.http.scaladsl.model.Uri

val uri: Uri = Uri(s "https://$host:$port/api/graphql" )

val http: HttpExt = Http()
val flow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] = http.outgoingConnectionHttps(uri.authority.host.address(), uri.effectivePort)
val client = GraphQLClient(uri, flow, clientOptions = ClientOptions.Default, headers = Nil)

  1. As Akka Http flow connection
import akka.http.scaladsl.model.Uri

val uri: Uri = Uri(s "http://$host:$port/api/graphql" )

val http: HttpExt = Http()
val flow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] = http.outgoingConnection(uri.authority.host.address(), uri.effectivePort)
val client = GraphQLClient(uri, flow, clientOptions = ClientOptions.Default, headers = Nil)

  1. As Akka Http single request
val client = GraphQLClient(s "http://$host:$port/api/graphql" )

Then, query:

val query =
graphql "" "
query HeroAndFriends {
hero {
id
name
friends {
name
}
appearsIn
}
}
"""

val cursor: GraphQLCursor = client.query[HeroQuery](query)
val data: Future[GraphQLResponse[HeroQuery]] = cursor.result

Working with theGraphQLCursor

typeHerosQuery=Map[String,List[Hero]]

caseclassPagination(offset:Int,size:Int)

valquery=
graphql"""
query Heros($offset:Int,$size:Int) {
heros(offset:$offset,size:$size) {
id
name
friends {
name
}
appearsIn
}
}
"""

valpage1:GraphQLCursor[HerosQuery,Pagination]=
client.query(query,Pagination(0,10))
valpage2:GraphQLCursor[HerosQuery,Pagination]=
page1.fetchMore(lastPage=>lastPage.copy(offset=lastPage.offset+lastPage.size ) )

Mutations

importcom.github.jarlakxen.drunk._
importio.circe._,io.circe.generic.semiauto._
importsangria.macros._

caseclassUser(id:String,name:String)

valclient=GraphQLClient(s"http://$host:$port/api/graphql")

valmutation=
graphql"""
mutation($user1:String!,$user2:String!) {
user1: newUser(name:$user1) {
id
name
}
user2: newUser(name:$user2) {
id
name
}
}
"""
valresult:Future[GraphQLResponse[Map[String,User]]]=
client.query(mutation,Map("user1"->"123","user2"->"456"))

Schema Introspection

importcom.github.jarlakxen.drunk._
importsangria.introspection.IntrospectionSchema

valclient=GraphQLClient(s"http://$host:$port/api/graphql")

valresult:Future[GraphQLResponse[IntrospectionSchema]]=client.schema

Typename Derive

It's very common in GraphQL to have response with polymorphic objects in the responses. One way to discriminate the type of object is to check the__typenamefield. For that purpose there an special derive decoder incom.github.jarlakxen.drunk.circe._:

importcom.github.jarlakxen.drunk.circe._
importio.circe._,io.circe.generic.semiauto._

traitCharacter{
defid:String
defname:Option[String]
deffriends:List[String]
defappearsIn:List[Episode.Value]
}

caseclassHuman(
id:String,
name:Option[String],
friends:List[String],
appearsIn:List[Episode.Value],
homePlanet:Option[String])extendsCharacter

caseclassDroid(
id:String,
name:Option[String],
friends:List[String],
appearsIn:List[Episode.Value],
primaryFunction:Option[String])extendsCharacter

implicitvalhumanDecoder:Decoder[Human]=deriveDecoder
implicitvaldroidDecoder:Decoder[Droid]=deriveDecoder

implicitvalcharacterDecoder:Decoder[Character]=deriveByTypenameDecoder(
"Human".decodeAs[Human],//for __typename: 'Human' is going to use humanDecoder
"Droid".decodeAs[Droid]//for __typename: 'Droid' is going to use droidDecoder
)

This code can be use to parse a response like:

{
"__typename":"Droid"
"name":"R2D2"
....
}

Take into account the client automatically adds the '__typename' field to every selector, so it's not required to be added in the queries.

Extensions

There are several extension for GraphQL, this client supports:

To get the information of the extensions you can:

valcursor:GraphQLCursor=client.query[HeroQuery](query)
valextensions:Future[GraphQLExtensions]=cursor.extensions
valmetrics:Future[Option[GraphQLMetricsExtension]]=extensions.map(_.metrics)
valcacheControl:Future[Option[GraphQLCacheControlExtension]]=extensions.map(_.cacheControl)

Contributing

If you have a question, or hit a problem, feel free to ask in theissues!

Or, if you encounter a bug, something is unclear in the code or documentation, don’t hesitate and open an issue.