Advertisement
Guest User

Untitled

a guest
Aug 3rd, 2015
224
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.73 KB | None | 0 0
  1.  
  2. // An example for how to map a GraphQL schema on to Scala's type system.
  3. // Work in progress and still lots of unknowns but the great benefit we are after is true type safety at compile time.
  4. // Tries to mimic the original Star Wars schema found in graphql-js reference impl.
  5. // The schema below does not deal with Futures at all but it something we definitively need to support.
  6.  
  7. import scala.annotation.StaticAnnotation
  8.  
  9. // Test fixture data
  10. object Data {
  11. val luke = new Human(
  12. id = "1000",
  13. name = "Luke Skywalker",
  14. friendIds = List("1002", "1003", "2000", "2001"),
  15. appearsInIds = List(4, 5, 6),
  16. _homePlanet = Some("Tatooine")
  17. )
  18.  
  19. val vader = new Human(
  20. id = "1001",
  21. name = "Darth Vader",
  22. friendIds = List("1004"),
  23. appearsInIds = List(4, 5, 6),
  24. _homePlanet = Some("Tatooine")
  25. )
  26.  
  27. val han = new Human(
  28. id = "1002",
  29. name = "Han Solo",
  30. friendIds = List("1000", "1003", "2001"),
  31. appearsInIds = List(4, 5, 6)
  32. )
  33.  
  34. val leia = new Human(
  35. id = "1002",
  36. name = "Leia Organa",
  37. friendIds = List("1000", "1002", "2000", "2001"),
  38. appearsInIds = List(4, 5, 6),
  39. _homePlanet = Some("Alderaan")
  40. )
  41.  
  42. val tarkin = new Human(
  43. id = "1004",
  44. name = "Wilhuff Tarkin",
  45. friendIds = List("1001"),
  46. appearsInIds = List(4)
  47. )
  48.  
  49. val humanData = Map(
  50. "1000" -> luke,
  51. "1001" -> vader,
  52. "1002" -> han,
  53. "1003" -> leia,
  54. "1004" -> tarkin
  55. )
  56.  
  57. val threepio = new Droid(
  58. id = "2000",
  59. name = "C-3PO",
  60. friendIds = List("1000", "1002", "1003", "2001"),
  61. appearsInIds = List(4, 5, 6),
  62. _primaryFunction = Some("Protocol")
  63. )
  64.  
  65. val artoo = new Droid(
  66. id = "2001",
  67. name = "R2-D2",
  68. friendIds = List("1000", "1002", "1003"),
  69. appearsInIds = List(4, 5, 6),
  70. _primaryFunction = Some("Astromech")
  71. )
  72.  
  73. val droidData = Map(
  74. "2000" -> threepio,
  75. "2001" -> artoo
  76. )
  77.  
  78. /** Helper function to get a character by ID. */
  79. def getCharacter(id: String) = Option(humanData.getOrElse(id, droidData.getOrElse(id, null)))
  80.  
  81. /** Helper function to get a character by ID. */
  82. def getEpisode(id: Int) = Option(Episode.lookup(id))
  83.  
  84. /** Allows us to query for a character's friends. */
  85. def getFriends(friendsIds: List[String]): List[Character] = friendsIds.flatMap(getCharacter)
  86.  
  87. /** Allows us to query for a character's friends. */
  88. def getEpisodes(episodeIds: List[Int]) = episodeIds.flatMap(getEpisode)
  89. }
  90.  
  91. // Placeholders for now. For Scala to retain annotations at runtime, they need to be declared in Java.
  92. // Annotations can be used to express metadata needed by the GraphQL introspection.
  93. case class Interface(desc: String = "__notset") extends StaticAnnotation
  94. case class Field(desc: String = "__notset") extends StaticAnnotation
  95. case class Object(desc: String = "__notset") extends StaticAnnotation
  96.  
  97. // A NonNull annotation might be useful over Scala's Option since GraphQL can guarantee
  98. // that input are for example, non-null. Hence no need for handling it in end-user code at all.
  99. case class NonNull() extends StaticAnnotation
  100.  
  101. // Ghetto implementation of a GraphQL Enum. Consider it a placeholder.
  102. // TBD: Use Scala Enumeration or a GraphQLEnum?
  103. case class Episode(value: Any, description: String)
  104. object Episode {
  105. val NEWHOPE = Episode(4, "Released in 1977.")
  106. val EMPIRE = Episode(5, "Released in 1980.")
  107. val JEDI = Episode(6, "Released in 1983.")
  108. val lookup = Map(NEWHOPE.value -> NEWHOPE, EMPIRE.value -> EMPIRE, JEDI.value -> JEDI)
  109. }
  110.  
  111. // Example below uses Scala native data types instead of GraphQLString, GraphQLList etc.
  112. // Still something we need to figure out.
  113.  
  114. @Interface(desc="A character in the Star Wars Trilogy")
  115. trait Character {
  116. @Field(desc="The id of the character.")
  117. def id: String
  118.  
  119. @Field(desc="The name of the character.")
  120. def name: String
  121.  
  122. @Field(desc="The friends of the character, or an empty list if they have none.")
  123. def friends: List[Character]
  124.  
  125. @Field(desc="Which movies they appear in.")
  126. def appearsIn: List[Episode]
  127.  
  128. // TBD: How do handle resolveType?
  129. // In graphql-js, Character is aware of its implementors (why?).
  130. }
  131.  
  132. // Human and Droid are actual classes and instances unlike graphql-js. The reason for this is to enforce type safety.
  133. // Rather than relying on the untyped "source" parameter JS object/Scala Map,
  134. // the data required for Human/Droid to do it's job is passed to the constructor.
  135.  
  136. // It is of course a fine balance how much data you should require in the constructor.
  137. // Preferably as little a possible to avoid overfetching from underlying data sources (other services or a database).
  138. // Additional nesting of fields/objects will likely help address this problem.
  139.  
  140. @Object(desc="A humanoid creature in the Star Wars universe.")
  141. case class Human(id: String, name: String, friendIds: List[String], appearsInIds: List[Int], _homePlanet: Option[String] = None) extends Character {
  142. // Note how friends and episodes will only be resolved when asked for (by calling the function, aka the resolver)
  143. // Right now, these are fast in-memory lookups but they can be something slow, say a database query.
  144. override def friends = Data.getFriends(friendIds)
  145. override def appearsIn = Data.getEpisodes(appearsInIds)
  146.  
  147. @Field(desc="The home planet of the human, or null if unknown.")
  148. def homePlanet: String = _homePlanet.orNull
  149. }
  150.  
  151. @Object(desc="A mechanical creature in the Star Wars universe.")
  152. case class Droid(id: String, name: String, friendIds: List[String], appearsInIds: List[Int], _primaryFunction: Option[String] = None) extends Character {
  153. // Same as in Human. The reason for these being duplicated in each GraphQL object class (Human and Droid)
  154. // is that a GraphQLInterface cannot have resolve functions.
  155. override def friends = Data.getFriends(friendIds)
  156. override def appearsIn = Data.getEpisodes(appearsInIds)
  157.  
  158. @Field(desc="The primary function of the droid.")
  159. def primaryFunction: String = _primaryFunction.orNull
  160. }
  161.  
  162. @Object
  163. class Query {
  164. def hero: Character = Data.artoo
  165.  
  166. def human(@NonNull @Field("ID of the human") id: String): Human = {
  167. // Not ideal null checking, but got type erasure if matching on Option[Character].
  168. val c = Data.getCharacter(id).orNull
  169. c match {
  170. case h: Human => h
  171. case _ => throw new IllegalArgumentException("Invalid human ID given")
  172. }
  173. }
  174.  
  175. def droid(@NonNull id: String): Droid = {
  176. val c = Data.getCharacter(id).orNull
  177. c match {
  178. case d: Droid => d
  179. case _ => throw new IllegalArgumentException("Invalid droid ID given")
  180. }
  181. }
  182. }
  183.  
  184. object Test {
  185. def main(args: Array[String]) {
  186. val q = new Query()
  187. println(s"The true hero in Star Wars is ${q.hero.name}")
  188. println(s"A friend of the hero is ${q.hero.friends(0).name}")
  189. println(s"Darth Vader appears in ${q.human("1001").appearsIn}")
  190. println(s"C3-PO's primary function is ${q.droid("2000").primaryFunction}")
  191. }
  192. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement