Skip to content

Commit

Permalink
Add support for schema description strings
Browse files Browse the repository at this point in the history
  • Loading branch information
hlship committed Jul 20, 2018
1 parent 4a5b636 commit 4bb2a50
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 48 deletions.
14 changes: 9 additions & 5 deletions CHANGES.md
Expand Up @@ -4,9 +4,12 @@ Changes have started to bring Lacinia into compliance with
the [June 2018 version of the GraphQL specification](https://github.com/facebook/graphql/releases/tag/June2018).

Lacinia now supports block strings (via `"""`) in query and schema documents.
However, we have not yet implemented using block strings as documentation in schemas.

The error maps inside the `:error` key are now structured according to the spec;
In addition, descriptions are now supported inside schema documents;
a string (or block string) before an element in the schema becomes the
documentation for that element.

The error maps inside the `:error` key are now structured according to the June 2018 spec;
the top level keys are `:message`, `:locations`, and `:path`, and
`:extensions` (which contains any other keys in the error map supplied
by the field resolver).
Expand All @@ -25,9 +28,10 @@ A change to how GraphQL schema documentation is attached.
Previously, arguments were refered to as `:MyType.my_field/arg_name`
but with this release, we've changed it to `:MyType/my_field.arg_name`.

It is now possible, when parsing a schema from SDL, to document interfaces, enums,
scalars, and unions.
Previously, only objects and input objects could be documented.
It is now possible, when parsing a schema from SDL via
`com.walmartlabs.lacinia.parser.schema/parse-schema`, to
attach descriptions to interfaces, enums, scalars, and unions.
Previously, only objects and input objects could have descriptions attached.

New function `com.walmartlabs.lacinia.util/inject-resolvers` is an alternate way
to attach resolvers to a schema.
Expand Down
60 changes: 60 additions & 0 deletions dev-resources/documented-schema.sdl
@@ -0,0 +1,60 @@
{
"""
Things that have a name.
"""
interface Named {
"The unique name for the Named thing."
name: String
}

"""
File node type.
"""
enum FileNodeType {
"A standard file-system file."
FILE

"A directory that may contain other files and directories."
DIR

"A special file, such as a device."
SPECIAL
}

type DirectoryListing implements Named {
name: String
node_type: FileNodeType
}

"""
String that identifies permissions on a file or directory.
"""
scalar Permissions

"""
Directory type.
"""
type Directory implements Named {
name : String
permissions: Permissions
contents(
"""
Wildcard used for matching.
"""
match : String) : [DirectoryListing]
}

type File implements Named {
name : String
}

"Stuff that can appear on the file system"
union FileSystemEntry = File | Directory

type Query {
file(path : String) : FileSystemEntry
}

}


25 changes: 15 additions & 10 deletions docs/schema/parsing.rst
@@ -1,20 +1,13 @@
GraphQL SDL Parsing
===================

.. important::
The GraphQL Schema Definition Language is not yet a formal specification
and is still under development. As such, this part of Lacinia will continue
to evolve to keep up with new developments, and it's possible that breaking
changes will occur.
.. sidebar:: GraphQL Spec

.. sidebar:: GraphQL SDL

See the `RFC PR <https://github.com/facebook/graphql/pull/90>`_ for details
on the GraphQL Schema Definition Language.
Read about :spec:`the Schema Definition Language <Schema>`.

As noted in the :doc:`overview <../overview>`, Lacinia schemas are represented as
Clojure data. However, Lacinia also contains a facility to transform schemas
written in the GraphQL Interface Definition Language into the form usable by Lacinia.
written in the GraphQL Schema Definition Language into the form usable by Lacinia.
This is exposed by the function ``com.walmartlabs.lacinia.parser.schema/parse-schema``.

The Lacinia schema definition includes things which are not available in the SDL, such as
Expand Down Expand Up @@ -60,6 +53,14 @@ The ``:documentation`` key uses a naming convention on the keys which become pat
``:Query/find_all_in_episode.episode`` applies to the ``episode`` argument, inside the ``find_all_in_episode`` field
of the ``Query`` object.

.. tip::

Attaching documentation this way is less necessary since release 0.29.0, which added support for
embedded :spec:`schema documentation <Descriptions>`.

Alternately, the documentation map can be parsed from a Markdown file using
``com.walmartlabs.lacinia.parser.docs/parse-docs``.

The same key structure can be used to document input objects and interfaces.

Unions may be documented, but do not contain fields.
Expand All @@ -71,3 +72,7 @@ are defined on ordinary schema objects, and the ``schema`` element identifies wh
which purposes.

The ``:roots`` map inside the Lacinia schema is equivalent to the ``schema`` element in the SDL.

.. warning::

:spec:`Schema extensions <Schema-Extension>` are defined in the GraphQL specification, but not yet implemented.
29 changes: 21 additions & 8 deletions resources/com/walmartlabs/lacinia/schema.g4
Expand Up @@ -4,6 +4,11 @@ graphqlSchema
: '{' (schemaDef|typeDef|inputTypeDef|unionDef|enumDef|interfaceDef|scalarDef)* '}'
;

description
: StringValue
| BlockStringValue
;

schemaDef
: 'schema' '{' operationTypeDef+ '}'
;
Expand All @@ -27,7 +32,7 @@ subscriptionOperationDef
;

typeDef
: 'type' typeName implementationDef? fieldDefs
: description? 'type' typeName implementationDef? fieldDefs
;

fieldDefs
Expand All @@ -40,35 +45,43 @@ implementationDef
;

inputTypeDef
: 'input' typeName fieldDefs
: description? 'input' typeName fieldDefs
;

interfaceDef
: 'interface' typeName fieldDefs
: description? 'interface' typeName fieldDefs
;

scalarDef
: 'scalar' typeName
: description? 'scalar' typeName
;

unionDef
: 'union' typeName '=' unionTypes
: description? 'union' typeName '=' unionTypes
;

unionTypes
: (typeName '|')* typeName
;

enumDef
: 'enum' typeName '{' scalarName+ '}'
: description? 'enum' typeName enumValueDefs
;

enumValueDefs
: '{' enumValueDef+ '}'
;

enumValueDef
: description? scalarName
;

scalarName
: Name
;

fieldDef
: fieldName fieldArgs? ':' typeSpec
: description? fieldName fieldArgs? ':' typeSpec
;

fieldArgs
Expand All @@ -80,7 +93,7 @@ fieldName
;

argument
: Name ':' typeSpec defaultValue?
: description? Name ':' typeSpec defaultValue?
;

typeSpec
Expand Down
84 changes: 59 additions & 25 deletions src/com/walmartlabs/lacinia/parser/schema.clj
Expand Up @@ -106,6 +106,11 @@
(-> prod second xform))


(defn ^:private apply-description
[parsed descripion-prod]
(cond-> parsed
descripion-prod (assoc :description (xform descripion-prod))))

(defmethod xform :schemaDef
[prod]
[[:roots] (checked-map "schema entry" (map xform (drop 2 prod)))])
Expand Down Expand Up @@ -142,34 +147,44 @@
[prod]
(-> prod second keyword))

(defmethod xform :description
[prod]
(xform-second prod))

(defmethod xform :typeDef
[prod]
(let [{:keys [typeName implementationDef fieldDefs]} (tag prod)]
(let [{:keys [typeName implementationDef fieldDefs description]} (tag prod)]
[[:objects (xform typeName)]
(cond-> (common/copy-meta {:fields (xform fieldDefs)} typeName)
implementationDef (assoc :implements (xform implementationDef)))]))
(-> {:fields (xform fieldDefs)}
(common/copy-meta typeName)
(apply-description description)
(cond-> implementationDef (assoc :implements (xform implementationDef))))]))

(defmethod xform :fieldDefs
[prod]
(checked-map "field" (map xform (rest prod))))

(defmethod xform :fieldDef
[prod]
(let [{:keys [fieldName typeSpec fieldArgs]} (tag prod)]
(let [{:keys [fieldName typeSpec fieldArgs description]} (tag prod)]
[(xform fieldName)
(cond-> (common/copy-meta {:type (xform typeSpec)} fieldName)
fieldArgs (assoc :args (xform fieldArgs)))]))
(-> {:type (xform typeSpec)}
(common/copy-meta fieldName)
(apply-description description)
(cond-> fieldArgs (assoc :args (xform fieldArgs))))]))

(defmethod xform :fieldArgs
[prod]
(checked-map "field argument" (map xform (rest prod))))

(defmethod xform :argument
[prod]
(let [{:keys [name typeSpec defaultValue]} (tag prod)]
(let [{:keys [name typeSpec defaultValue description]} (tag prod)]
[(xform name)
(cond-> (common/copy-meta {:type (xform typeSpec)} name)
defaultValue (assoc :default-value (xform-second defaultValue)))]))
(-> {:type (xform typeSpec)}
(common/copy-meta name)
(apply-description description)
(cond-> defaultValue (assoc :default-value (xform-second defaultValue))))]))

(defmethod xform :value
[prod]
Expand Down Expand Up @@ -214,15 +229,19 @@

(defmethod xform :interfaceDef
[prod]
(let [[_ _ type fieldDefs] prod]
[[:interfaces (xform type)]
(common/copy-meta {:fields (xform fieldDefs)} type)]))
(let [{:keys [typeName fieldDefs description]} (tag prod)]
[[:interfaces (xform typeName)]
(-> {:fields (xform fieldDefs)}
(common/copy-meta typeName)
(apply-description description))]))

(defmethod xform :unionDef
[prod]
(let [[_ _ type unionTypes] prod]
[[:unions (xform type)]
(common/copy-meta {:members (xform unionTypes)} type)]))
(let [{:keys [description typeName unionTypes]} (tag prod)]
[[:unions (xform typeName)]
(-> {:members (xform unionTypes)}
(common/copy-meta typeName)
(apply-description description))]))

(defmethod xform :unionTypes
[prod]
Expand All @@ -233,31 +252,46 @@

(defmethod xform :enumDef
[prod]
(let [[_ _ type & enumValues] prod]
[[:enums (xform type)]
{:values (mapv (fn [prod]
(common/copy-meta {:enum-value (xform prod)} prod))
enumValues)}]))
(let [{:keys [description typeName enumValueDefs]} (tag prod)]
[[:enums (xform typeName)]
(-> {:values (xform enumValueDefs)}
(common/copy-meta typeName)
(apply-description description))]))

(defmethod xform :enumValueDefs
[prod]
(mapv xform (rest prod)))

(defmethod xform :enumValueDef
[prod]
(let [{:keys [description scalarName]} (tag prod)]
(-> {:enum-value (xform scalarName)}
(common/copy-meta scalarName)
(apply-description description))))

(defmethod xform :scalarName
[prod]
(xform-second prod))

(defmethod xform :inputTypeDef
[prod]
(let [{:keys [typeName fieldDefs]} (tag prod)]
(let [{:keys [typeName fieldDefs description]} (tag prod)]
[[:input-objects (xform typeName)]
(common/copy-meta {:fields (xform fieldDefs)} typeName)]))
(-> {:fields (xform fieldDefs)}
(common/copy-meta typeName)
(apply-description description))]))

(defmethod xform :scalarDef
[prod]
(let [[_ _ typeName] prod]
(let [{:keys [typeName description]} (tag prod)]
[[:scalars (xform typeName)]
(common/copy-meta {} typeName)]))
(-> {}
(common/copy-meta typeName)
(apply-description description))]))

(defmethod xform :objectValue
[prod]
(-> (map xform (rest prod))
(-> (mapv xform (rest prod))
(as-> % (checked-map "object key" %))
(common/copy-meta prod)))

Expand Down

0 comments on commit 4bb2a50

Please sign in to comment.