Skip to content

Commit

Permalink
Refactor GraphQLSchemaPlugin into two separate plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
muuki88 committed Jan 13, 2018
1 parent 1f8a6bb commit 8b1b1b0
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 353 deletions.
18 changes: 5 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ Your build can contain multiple schemas. They are stored in the `graphqlSchemas`
This allows to compare arbitrary schemas, write schema.json files for each of them and validate
your queries against them.

There are already two schemas predefined. The `build` schema and the `prod` schema.
There is already one schemas predefined. The `build` schema is defined by the `graphqlSchemaGen` task.
You can configure the `graphqlSchemas` label with

* The `build` schema is defined by the `graphqlSchemaGen` task.
* The `prod` schema is defined by the `graphqlProductionSchema`.
```sbt
name in graphqlSchemaGen := "local-build"
```

### Add a schema

Expand All @@ -86,16 +88,6 @@ Schemas are defined via a `GraphQLSchema` case class. You need to define
* a `description`. Explain where this schema comes from and what it represents
* a `schemaTask`. A sbt task that generates the schema

This is how the `prod` schema is defined.

```scala
graphqlSchemas += GraphQLSchema(
GraphQLSchemaLabels.PROD,
"schema generated by the graphqlProductionSchema task",
graphqlProductionSchema.taskValue
)
```

You can also define a schema from a `SchemaLoader`. This requires defining an anonymous sbt task.

```scala
Expand Down
30 changes: 15 additions & 15 deletions src/main/scala/rocks/muki/graphql/GraphQLCodegenPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ object GraphQLCodegenPlugin extends AutoPlugin {
excludeFilter in graphqlCodegen := HiddenFileFilter,
graphqlCodegenQueries := Defaults
.collectFiles(resourceDirectories in graphqlCodegen,
includeFilter in graphqlCodegen,
excludeFilter in graphqlCodegen)
includeFilter in graphqlCodegen,
excludeFilter in graphqlCodegen)
.value,
sourceGenerators in Compile += Def.task { Seq(graphqlCodegen.value) },
graphqlCodegenPackage := "graphql.codegen",
Expand All @@ -44,23 +44,23 @@ object GraphQLCodegenPlugin extends AutoPlugin {
log.info(s"Use schema $schema for query validation")

val builder =
if (schema.getName.endsWith(".json"))
Builder(SchemaLoader.fromFile(schema).loadSchema())
else
Builder(schema)
if (schema.getName.endsWith(".json"))
Builder(SchemaLoader.fromFile(schema).loadSchema())
else
Builder(schema)

val result = builder
.withQuery(queries: _*)
.generate(generator)
.map { code =>
IO.createDirectory(output.getParentFile)
IO.writeLines(output,
List(s"package $packageName", code.show[Syntax]))
}
.withQuery(queries: _*)
.generate(generator)
.map { code =>
IO.createDirectory(output.getParentFile)
IO.writeLines(output,
List(s"package $packageName", code.show[Syntax]))
}

result match {
case Left(error) => sys.error(s"Failed to generate code: $error")
case Right(()) => output
case Left(error) => sys.error(s"Failed to generate code: $error")
case Right(()) => output
}
}
)
Expand Down
75 changes: 75 additions & 0 deletions src/main/scala/rocks/muki/graphql/GraphQLPlugin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package rocks.muki.graphql

import sbt.Keys._
import sbt._
import rocks.muki.graphql.schema.{GraphQLSchemas, SchemaLoader}

/**
* == GraphQL Plugin ==
*
* Root plugin for all other graphql plugins. Provides a schema registry that can be used for
*
* - validating queries against a specific schema
* - comparing schemas
* - code generation based on a specific schema
*
*/
object GraphQLPlugin extends AutoPlugin {

object autoImport {

/**
* Helper to load schemas from different places
*/
val GraphQLSchemaLoader: SchemaLoader.type =
rocks.muki.graphql.schema.SchemaLoader

val GraphQLSchema: rocks.muki.graphql.schema.GraphQLSchema.type =
rocks.muki.graphql.schema.GraphQLSchema

/**
* Contains all schemas available in this build.
*
* @example Adding a new schema
* {{{
* graphqlSchemas += GraphQLSchema(
* "temporary",
* "schema loaded from schema.json in the base directory",
* SchemaLoader.fromFile(baseDirectory.value / "schema.json")),
* }}}
*
*/
val graphqlSchemas: SettingKey[GraphQLSchemas] =
settingKey[GraphQLSchemas]("all schemas available in this build")

/**
* Renders the given schema into a graphql file.
* The input is the label in the graphqlSchemas setting.
*/
val graphqlRenderSchema: InputKey[File] =
inputKey[File]("renders the given schema to a graphql file")

}
import autoImport._

override def projectSettings: Seq[Setting[_]] = Seq(
graphqlSchemas := GraphQLSchemas(),
// schema rendering
target in graphqlRenderSchema := (target in Compile).value / "graphql",
graphqlRenderSchema := graphqlRenderSchemaTask.evaluated
)

private val graphqlRenderSchemaTask = Def.inputTaskDyn[File] {
val log = streams.value.log
val schemaDefinition = singleGraphQLSchemaParser.parsed
val file = (target in graphqlRenderSchema).value / s"${schemaDefinition.label}.graphql"
log.info(s"Rendering schema to: ${file.getPath}")

Def.task {
val schema = schemaDefinition.schemaTask.value
IO.write(file, schema.renderPretty)
file
}
}

}
93 changes: 51 additions & 42 deletions src/main/scala/rocks/muki/graphql/GraphQLQueryPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,58 +17,67 @@ object GraphQLQueryPlugin extends AutoPlugin {
*/
val graphqlValidateQueries: TaskKey[Unit] =
taskKey[Unit]("validate all queries in the graphql source directory")

val graphqlQueryDirectory: SettingKey[File] =
settingKey[File]("graphql files")
}

import autoImport._
import GraphQLSchemaPlugin.autoImport._

override def projectSettings: Seq[Setting[_]] = Seq(
sourceDirectory in (Compile, graphqlValidateQueries) := (sourceDirectory in Compile).value / "graphql",
graphqlValidateQueries := {
val log = streams.value.log
val schemaFile = IO.read(graphqlSchemaGen.value)
val schemaDocument = QueryParser
.parse(schemaFile)
.getOrElse(
sys.error(
"Invalid graphql schema generated by `graphqlSchemaGen` task")
)
val schema = Schema.buildFromAst(schemaDocument)
// TODO separate these into two auto plugins
override def projectSettings: Seq[Setting[_]] =
pluginSettings(Compile) ++ pluginSettings(IntegrationTest)

val src = (sourceDirectory in (Compile, graphqlValidateQueries)).value
val graphqlFiles = (src ** "*.graphql").get
val violations = graphqlFiles.flatMap {
file =>
log.info(s"Validate ${file.getPath}")
val query = IO.read(file)
val violations = QueryParser
.parse(query)
.fold(
error => Vector(InvalidQueryValidation(error)),
query => QueryValidator.default.validateQuery(schema, query)
private def pluginSettings(config: Configuration): Seq[Setting[_]] =
inConfig(config)(
Seq(
graphqlQueryDirectory := (sourceDirectory in Compile).value / "graphql",
graphqlValidateQueries := {
val log = streams.value.log
val schemaFile = IO.read(graphqlSchemaGen.value)
val schemaDocument = QueryParser
.parse(schemaFile)
.getOrElse(
sys.error(
"Invalid graphql schema generated by `graphqlSchemaGen` task")
)
if (violations.nonEmpty) {
log.error(s"File: ${file.getAbsolutePath}")
log.error("## Query ##")
log.error(query)
log.error("## Violations ##")
violations.foreach(v => log.error(v.errorMessage))
List(QueryViolations(file, query, violations))
} else {
Nil
val schema = Schema.buildFromAst(schemaDocument)

log.info(s"Checking graphl files in ${graphqlQueryDirectory.value}")
val graphqlFiles = (graphqlQueryDirectory.value ** "*.graphql").get
val violations = graphqlFiles.flatMap {
file =>
log.info(s"Validate ${file.getPath}")
val query = IO.read(file)
val violations = QueryParser
.parse(query)
.fold(
error => Vector(InvalidQueryValidation(error)),
query => QueryValidator.default.validateQuery(schema, query)
)
if (violations.nonEmpty) {
log.error(s"File: ${file.getAbsolutePath}")
log.error("## Query ##")
log.error(query)
log.error("## Violations ##")
violations.foreach(v => log.error(v.errorMessage))
List(QueryViolations(file, query, violations))
} else {
Nil
}
}
}

if (violations.nonEmpty) {
log.error("Validation errors in")
violations.foreach { queryViolation =>
log.error(s"File: ${queryViolation.file.getAbsolutePath}")
if (violations.nonEmpty) {
log.error("Validation errors in")
violations.foreach { queryViolation =>
log.error(s"File: ${queryViolation.file.getAbsolutePath}")
}
quietError("Some queries contain validation violations")
}
log.success(s"All ${graphqlFiles.size} graphql files are valid")
}
quietError("Some queries contain validation violations")
}
log.success(s"All ${graphqlFiles.size} graphql files are valid")
}
)
))

/**
* Aggregates violations for a single file
Expand Down
Loading

0 comments on commit 8b1b1b0

Please sign in to comment.