├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── examples
├── digitransit.dot
├── digitransit.json
├── digitransit.png
└── digitransit.svg
├── project.clj
├── resources
└── introspection.query
└── src
└── graphqlviz
└── core.clj
/.gitignore:
--------------------------------------------------------------------------------
1 | deps
2 | .nrepl-port
3 | /target
4 | /classes
5 | /checkouts
6 | pom.xml
7 | pom.xml.asc
8 | *.class
9 | /.lein-*
10 | /.nrepl-port
11 | .hgignore
12 | .hg/
13 | *~
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: clojure
2 | lein: lein2
3 | script: lein2 do clean, test
4 | jdk:
5 | - openjdk6
6 | - openjdk7
7 | - oraclejdk7
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
4 |
5 | 1. DEFINITIONS
6 |
7 | "Contribution" means:
8 |
9 | a) in the case of the initial Contributor, the initial code and
10 | documentation distributed under this Agreement, and
11 |
12 | b) in the case of each subsequent Contributor:
13 |
14 | i) changes to the Program, and
15 |
16 | ii) additions to the Program;
17 |
18 | where such changes and/or additions to the Program originate from and are
19 | distributed by that particular Contributor. A Contribution 'originates' from
20 | a Contributor if it was added to the Program by such Contributor itself or
21 | anyone acting on such Contributor's behalf. Contributions do not include
22 | additions to the Program which: (i) are separate modules of software
23 | distributed in conjunction with the Program under their own license
24 | agreement, and (ii) are not derivative works of the Program.
25 |
26 | "Contributor" means any person or entity that distributes the Program.
27 |
28 | "Licensed Patents" mean patent claims licensable by a Contributor which are
29 | necessarily infringed by the use or sale of its Contribution alone or when
30 | combined with the Program.
31 |
32 | "Program" means the Contributions distributed in accordance with this
33 | Agreement.
34 |
35 | "Recipient" means anyone who receives the Program under this Agreement,
36 | including all Contributors.
37 |
38 | 2. GRANT OF RIGHTS
39 |
40 | a) Subject to the terms of this Agreement, each Contributor hereby grants
41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
42 | reproduce, prepare derivative works of, publicly display, publicly perform,
43 | distribute and sublicense the Contribution of such Contributor, if any, and
44 | such derivative works, in source code and object code form.
45 |
46 | b) Subject to the terms of this Agreement, each Contributor hereby grants
47 | Recipient a non-exclusive, worldwide, royalty-free patent license under
48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
49 | transfer the Contribution of such Contributor, if any, in source code and
50 | object code form. This patent license shall apply to the combination of the
51 | Contribution and the Program if, at the time the Contribution is added by the
52 | Contributor, such addition of the Contribution causes such combination to be
53 | covered by the Licensed Patents. The patent license shall not apply to any
54 | other combinations which include the Contribution. No hardware per se is
55 | licensed hereunder.
56 |
57 | c) Recipient understands that although each Contributor grants the licenses
58 | to its Contributions set forth herein, no assurances are provided by any
59 | Contributor that the Program does not infringe the patent or other
60 | intellectual property rights of any other entity. Each Contributor disclaims
61 | any liability to Recipient for claims brought by any other entity based on
62 | infringement of intellectual property rights or otherwise. As a condition to
63 | exercising the rights and licenses granted hereunder, each Recipient hereby
64 | assumes sole responsibility to secure any other intellectual property rights
65 | needed, if any. For example, if a third party patent license is required to
66 | allow Recipient to distribute the Program, it is Recipient's responsibility
67 | to acquire that license before distributing the Program.
68 |
69 | d) Each Contributor represents that to its knowledge it has sufficient
70 | copyright rights in its Contribution, if any, to grant the copyright license
71 | set forth in this Agreement.
72 |
73 | 3. REQUIREMENTS
74 |
75 | A Contributor may choose to distribute the Program in object code form under
76 | its own license agreement, provided that:
77 |
78 | a) it complies with the terms and conditions of this Agreement; and
79 |
80 | b) its license agreement:
81 |
82 | i) effectively disclaims on behalf of all Contributors all warranties and
83 | conditions, express and implied, including warranties or conditions of title
84 | and non-infringement, and implied warranties or conditions of merchantability
85 | and fitness for a particular purpose;
86 |
87 | ii) effectively excludes on behalf of all Contributors all liability for
88 | damages, including direct, indirect, special, incidental and consequential
89 | damages, such as lost profits;
90 |
91 | iii) states that any provisions which differ from this Agreement are offered
92 | by that Contributor alone and not by any other party; and
93 |
94 | iv) states that source code for the Program is available from such
95 | Contributor, and informs licensees how to obtain it in a reasonable manner on
96 | or through a medium customarily used for software exchange.
97 |
98 | When the Program is made available in source code form:
99 |
100 | a) it must be made available under this Agreement; and
101 |
102 | b) a copy of this Agreement must be included with each copy of the Program.
103 |
104 | Contributors may not remove or alter any copyright notices contained within
105 | the Program.
106 |
107 | Each Contributor must identify itself as the originator of its Contribution,
108 | if any, in a manner that reasonably allows subsequent Recipients to identify
109 | the originator of the Contribution.
110 |
111 | 4. COMMERCIAL DISTRIBUTION
112 |
113 | Commercial distributors of software may accept certain responsibilities with
114 | respect to end users, business partners and the like. While this license is
115 | intended to facilitate the commercial use of the Program, the Contributor who
116 | includes the Program in a commercial product offering should do so in a
117 | manner which does not create potential liability for other Contributors.
118 | Therefore, if a Contributor includes the Program in a commercial product
119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend
120 | and indemnify every other Contributor ("Indemnified Contributor") against any
121 | losses, damages and costs (collectively "Losses") arising from claims,
122 | lawsuits and other legal actions brought by a third party against the
123 | Indemnified Contributor to the extent caused by the acts or omissions of such
124 | Commercial Contributor in connection with its distribution of the Program in
125 | a commercial product offering. The obligations in this section do not apply
126 | to any claims or Losses relating to any actual or alleged intellectual
127 | property infringement. In order to qualify, an Indemnified Contributor must:
128 | a) promptly notify the Commercial Contributor in writing of such claim, and
129 | b) allow the Commercial Contributor tocontrol, and cooperate with the
130 | Commercial Contributor in, the defense and any related settlement
131 | negotiations. The Indemnified Contributor may participate in any such claim
132 | at its own expense.
133 |
134 | For example, a Contributor might include the Program in a commercial product
135 | offering, Product X. That Contributor is then a Commercial Contributor. If
136 | that Commercial Contributor then makes performance claims, or offers
137 | warranties related to Product X, those performance claims and warranties are
138 | such Commercial Contributor's responsibility alone. Under this section, the
139 | Commercial Contributor would have to defend claims against the other
140 | Contributors related to those performance claims and warranties, and if a
141 | court requires any other Contributor to pay any damages as a result, the
142 | Commercial Contributor must pay those damages.
143 |
144 | 5. NO WARRANTY
145 |
146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
151 | appropriateness of using and distributing the Program and assumes all risks
152 | associated with its exercise of rights under this Agreement , including but
153 | not limited to the risks and costs of program errors, compliance with
154 | applicable laws, damage to or loss of data, programs or equipment, and
155 | unavailability or interruption of operations.
156 |
157 | 6. DISCLAIMER OF LIABILITY
158 |
159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
166 | OF SUCH DAMAGES.
167 |
168 | 7. GENERAL
169 |
170 | If any provision of this Agreement is invalid or unenforceable under
171 | applicable law, it shall not affect the validity or enforceability of the
172 | remainder of the terms of this Agreement, and without further action by the
173 | parties hereto, such provision shall be reformed to the minimum extent
174 | necessary to make such provision valid and enforceable.
175 |
176 | If Recipient institutes patent litigation against any entity (including a
177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
178 | (excluding combinations of the Program with other software or hardware)
179 | infringes such Recipient's patent(s), then such Recipient's rights granted
180 | under Section 2(b) shall terminate as of the date such litigation is filed.
181 |
182 | All Recipient's rights under this Agreement shall terminate if it fails to
183 | comply with any of the material terms or conditions of this Agreement and
184 | does not cure such failure in a reasonable period of time after becoming
185 | aware of such noncompliance. If all Recipient's rights under this Agreement
186 | terminate, Recipient agrees to cease use and distribution of the Program as
187 | soon as reasonably practicable. However, Recipient's obligations under this
188 | Agreement and any licenses granted by Recipient relating to the Program shall
189 | continue and survive.
190 |
191 | Everyone is permitted to copy and distribute copies of this Agreement, but in
192 | order to avoid inconsistency the Agreement is copyrighted and may only be
193 | modified in the following manner. The Agreement Steward reserves the right to
194 | publish new versions (including revisions) of this Agreement from time to
195 | time. No one other than the Agreement Steward has the right to modify this
196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
197 | Eclipse Foundation may assign the responsibility to serve as the Agreement
198 | Steward to a suitable separate entity. Each new version of the Agreement will
199 | be given a distinguishing version number. The Program (including
200 | Contributions) may always be distributed subject to the version of the
201 | Agreement under which it was received. In addition, after a new version of
202 | the Agreement is published, Contributor may elect to distribute the Program
203 | (including its Contributions) under the new version. Except as expressly
204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
205 | licenses to the intellectual property of any Contributor under this
206 | Agreement, whether expressly, by implication, estoppel or otherwise. All
207 | rights in the Program not expressly granted under this Agreement are
208 | reserved.
209 |
210 | This Agreement is governed by the laws of the State of New York and the
211 | intellectual property laws of the United States of America. No party to this
212 | Agreement will bring a legal action under this Agreement more than one year
213 | after the cause of action arose. Each party waives its rights to a jury trial
214 | in any resulting litigation.
215 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GraphQLviz [](https://travis-ci.org/Macroz/GraphQLViz)
2 |
3 | GraphQLviz marries GraphQL (schemas) with Graphviz.
4 |
5 | It's a tool to show you what your schema really looks like. At the moment, it's mostly useful as a complement to [GraphiQL](https://github.com/graphql/graphiql), like printing a huge poster size version to facilitate team discussions.
6 |
7 | ## Quick Usage
8 |
9 | Requires [Graphviz](http://www.graphviz.org) and [Java Virtual Machine](http://openjdk.java.net/).
10 |
11 | There is a convenient uberjar release available to download from the [release page](https://github.com/Macroz/GraphQLviz/releases).
12 |
13 | Once you have Graphviz, Java and the uberjar, then you can use GraphQLviz like a regular Java app like so:
14 |
15 | ```
16 | java -jar graphqlviz.jar https://api.digitransit.fi/routing/v1/routers/finland/index/graphql digitransit
17 | ```
18 |
19 | [Example schema](examples/digitransit.json?raw=true) (from [Digitransit](http://digitransit.fi))
20 |
21 | 
22 |
23 | Run `java -jar graphqlviz.jar --help` for more options or read the Full Usage.
24 |
25 | ## Full Usage
26 |
27 | You must have Graphviz executable from command-line. Download and install it using your package manager or go to [Graphviz](http://www.graphviz.org).
28 |
29 | You also need [Leiningen](http://leiningen.org). Leiningen is the [Clojure](http://clojure.org) build tool that runs on the Java Virtual Machine. This means you also need Java.
30 |
31 | If you like to use Leiningen, you can run like this:
32 | ```
33 | lein run https://api.digitransit.fi/routing/v1/routers/finland/index/graphql digitransit
34 | ```
35 |
36 | There is a prepackaged jar in [Clojars](https://clojars.org/macroz/graphqlviz) so you don't have to build GraphQLviz yourself.
37 |
38 | Also there is a convenient uberjar release available to download from the [release page](https://github.com/Macroz/GraphQLviz/releases).
39 |
40 | With these JAR files you can run like this:
41 |
42 | ```
43 | java -jar graphqlviz.jar https://api.digitransit.fi/routing/v1/routers/finland/index/graphql digitransit
44 | ```
45 |
46 | If you want to use the code from Clojure, perhaps from your own program, add to your project.clj:
47 |
48 | [](http://clojars.org/macroz/graphqlviz)
49 |
50 | ### Separate introspection
51 |
52 | Instead of running against a live GraphQL server, you can use a downloaded result of an introspection query like this:
53 |
54 | ```
55 | java -jar graphqlviz.jar examples/digitransit.json digitransit
56 | ```
57 |
58 | Otherwise, in the case of a URL, the introspection query is executed against the GraphQL endpoint and the results visualized.
59 |
60 | ### Authentication
61 |
62 | Since 0.4.0, you can pass options for authenticating with the server for the introspection query (`-a` or `--auth`).
63 |
64 | The supported authentication types are `basic`, `digest` (with user and password) as well as `oauth2` (with oauth token) See also `-h` for help.
65 |
66 | ```
67 | java -jar graphqlviz.jar http://secret.example.com/graphql secret-schema-name -abasic -utester -ppassword
68 | ```
69 |
70 | Or with password prompt `-pp` or `--password-prompt` (since 0.5.0).
71 |
72 | ```
73 | java -jar graphqlviz.jar http://secret.example.com/graphql secret-schema-name -abasic -utester --password-prompt
74 | ```
75 |
76 | ## Backlog
77 |
78 | - Use dynamic graph features as in [archi](https://github.com/Macroz/archi)
79 | - Develop a complete JavaScript solution of the same idea
80 |
81 | ## License
82 |
83 | Copyright © 2015-2017 Markku Rontu
84 |
85 | Distributed under the Eclipse Public License either version 1.0 or (at
86 | your option) any later version.
87 |
--------------------------------------------------------------------------------
/examples/digitransit.dot:
--------------------------------------------------------------------------------
1 | digraph "digitransit" {
2 | graph[dpi=100, rankdir=LR, label="digitransit"]
3 | node[shape=none, margin=0]
4 | "QueryType"[label=<
>]
5 | "Node"[label=<>]
6 | "Agency"[label=<Agency
|
id: ID! |
gtfsId: String! |
name: String! |
url: String! |
timezone: String! |
lang: String |
phone: String |
fareUrl: String |
>]
7 | "Route"[label=<Route
|
id: ID! |
gtfsId: String! |
shortName: String |
longName: String |
type: String |
desc: String |
url: String |
color: String |
textColor: String |
>]
8 | "BikesAllowed"[label=<BikesAllowed «enum» |
NO_INFORMATION |
ALLOWED |
NOT_ALLOWED |
>]
9 | "Pattern"[label=<Pattern
|
id: ID! |
directionId: Int |
name: String |
code: String! |
headsign: String |
semanticHash: String |
>]
10 | "Trip"[label=<Trip
|
id: ID! |
gtfsId: String! |
serviceId: String |
tripShortName: String |
tripHeadsign: String |
routeShortName: String |
directionId: String |
blockId: String |
shapeId: String |
geometry: String |
>]
11 | "WheelchairBoarding"[label=<WheelchairBoarding «enum» |
NO_INFORMATION |
POSSIBLE |
NOT_POSSIBLE |
>]
12 | "Stop"[label=<Stop
|
id: ID! |
gtfsId: String! |
name: String! |
lat: Float! |
lon: Float! |
code: String |
desc: String |
zoneId: String |
url: String |
direction: String |
timezone: String |
vehicleType: Int |
platformCode: String |
>]
13 | "LocationType"[label=<LocationType «enum» |
STOP |
STATION |
ENTRANCE |
>]
14 | "Cluster"[label=<Cluster
|
id: ID! |
gtfsId: String! |
name: String! |
lat: Float! |
lon: Float! |
>]
15 | "stopAtDistance"[label=<stopAtDistance
|
distance: Int |
>]
16 | "StoptimesInPattern"[label=<>]
17 | "Stoptime"[label=<Stoptime
|
scheduledArrival: Int |
realtimeArrival: Int |
arrivalDelay: Int |
scheduledDeparture: Int |
realtimeDeparture: Int |
departureDelay: Int |
timepoint: Boolean |
realtime: Boolean |
serviceDay: Long |
>]
18 | "Coordinates"[label=<Coordinates
|
lat: Float |
lon: Float |
>]
19 | "stopAtDistanceConnection"[label=<>]
20 | "stopAtDistanceEdge"[label=<stopAtDistanceEdge
|
cursor: String! |
>]
21 | "PageInfo"[label=<PageInfo
|
hasNextPage: Boolean! |
hasPreviousPage: Boolean! |
startCursor: String |
endCursor: String |
>]
22 |
23 | "QueryType" -> "Node"[label="node(...)", labeltooltip="Fetches an object given its ID"]
24 | "QueryType" -> "Agency"[label="agencies", labeltooltip="Get all agencies for the specified graph"]
25 | "QueryType" -> "Agency"[label="agency(...)", labeltooltip="Get a single agency based on agency ID"]
26 | "QueryType" -> "Stop"[label="stops(...)", labeltooltip="Get all stops for the specified graph"]
27 | "QueryType" -> "Stop"[label="stopsByBbox(...)", labeltooltip="Get all stops within the specified bounding box"]
28 | "QueryType" -> "stopAtDistanceConnection"[label="stopsByRadius(...)", labeltooltip="Get all stops within the specified radius from a location. The returned type has two fields stop and distance"]
29 | "QueryType" -> "Stop"[label="stop(...)", labeltooltip="Get a single stop based on its id (format is Agency\:StopId)"]
30 | "QueryType" -> "Route"[label="routes(...)", labeltooltip="Get all routes for the specified graph"]
31 | "QueryType" -> "Route"[label="route(...)", labeltooltip="Get a single route based on its id (format is Agency\:RouteId)"]
32 | "QueryType" -> "Trip"[label="trips", labeltooltip="Get all trips for the specified graph"]
33 | "QueryType" -> "Trip"[label="trip(...)", labeltooltip="Get a single trip based on its id (format is Agency\:TripId)"]
34 | "QueryType" -> "Trip"[label="fuzzyTrip(...)", labeltooltip=""]
35 | "QueryType" -> "Pattern"[label="patterns", labeltooltip="Get all patterns for the specified graph"]
36 | "QueryType" -> "Pattern"[label="pattern(...)", labeltooltip="Get a single pattern based on its id"]
37 | "QueryType" -> "Cluster"[label="clusters", labeltooltip="Get all clusters for the specified graph"]
38 | "QueryType" -> "Cluster"[label="cluster(...)", labeltooltip="Get a single cluster based on its id"]
39 | "QueryType" -> "QueryType"[label="viewer", labeltooltip="Needed until https\://github.com/facebook/relay/issues/112 is resolved"]
40 | "Agency" -> "Route"[label="routes", labeltooltip=""]
41 | "Route" -> "Agency"[label="agency", labeltooltip=""]
42 | "Route" -> "BikesAllowed"[label="bikesAllowed", labeltooltip=""]
43 | "Route" -> "Pattern"[label="patterns", labeltooltip=""]
44 | "Route" -> "Stop"[label="stops", labeltooltip=""]
45 | "Route" -> "Trip"[label="trips", labeltooltip=""]
46 | "Pattern" -> "Route"[label="route", labeltooltip=""]
47 | "Pattern" -> "Trip"[label="trips", labeltooltip=""]
48 | "Pattern" -> "Stop"[label="stops", labeltooltip=""]
49 | "Pattern" -> "Coordinates"[label="geometry", labeltooltip=""]
50 | "Trip" -> "Route"[label="route", labeltooltip=""]
51 | "Trip" -> "WheelchairBoarding"[label="wheelchairAccessible", labeltooltip=""]
52 | "Trip" -> "BikesAllowed"[label="bikesAllowed", labeltooltip=""]
53 | "Trip" -> "Pattern"[label="pattern", labeltooltip=""]
54 | "Trip" -> "Stop"[label="stops", labeltooltip=""]
55 | "Trip" -> "Stop"[label="semanticHash", labeltooltip=""]
56 | "Trip" -> "Stoptime"[label="stoptimes", labeltooltip=""]
57 | "Trip" -> "Stoptime"[label="stoptimesForDate(...)", labeltooltip=""]
58 | "Stop" -> "LocationType"[label="locationType", labeltooltip=""]
59 | "Stop" -> "Stop"[label="parentStation", labeltooltip=""]
60 | "Stop" -> "WheelchairBoarding"[label="wheelchairBoarding", labeltooltip=""]
61 | "Stop" -> "Cluster"[label="cluster", labeltooltip=""]
62 | "Stop" -> "Route"[label="routes", labeltooltip=""]
63 | "Stop" -> "Pattern"[label="patterns", labeltooltip=""]
64 | "Stop" -> "stopAtDistance"[label="transfers", labeltooltip=""]
65 | "Stop" -> "StoptimesInPattern"[label="stoptimesForServiceDate(...)", labeltooltip=""]
66 | "Stop" -> "StoptimesInPattern"[label="stoptimesForPatterns(...)", labeltooltip=""]
67 | "Stop" -> "Stoptime"[label="stoptimesWithoutPatterns(...)", labeltooltip=""]
68 | "Cluster" -> "Stop"[label="stops", labeltooltip=""]
69 | "stopAtDistance" -> "Stop"[label="stop", labeltooltip=""]
70 | "StoptimesInPattern" -> "Pattern"[label="pattern", labeltooltip=""]
71 | "StoptimesInPattern" -> "Stoptime"[label="stoptimes", labeltooltip=""]
72 | "Stoptime" -> "Stop"[label="stop", labeltooltip=""]
73 | "Stoptime" -> "Trip"[label="trip", labeltooltip=""]
74 | "stopAtDistanceConnection" -> "stopAtDistanceEdge"[label="edges", labeltooltip=""]
75 | "stopAtDistanceConnection" -> "PageInfo"[label="pageInfo", labeltooltip=""]
76 | "stopAtDistanceEdge" -> "stopAtDistance"[label="node", labeltooltip="The item at the end of the edge"]
77 | }
78 |
--------------------------------------------------------------------------------
/examples/digitransit.json:
--------------------------------------------------------------------------------
1 | {"__schema":{"queryType":{"name":"QueryType"},"mutationType":null,"types":[{"kind":"OBJECT","name":"QueryType","description":null,"fields":[{"name":"node","description":"Fetches an object given its ID","args":[{"name":"id","description":"The ID of an object","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"defaultValue":null}],"type":{"kind":"INTERFACE","name":"Node","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"agencies","description":"Get all agencies for the specified graph","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Agency","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"agency","description":"Get a single agency based on agency ID","args":[{"name":"id","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Agency","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"stops","description":"Get all stops for the specified graph","args":[{"name":"ids","description":null,"type":{"kind":"LIST","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Stop","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stopsByBbox","description":"Get all stops within the specified bounding box","args":[{"name":"minLat","description":null,"type":{"kind":"SCALAR","name":"Float","ofType":null},"defaultValue":null},{"name":"minLon","description":null,"type":{"kind":"SCALAR","name":"Float","ofType":null},"defaultValue":null},{"name":"maxLat","description":null,"type":{"kind":"SCALAR","name":"Float","ofType":null},"defaultValue":null},{"name":"maxLon","description":null,"type":{"kind":"SCALAR","name":"Float","ofType":null},"defaultValue":null},{"name":"agency","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Stop","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stopsByRadius","description":"Get all stops within the specified radius from a location. The returned type has two fields stop and distance","args":[{"name":"lat","description":"Latitude of the location","type":{"kind":"SCALAR","name":"Float","ofType":null},"defaultValue":null},{"name":"lon","description":"Longitude of the location","type":{"kind":"SCALAR","name":"Float","ofType":null},"defaultValue":null},{"name":"radius","description":"Radius (in meters) to search for from the specidied location","type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":null},{"name":"agency","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null},{"name":"before","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null},{"name":"after","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null},{"name":"first","description":null,"type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":null},{"name":"last","description":null,"type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":null}],"type":{"kind":"OBJECT","name":"stopAtDistanceConnection","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"stop","description":"Get a single stop based on its id (format is Agency:StopId)","args":[{"name":"id","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Stop","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"routes","description":"Get all routes for the specified graph","args":[{"name":"ids","description":null,"type":{"kind":"LIST","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Route","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"route","description":"Get a single route based on its id (format is Agency:RouteId)","args":[{"name":"id","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Route","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"trips","description":"Get all trips for the specified graph","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Trip","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"trip","description":"Get a single trip based on its id (format is Agency:TripId)","args":[{"name":"id","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Trip","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"fuzzyTrip","description":null,"args":[{"name":"route","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null},{"name":"direction","description":null,"type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":null},{"name":"date","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null},{"name":"time","description":null,"type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Trip","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"patterns","description":"Get all patterns for the specified graph","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Pattern","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"pattern","description":"Get a single pattern based on its id","args":[{"name":"id","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Pattern","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"clusters","description":"Get all clusters for the specified graph","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Cluster","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"cluster","description":"Get a single cluster based on its id","args":[{"name":"id","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Cluster","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"viewer","description":"Needed until https:\/\/github.com\/facebook\/relay\/issues\/112 is resolved","args":[],"type":{"kind":"OBJECT","name":"QueryType","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"INTERFACE","name":"Node","description":"An object with an ID","fields":[{"name":"id","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Agency","ofType":null},{"kind":"OBJECT","name":"Route","ofType":null},{"kind":"OBJECT","name":"Pattern","ofType":null},{"kind":"OBJECT","name":"Trip","ofType":null},{"kind":"OBJECT","name":"Stop","ofType":null},{"kind":"OBJECT","name":"Cluster","ofType":null}]},{"kind":"SCALAR","name":"ID","description":"Built-in ID","fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Agency","description":"Agency in the graph","fields":[{"name":"id","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"gtfsId","description":"Agency id","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"url","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"timezone","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"lang","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"phone","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"fareUrl","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"routes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Route","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[{"kind":"INTERFACE","name":"Node","ofType":null}],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"String","description":"Built-in String","fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Route","description":null,"fields":[{"name":"id","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"gtfsId","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"agency","description":null,"args":[],"type":{"kind":"OBJECT","name":"Agency","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"shortName","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"longName","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"desc","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"url","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"color","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"textColor","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"bikesAllowed","description":null,"args":[],"type":{"kind":"ENUM","name":"BikesAllowed","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"patterns","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Pattern","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stops","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Stop","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"trips","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Trip","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[{"kind":"INTERFACE","name":"Node","ofType":null}],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"BikesAllowed","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"NO_INFORMATION","description":"There is no bike information for the trip.","isDeprecated":false,"deprecationReason":null},{"name":"ALLOWED","description":"The vehicle being used on this particular trip can accommodate at least one bicycle.","isDeprecated":false,"deprecationReason":null},{"name":"NOT_ALLOWED","description":"No bicycles are allowed on this trip.","isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"Pattern","description":null,"fields":[{"name":"id","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"route","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Route","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"directionId","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"code","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"headsign","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"trips","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Trip","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"stops","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Stop","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"geometry","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Coordinates","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"semanticHash","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[{"kind":"INTERFACE","name":"Node","ofType":null}],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Int","description":"Built-in Int","fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Trip","description":null,"fields":[{"name":"id","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"gtfsId","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"route","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Route","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"serviceId","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"tripShortName","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"tripHeadsign","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"routeShortName","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"directionId","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"blockId","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"shapeId","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"wheelchairAccessible","description":null,"args":[],"type":{"kind":"ENUM","name":"WheelchairBoarding","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"bikesAllowed","description":null,"args":[],"type":{"kind":"ENUM","name":"BikesAllowed","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"pattern","description":null,"args":[],"type":{"kind":"OBJECT","name":"Pattern","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"stops","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Stop"}}}},"isDeprecated":false,"deprecationReason":null},{"name":"semanticHash","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Stop"}}}},"isDeprecated":false,"deprecationReason":null},{"name":"stoptimes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Stoptime","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stoptimesForDate","description":null,"args":[{"name":"serviceDay","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Stoptime","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"geometry","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[{"kind":"INTERFACE","name":"Node","ofType":null}],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"WheelchairBoarding","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"NO_INFORMATION","description":"There is no accessibility information for the stop.","isDeprecated":false,"deprecationReason":null},{"name":"POSSIBLE","description":"At least some vehicles at this stop can be boarded by a rider in a wheelchair.","isDeprecated":false,"deprecationReason":null},{"name":"NOT_POSSIBLE","description":"Wheelchair boarding is not possible at this stop.","isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"Stop","description":null,"fields":[{"name":"id","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"gtfsId","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"lat","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Float","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"lon","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Float","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"code","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"desc","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"zoneId","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"url","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"locationType","description":null,"args":[],"type":{"kind":"ENUM","name":"LocationType","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"parentStation","description":null,"args":[],"type":{"kind":"OBJECT","name":"Stop","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"wheelchairBoarding","description":null,"args":[],"type":{"kind":"ENUM","name":"WheelchairBoarding","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"direction","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"timezone","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"vehicleType","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"platformCode","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"cluster","description":null,"args":[],"type":{"kind":"OBJECT","name":"Cluster","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"routes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Route","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"patterns","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Pattern","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"transfers","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"stopAtDistance","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stoptimesForServiceDate","description":null,"args":[{"name":"date","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"StoptimesInPattern","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stoptimesForPatterns","description":null,"args":[{"name":"startTime","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":"0"},{"name":"timeRange","description":null,"type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":"86400"},{"name":"numberOfDepartures","description":null,"type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":"5"}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"StoptimesInPattern","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stoptimesWithoutPatterns","description":null,"args":[{"name":"startTime","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":"0"},{"name":"timeRange","description":null,"type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":"86400"},{"name":"numberOfDepartures","description":null,"type":{"kind":"SCALAR","name":"Int","ofType":null},"defaultValue":"5"}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Stoptime","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[{"kind":"INTERFACE","name":"Node","ofType":null}],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Float","description":"Built-in Float","fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"LocationType","description":"Identifies whether this stop represents a stop or station.","fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"STOP","description":"A location where passengers board or disembark from a transit vehicle.","isDeprecated":false,"deprecationReason":null},{"name":"STATION","description":"A physical structure or area that contains one or more stop.","isDeprecated":false,"deprecationReason":null},{"name":"ENTRANCE","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"Cluster","description":null,"fields":[{"name":"id","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"gtfsId","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"lat","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Float","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"lon","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Float","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stops","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Stop","ofType":null}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[{"kind":"INTERFACE","name":"Node","ofType":null}],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"stopAtDistance","description":null,"fields":[{"name":"stop","description":null,"args":[],"type":{"kind":"OBJECT","name":"Stop","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"distance","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"StoptimesInPattern","description":null,"fields":[{"name":"pattern","description":null,"args":[],"type":{"kind":"OBJECT","name":"Pattern","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"stoptimes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Stoptime","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Stoptime","description":null,"fields":[{"name":"stop","description":null,"args":[],"type":{"kind":"OBJECT","name":"Stop","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"scheduledArrival","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"realtimeArrival","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"arrivalDelay","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"scheduledDeparture","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"realtimeDeparture","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"departureDelay","description":null,"args":[],"type":{"kind":"SCALAR","name":"Int","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"timepoint","description":null,"args":[],"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"realtime","description":null,"args":[],"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"serviceDay","description":null,"args":[],"type":{"kind":"SCALAR","name":"Long","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"trip","description":null,"args":[],"type":{"kind":"OBJECT","name":"Trip","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Boolean","description":"Built-in Boolean","fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Long","description":"Long type","fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Coordinates","description":null,"fields":[{"name":"lat","description":null,"args":[],"type":{"kind":"SCALAR","name":"Float","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"lon","description":null,"args":[],"type":{"kind":"SCALAR","name":"Float","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"stopAtDistanceConnection","description":"A connection to a list of items.","fields":[{"name":"edges","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"stopAtDistanceEdge","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"pageInfo","description":null,"args":[],"type":{"kind":"OBJECT","name":"PageInfo","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"stopAtDistanceEdge","description":"An edge in a connection.","fields":[{"name":"node","description":"The item at the end of the edge","args":[],"type":{"kind":"OBJECT","name":"stopAtDistance","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"cursor","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"PageInfo","description":"Information about pagination in a connection.","fields":[{"name":"hasNextPage","description":"When paginating forwards, are there more items?","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"hasPreviousPage","description":"When paginating backwards, are there more items?","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"startCursor","description":"When paginating backwards, the cursor to continue.","args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"endCursor","description":"When paginating forwards, the cursor to continue.","args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Schema","description":"A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on 'the server, as well as the entry points for query and 'mutation operations.","fields":[{"name":"types","description":"A list of all types supported by this server.","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type"}}}},"isDeprecated":false,"deprecationReason":null},{"name":"queryType","description":"The type that query operations will be rooted at.","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"mutationType","description":"If this server supports mutation, the type that mutation operations will be rooted at.","args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"directives","description":"'A list of all directives supported by this server.","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Directive"}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Type","description":null,"fields":[{"name":"kind","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__TypeKind","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"fields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":"false"}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Field","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"interfaces","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"possibleTypes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"enumValues","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":"false"}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__EnumValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"inputFields","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"ofType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__TypeKind","description":"An enum describing what kind of type a given __Type is","fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"SCALAR","description":"Indicates this type is a scalar.","isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":"Indicates this type is an object. `fields` and `interfaces` are valid fields.","isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":"Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.","isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":"Indicates this type is a union. `possibleTypes` is a valid field.","isDeprecated":false,"deprecationReason":null},{"name":"ENUM","description":"Indicates this type is an enum. `enumValues` is a valid field.","isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":"Indicates this type is an input object. `inputFields` is a valid field.","isDeprecated":false,"deprecationReason":null},{"name":"LIST","description":"Indicates this type is a list. `ofType` is a valid field.","isDeprecated":false,"deprecationReason":null},{"name":"NON_NULL","description":"Indicates this type is a non-null. `ofType` is a valid field.","isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"__Field","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue"}}}},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__InputValue","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"defaultValue","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__EnumValue","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Directive","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue"}}}},"isDeprecated":false,"deprecationReason":null},{"name":"onOperation","description":null,"args":[],"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"onFragment","description":null,"args":[],"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"onField","description":null,"args":[],"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null}],"directives":[{"name":"include","description":"Directs the executor to include this field or fragment only when the `if` argument is true","args":[{"name":"if","description":"Included when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}],"onOperation":false,"onFragment":true,"onField":true},{"name":"skip","description":"Directs the executor to skip this field or fragment when the `if`'argument is true.","args":[{"name":"skip","description":"Skipped when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}],"onOperation":false,"onFragment":true,"onField":true}]}}
--------------------------------------------------------------------------------
/examples/digitransit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Macroz/GraphQLviz/6df0713ae51d7cfea489a9183c59b42d1987ab09/examples/digitransit.png
--------------------------------------------------------------------------------
/examples/digitransit.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
597 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject macroz/graphqlviz "0.5.0"
2 | :description "Visualize GraphQL schemas using Graphviz"
3 | :url "https://github.com/Macroz/graphqlviz"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :dependencies [[org.clojure/clojure "1.8.0"]
7 | [macroz/tangle "0.1.8"]
8 | [org.clojure/data.json "0.2.6"]
9 | [clj-http "2.1.0"]
10 | [org.clojure/tools.cli "0.3.3"]]
11 | :main graphqlviz.core
12 | :aot :all
13 | )
14 |
--------------------------------------------------------------------------------
/resources/introspection.query:
--------------------------------------------------------------------------------
1 | query IntrospectionQuery {
2 | __schema {
3 | queryType { name }
4 | mutationType { name }
5 | types {
6 | ...FullType
7 | }
8 | directives {
9 | name
10 | description
11 | args {
12 | ...InputValue
13 | }
14 | onOperation
15 | onFragment
16 | onField
17 | }
18 | }
19 | }
20 |
21 | fragment FullType on __Type {
22 | kind
23 | name
24 | description
25 | fields {
26 | name
27 | description
28 | args {
29 | ...InputValue
30 | }
31 | type {
32 | ...TypeRef
33 | }
34 | isDeprecated
35 | deprecationReason
36 | }
37 | inputFields {
38 | ...InputValue
39 | }
40 | interfaces {
41 | ...TypeRef
42 | }
43 | enumValues {
44 | name
45 | description
46 | isDeprecated
47 | deprecationReason
48 | }
49 | possibleTypes {
50 | ...TypeRef
51 | }
52 | }
53 |
54 | fragment InputValue on __InputValue {
55 | name
56 | description
57 | type { ...TypeRef }
58 | defaultValue
59 | }
60 |
61 | fragment TypeRef on __Type {
62 | kind
63 | name
64 | ofType {
65 | kind
66 | name
67 | ofType {
68 | kind
69 | name
70 | ofType {
71 | kind
72 | name
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/graphqlviz/core.clj:
--------------------------------------------------------------------------------
1 | (ns graphqlviz.core
2 | (:require [clojure.data.json :as json]
3 | [tangle.core :refer :all]
4 | [clj-http.client :as http]
5 | [clojure.java.io :as io]
6 | [clojure.string :as string]
7 | [clojure.tools.cli :refer [parse-opts]])
8 | (:gen-class))
9 |
10 | ;;; GraphQL
11 |
12 | (defn internal-type? [t]
13 | (let [name (:name t)]
14 | (or (and name (.startsWith name "__"))
15 | #_(= "QueryType" name))))
16 |
17 | (defn scalar? [t]
18 | (= "SCALAR" (:kind t)))
19 |
20 | (defn enum? [t]
21 | (= "ENUM" (:kind t)))
22 |
23 | (defn enum-values [t]
24 | (:enumValues t))
25 |
26 | (defn terminal-type [t]
27 | (if (:ofType t)
28 | (recur (:ofType t))
29 | t))
30 |
31 | (defn introspection-query []
32 | {:query (slurp (io/resource "introspection.query"))})
33 |
34 |
35 |
36 | ;;; Configuration
37 |
38 | (def config (atom {:expand-args false
39 | :expand-arg-types false}))
40 |
41 |
42 | ;;; Visualization
43 |
44 | (defn relation-field? [f]
45 | (not (scalar? (terminal-type (:type f)))))
46 |
47 | (defn type->id [t]
48 | (:name t))
49 |
50 | (defn describe-field-type [t]
51 | (case (:kind t)
52 | "NON_NULL" (str (describe-field-type (:ofType t)) "!")
53 | "LIST" (str "[" (describe-field-type (:ofType t)) "]")
54 | (:name t)))
55 |
56 | (defn arg->str [a]
57 | (str (:name a)
58 | (if (:expand-arg-types @config)
59 | (str ": " (describe-field-type (:type a)))
60 | "")))
61 |
62 | (defn format-args [args]
63 | (if (:expand-args @config)
64 | (apply str (interpose ", " (map arg->str args)))
65 | "..."))
66 |
67 | (defn get-field-label [field]
68 | (let [{:keys [type name description args]} field]
69 | (str name
70 | (if-not (empty? args)
71 | (str "(" (format-args args) ")")
72 | "")
73 | ": "
74 | (describe-field-type type))))
75 |
76 | (defn field-to-edge [t field]
77 | (let [{:keys [type name description]} field]
78 | [(:name t) (:name (terminal-type type)) {:label (get-field-label field)
79 | :labeltooltip (str description)}]))
80 |
81 | (defn type->edges [t]
82 | (->> (:fields t)
83 | (filter relation-field?)
84 | (map (partial field-to-edge t))))
85 |
86 | (defn field->str [t f]
87 | (let [target (str "<" (:name t) "_" (:name f) ">")]
88 | (str (:name f) ": " (describe-field-type (:type f)))))
89 |
90 | (defn edge-matches? [e s]
91 | (>= (.indexOf (second e) s) 0))
92 |
93 | (defn relates-to? [t name]
94 | (some #(edge-matches? % name) (:edges t)))
95 |
96 | (defn connection-type? [t]
97 | (let [pointed-to-type-name (string/replace (:name t) "Connection" "")]
98 | (and (string/ends-with? (:name t) "Connection")
99 | (or (relates-to? t pointed-to-type-name)
100 | (relates-to? t "PageInfo")))))
101 |
102 | (defn edge-type? [t]
103 | (let [pointed-to-type-name (string/replace (:name t) "Edge" "")]
104 | (and (string/ends-with? (:name t) "Edge")
105 | (relates-to? t pointed-to-type-name))))
106 |
107 | (defn page-info-type? [t]
108 | (string/ends-with? (:name t) "PageInfo"))
109 |
110 | (defn stereotype [t]
111 | (cond (enum? t) "«enum»"
112 | (connection-type? t) "«connection»"
113 | (edge-type? t) "«edge»"
114 | (page-info-type? t) "«page info»"
115 | :else ""))
116 |
117 | (defn type-description [t]
118 | [[:TR [:TD {:BGCOLOR "#E535AB" :COLSPAN 2} [:FONT {:COLOR "white"} [:B (:name t)] [:BR] (stereotype t)]]]])
119 |
120 | (defn scalar-field-description [t f]
121 | [:TR [:TD {:ALIGN "left" :BORDER 0} (:name f) ": " (describe-field-type (:type f))]])
122 |
123 | (defn enum-value-description [v]
124 | [:TR [:TD {:ALIGN "left" :BORDER 0 :COLSPAN 2} (:name v)]])
125 |
126 | (defn type->descriptor [t]
127 | (let [scalar-fields (remove relation-field? (:fields t))]
128 | {:label (into [:TABLE {:CELLSPACING 0 :BORDER 1}]
129 | (concat (type-description t)
130 | (map (partial scalar-field-description t) scalar-fields)
131 | (map enum-value-description (enum-values t))))}))
132 |
133 | (defn render [nodes edges filename]
134 | (println "Generating graph from" (count nodes) "nodes and" (count edges) "edges")
135 | (let [dot (graph->dot nodes edges {:node {:shape :none :margin 0}
136 | :graph {:label filename :rankdir :LR}
137 | :directed? true
138 | :node->id type->id
139 | :node->descriptor type->descriptor})]
140 | (println "Writing DOT" (str filename ".dot"))
141 | (spit (str filename ".dot") dot)
142 | (println "Writing SVG" (str filename ".svg"))
143 | (spit (str filename ".svg") (dot->svg dot))
144 | ))
145 |
146 | (defn slurp-json [filename]
147 | (-> filename
148 | (slurp)
149 | (json/read-str :key-fn keyword)))
150 |
151 | (defn add-edge-info [node edges]
152 | (assoc node :edges edges))
153 |
154 | (defn assoc-nodes-edges [nodes edges-by-name]
155 | (vec (for [node nodes]
156 | (assoc node :edges (edges-by-name (:name node))))))
157 |
158 | (defn interesting-node? [n]
159 | (not (or (scalar? n)
160 | (page-info-type? n)
161 | (edge-type? n)
162 | (connection-type? n))))
163 |
164 | (defn interesting-edge? [e nodes-by-name]
165 | (let [from-node (first (nodes-by-name (first e)))
166 | to-node (first (nodes-by-name (second e)))]
167 | (and (interesting-node? from-node)
168 | (interesting-node? to-node))))
169 |
170 | (defn add-simplified-connection-edges [nodes edges nodes-by-name]
171 | (concat edges
172 | (mapcat (fn [[from-node-name to-node-name {:keys [label labeltooltip]} :as e]]
173 | (let [to-node (first (nodes-by-name to-node-name))]
174 | (if (connection-type? to-node)
175 | (let [real-to-node-name (string/replace (:name to-node) "Connection" "")
176 | new-edge [from-node-name real-to-node-name
177 | {:label (str label "\n«connection»")
178 | :labeltooltip labeltooltip}]]
179 | [new-edge]
180 | ))))
181 | (mapcat :edges nodes))))
182 |
183 | (defn add-synthetic-edges [nodes edges nodes-by-name]
184 | (add-simplified-connection-edges nodes edges nodes-by-name))
185 |
186 | (defn load-schema [filename]
187 | (let [schema (slurp-json filename)
188 | types (:types (:__schema (:data schema)))
189 | nodes (remove internal-type? types)
190 | edges (mapcat type->edges nodes)
191 | edges-by-name (group-by first edges)
192 | nodes (assoc-nodes-edges nodes edges-by-name)
193 | nodes-by-name (group-by :name nodes)
194 | nodes (filter interesting-node? nodes)
195 | edges (filter #(interesting-edge? % nodes-by-name) edges)
196 | edges (add-synthetic-edges nodes edges nodes-by-name)
197 | ]
198 | [nodes edges]))
199 |
200 | (defn fetch-schema [input output]
201 | (if (.startsWith input "http")
202 | (let [options (merge {:content-type "application/json"
203 | :body (json/write-str (introspection-query))}
204 | (:auth @config))]
205 | (println "Fetching schema from" input)
206 | (let [response (http/post input options)]
207 | (spit (str output ".json") (-> response
208 | (:body)
209 | (json/read-str :key-fn keyword)
210 | (json/write-str)))))
211 | (do (println "Loading schema from" input)
212 | (spit (str output ".json") (slurp input)))))
213 |
214 | (defn process-schema [output]
215 | (let [[nodes edges] (load-schema (str output ".json"))]
216 | (render nodes edges output)))
217 |
218 | (defn read-password []
219 | (String/valueOf (.readPassword (System/console) "Password:" nil)))
220 |
221 | (def cli-options
222 | [["-a" "--auth AUTH" "Type of auth: basic, digest or oauth2"
223 | :parse-fn (comp keyword string/trim)]
224 | ["-u" "--user USERNAME" "Username for authentication"]
225 | ["-p" "--password PASSWORD" "Password for authentication"]
226 | ["-pp" "--password-prompt" "Prompt password for authentication"]
227 | ["-o" "--oauth TOKEN" "oAuth2 token for authentication"]
228 | ["-h" "--help"]])
229 |
230 | (defn -main [& args]
231 | (let [parsed-options (parse-opts args cli-options)
232 | options (:options parsed-options)
233 | args (:arguments parsed-options)
234 | options (if (:password-prompt options)
235 | (assoc options :password (read-password))
236 | options)
237 | auth-config (case (:auth options)
238 | :basic {:auth {:basic-auth [(:username options) (:password options)]}}
239 | :digest {:auth {:digest-auth [(:username options) (:password options)]}}
240 | :oauth {:auth {:oauth-token (:oauth-token options)}}
241 | {})]
242 | (swap! config merge auth-config)
243 | (if (= (count args) 2)
244 | (let [input (first args)
245 | output (second args)]
246 | (fetch-schema input output)
247 | (process-schema output)
248 | (println "Done!")
249 | (shutdown-agents))
250 | (if (:help options)
251 | (do (println "Usage:")
252 | (println " graphqlviz ")
253 | (println "\nOptions:")
254 | (println (:summary parsed-options)))
255 | (println (:errors parsed-options))))))
256 |
--------------------------------------------------------------------------------