- Compares two graphQL schemas
- If schema is not set it takes actual schema from server
- Html can be used with any server as it returns data in format
{"error":null,"data":[{"breaking":true,"description":"`Mutation` type was removed"}]}
Last active
June 29, 2017 11:51
-
-
Save fokot/50656eceba00f5803472607dc40396d5 to your computer and use it in GitHub Desktop.
GraphQL schema compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8" /> | |
<style> | |
div, input, button, li { | |
font-size: large; | |
} | |
.CompareForm { | |
display: flex; | |
flex-direction: column; | |
padding: 5px; | |
} | |
.CompareForm > * { | |
margin-bottom: 10px; | |
} | |
.breaking { | |
font-weight: bold; | |
color: red; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Compare two GraphQL schemas</h1> | |
<ul> | |
<li>input graphql endpoint e.g. https://dev.merlonintelligence.com/graphql</li> | |
<li>press `Compare`</li> | |
<li>if not set, current server graphQL will be used</li> | |
<li>breaking changes are in bold</li> | |
</ul> | |
<br/> | |
<div id="react-app"></div> | |
<script src="https://cdn.jsdelivr.net/react/15.5.4/react.js"></script> | |
<script src="https://cdn.jsdelivr.net/react/15.5.4/react-dom.js"></script> | |
<script> | |
var Change = function(props){ | |
return React.createElement('li', {className: props.change.breaking ? 'breaking' : 'non-breaking'}, props.change.description) | |
}; | |
var CompareForm = (props) => | |
React.createElement('form', {className: 'CompareForm'}, | |
React.createElement('div', {}, 'Old schema'), | |
React.createElement('input', { | |
type: 'text', | |
value: props.oldSchema, | |
onChange: e => props.setOldSchema(e.target.value) | |
}), | |
React.createElement('div', {}, 'New schema'), | |
React.createElement('input', { | |
type: 'text', | |
value: props.newSchema, | |
onChange: e => props.setNewSchema(e.target.value) | |
}), | |
React.createElement('button', { type: 'submit', onClick: e => { e.preventDefault(); props.compare()} }, "Compare") | |
); | |
var CompareView = (props) => { | |
if(props.comparing) { | |
return React.createElement('h1', {}, 'Comparing...'); | |
} | |
if(props.result.error) { | |
return React.createElement('h1', {}, props.result.error); | |
} | |
if(props.result.data.length === 0) { | |
return React.createElement('h1', {}, 'Schemas are identical'); | |
} | |
return React.createElement('div', {}, | |
React.createElement('h1', {}, `${props.result.data.length} changes`), | |
React.createElement('ul', {}, props.result.data.map((c, i) => | |
React.createElement(Change, {key: i, change: c}) | |
) | |
) | |
); | |
} | |
var App = (props) => | |
React.createElement('div', {}, | |
React.createElement(CompareForm, props), | |
React.createElement(CompareView, props) | |
); | |
class MyComponent extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
result: { | |
data: [], | |
error: 'No comparison yet' | |
}, | |
comparing: false, | |
oldSchema: '', | |
newSchema: '', | |
}; | |
this.compare = this.compare.bind(this); | |
} | |
compare() { | |
this.setState({comparing: true}); | |
const url = "/schemaCompareResult"; | |
const xhr = new XMLHttpRequest(); | |
window.w = this; | |
var params = "oldSchema=" + this.state.oldSchema + "&newSchema=" + this.state.newSchema; | |
xhr.open("GET", url+"?"+params, true); | |
xhr.send(null); | |
xhr.onreadystatechange = function() {//Call a function when the state changes. | |
if(xhr.readyState === 4 && xhr.status === 200) { | |
w.setState({comparing: false, result: JSON.parse(xhr.responseText)}); | |
} else if(xhr.readyState === 4 && xhr.status !== 200) { | |
w.setState({comparing: false, result: { data: [], error: 'Calling server failed'}}); | |
} | |
} | |
} | |
render() { | |
return React.createElement('div', {}, | |
React.createElement(CompareForm, { | |
oldSchema: this.state.oldSchema, | |
newSchema: this.state.newSchema, | |
setOldSchema: (s) => this.setState({oldSchema: s}), | |
setNewSchema: (s) => this.setState({newSchema: s}), | |
compare: this.compare, | |
}), | |
React.createElement(CompareView, this.state) | |
); | |
} | |
} | |
ReactDOM.render(React.createElement(MyComponent), document.getElementById('react-app')); | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package raptorclientapi | |
import io.circe.Encoder | |
import io.circe.parser.parse | |
import sangria.schema.{Schema, SchemaChange} | |
import scalaj.http.{Http => SJHttp} | |
import sangria.marshalling.circe.CirceInputUnmarshaller | |
import io.circe._ | |
import io.circe.generic.semiauto._ | |
import cats.implicits._ | |
import scala.util.Try | |
object SchemaCompare { | |
val introspectionQuery = s"""{"query":"${sangria.introspection.introspectionQuery.source.get.lines.mkString(" ")}"}""" | |
type ParsedSchema = Either[String, Schema[_, _]] | |
def downloadAndParseSchema(url: String): ParsedSchema = | |
Try(Schema.buildFromIntrospection(parse( SJHttp(url) | |
.postData(introspectionQuery) | |
.header("content-type", "application/json") | |
.asString.body ).right.get)).toOption.toRight(s"Can't parse schema from $url") | |
case class ComparisonResult(error: Option[String], data: Vector[SchemaChange] = Vector.empty) | |
implicit val SchemaChangeEncoder: Encoder[SchemaChange] = new Encoder[SchemaChange] { | |
final def apply(c: SchemaChange): Json = Json.fromFields( | |
"breaking" -> Json.fromBoolean(c.breakingChange) :: "description" -> Json.fromString(c.description) :: Nil | |
) | |
} | |
implicit val ComparisonResultEncoder: Encoder[ComparisonResult] = deriveEncoder | |
def schemaCompare(oldSchema: Option[String], newSchema: Option[String]): ComparisonResult = (oldSchema, newSchema) match { | |
case (None, None) => ComparisonResult("No schema selected".some) | |
case (Some(o), None) => compare(downloadAndParseSchema(o), SchemaDefinition.RaptorSchema.asRight[String]) | |
case (None, Some(n)) => compare(SchemaDefinition.RaptorSchema.asRight[String], downloadAndParseSchema(n)) | |
case (Some(o), Some(n)) => compare(downloadAndParseSchema(o), downloadAndParseSchema(n)) | |
} | |
def compare(oldSchema: ParsedSchema, newSchema: ParsedSchema): ComparisonResult = { | |
val res = for( | |
o <- oldSchema; | |
n <- newSchema | |
) yield ComparisonResult(None, n compare o) | |
res.left.map(error => ComparisonResult(error.some)).merge | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment