initial commit
This commit is contained in:
commit
343020f450
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/dist
|
||||||
|
/elm-stuff
|
||||||
|
/node_modules
|
28
elm.json
Normal file
28
elm.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"type": "application",
|
||||||
|
"source-directories": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"elm-version": "0.19.1",
|
||||||
|
"dependencies": {
|
||||||
|
"direct": {
|
||||||
|
"elm/browser": "1.0.2",
|
||||||
|
"elm/core": "1.0.5",
|
||||||
|
"elm/html": "1.0.0",
|
||||||
|
"elm/http": "2.0.0",
|
||||||
|
"elm/json": "1.1.3",
|
||||||
|
"elm/url": "1.0.0",
|
||||||
|
"wittjosiah/elm-ordered-dict": "1.0.1"
|
||||||
|
},
|
||||||
|
"indirect": {
|
||||||
|
"elm/bytes": "1.0.8",
|
||||||
|
"elm/file": "1.0.5",
|
||||||
|
"elm/time": "1.0.0",
|
||||||
|
"elm/virtual-dom": "1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-dependencies": {
|
||||||
|
"direct": {},
|
||||||
|
"indirect": {}
|
||||||
|
}
|
||||||
|
}
|
24
makefile
Normal file
24
makefile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
HOST ?= localhost
|
||||||
|
|
||||||
|
.PHONY: build dev watch serve-and-watch
|
||||||
|
build: node_modules dist/index.html dist/styles.css dist/main.js
|
||||||
|
|
||||||
|
node_modules: package.json yarn.lock ; yarn install
|
||||||
|
|
||||||
|
dev: build node_modules
|
||||||
|
./node_modules/.bin/elm-live src/Main.elm --host="${HOST}" --dir=dist --hot --start-page=index.html -- --debug --output=dist/main.js
|
||||||
|
|
||||||
|
reactor: node_modules ; yarn elm reactor
|
||||||
|
watch: node_modules ; fswi "make build" src
|
||||||
|
|
||||||
|
dist/main.js: node_modules dist src/Main.elm
|
||||||
|
yarn elm make --output=dist/main.js src/Main.elm
|
||||||
|
|
||||||
|
dist/index.html: dist src/index.html
|
||||||
|
cp src/index.html dist/
|
||||||
|
|
||||||
|
dist/styles.css: node_modules dist src/styles.styl
|
||||||
|
yarn stylus src/styles.styl -o dist/
|
||||||
|
|
||||||
|
dist: ; mkdir -p dist/
|
||||||
|
clean: ; rm -rf dist/
|
7
package.json
Normal file
7
package.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"elm": "^0.19.1-3",
|
||||||
|
"elm-live": "^4.0.2",
|
||||||
|
"stylus": "^0.54.7"
|
||||||
|
}
|
||||||
|
}
|
341
src/Main.elm
Normal file
341
src/Main.elm
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
module Main exposing (main)
|
||||||
|
|
||||||
|
import Browser
|
||||||
|
import Dict
|
||||||
|
import Html as H exposing (Attribute, Html)
|
||||||
|
import Html.Attributes as A
|
||||||
|
import Html.Events as Ev
|
||||||
|
import Http
|
||||||
|
import Json.Decode as J exposing (Decoder)
|
||||||
|
import Json.Encode as Encode
|
||||||
|
import OrderedDict exposing (OrderedDict)
|
||||||
|
import Url
|
||||||
|
|
||||||
|
|
||||||
|
main =
|
||||||
|
Browser.element
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, subscriptions = \_ -> Sub.none
|
||||||
|
, view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias ArgType =
|
||||||
|
{ kind : String
|
||||||
|
, name : Maybe String
|
||||||
|
, ofType : OfArgType
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias FieldType =
|
||||||
|
ArgType
|
||||||
|
|
||||||
|
|
||||||
|
type OfArgType
|
||||||
|
= OfArgType (Maybe ArgType)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Arg =
|
||||||
|
{ defaultValue : J.Value
|
||||||
|
, description : Maybe String
|
||||||
|
, name : String
|
||||||
|
, argType : ArgType
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Directive =
|
||||||
|
{ args : List Arg
|
||||||
|
, locations : Maybe (List String)
|
||||||
|
, description : Maybe String
|
||||||
|
, name : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Field =
|
||||||
|
{ name : String
|
||||||
|
, description : Maybe String
|
||||||
|
, args : List Arg
|
||||||
|
, fieldType : FieldType
|
||||||
|
, isDeprecated : Bool
|
||||||
|
, deprecationReason : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Interface =
|
||||||
|
J.Value
|
||||||
|
|
||||||
|
|
||||||
|
type alias EnumValue =
|
||||||
|
J.Value
|
||||||
|
|
||||||
|
|
||||||
|
type alias PossibleType =
|
||||||
|
ArgType
|
||||||
|
|
||||||
|
|
||||||
|
type alias QLType =
|
||||||
|
{ kind : String
|
||||||
|
, name : String
|
||||||
|
, description : Maybe String
|
||||||
|
, fields : Maybe (List Field)
|
||||||
|
, inputFields : Maybe (List Field)
|
||||||
|
, interfaces : Maybe (List Interface)
|
||||||
|
, enumValues : Maybe (List EnumValue)
|
||||||
|
, possibleTypes : Maybe (List PossibleType)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Introspection =
|
||||||
|
{ directives : List Directive
|
||||||
|
, mutationType : Maybe String
|
||||||
|
, queryType : Maybe String
|
||||||
|
, subscriptionType : Maybe String
|
||||||
|
, types : List QLType
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type IntrospectionResult
|
||||||
|
= Loading
|
||||||
|
| Error Http.Error
|
||||||
|
| Success Introspection
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ introspectUrl : String
|
||||||
|
, introspections : OrderedDict String IntrospectionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init : () -> ( Model, Cmd Msg )
|
||||||
|
init _ =
|
||||||
|
( Model "https://www.graphqlhub.com/graphql" OrderedDict.empty, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= UpdateIntrospectUrl String
|
||||||
|
| RequestIntrospection
|
||||||
|
| IntrospectionRequest String (Result Http.Error Introspection)
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
UpdateIntrospectUrl url ->
|
||||||
|
( { model | introspectUrl = url }, Cmd.none )
|
||||||
|
|
||||||
|
RequestIntrospection ->
|
||||||
|
( { model | introspections = OrderedDict.insert model.introspectUrl Loading model.introspections }, requestIntrospection model.introspectUrl )
|
||||||
|
|
||||||
|
IntrospectionRequest url result ->
|
||||||
|
let
|
||||||
|
val =
|
||||||
|
case result of
|
||||||
|
Ok i ->
|
||||||
|
Success i
|
||||||
|
|
||||||
|
Err e ->
|
||||||
|
Error e
|
||||||
|
in
|
||||||
|
( { model | introspections = OrderedDict.insert url val model.introspections }, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
requestIntrospection : String -> Cmd Msg
|
||||||
|
requestIntrospection url =
|
||||||
|
Http.post
|
||||||
|
{ url = url
|
||||||
|
, body = introspectionQuery
|
||||||
|
, expect = Http.expectJson (IntrospectionRequest url) decodeIntrospection
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> Html Msg
|
||||||
|
view model =
|
||||||
|
H.div [ A.id "app" ]
|
||||||
|
[ header model
|
||||||
|
, H.main_
|
||||||
|
[]
|
||||||
|
[ H.ul []
|
||||||
|
(List.filterMap
|
||||||
|
(\u -> introspectionResultView u (Dict.get u model.introspections.dict))
|
||||||
|
model.introspections.order
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
header : Model -> Html Msg
|
||||||
|
header model =
|
||||||
|
H.header (styleGroup [ ( "display", "flex" ), ( "align-items", "center" ) ])
|
||||||
|
[ H.h1 (styleGroup [ ( "font-size", "inherit" ), ( "padding", "0.5em" ), ( "width", "100%" ), ( "white-space", "nowrap" ), ( "flex", "1" ) ]) [ H.text "GraphQL Introspector" ]
|
||||||
|
, H.input
|
||||||
|
(styleGroup [ ( "width", "100%" ), ( "padding", "0.5em" ), ( "margin", "0.25em 0 0.25em 0" ) ]
|
||||||
|
++ [ A.value model.introspectUrl, Ev.onInput UpdateIntrospectUrl, A.placeholder "https://your-graphql-endpoint.example.com/graphql" ]
|
||||||
|
)
|
||||||
|
[]
|
||||||
|
, H.button (Ev.onClick RequestIntrospection :: styleGroup [ ( "padding", "0.5em" ), ( "margin", "0.25em" ) ]) [ H.text "Add" ]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
introspectionResultView : String -> Maybe IntrospectionResult -> Maybe (Html Msg)
|
||||||
|
introspectionResultView url mir =
|
||||||
|
case mir of
|
||||||
|
Just ir ->
|
||||||
|
Just
|
||||||
|
(H.li []
|
||||||
|
([ H.text url ]
|
||||||
|
++ (case ir of
|
||||||
|
Loading ->
|
||||||
|
[ H.text "Loading..." ]
|
||||||
|
|
||||||
|
Error e ->
|
||||||
|
[ H.text ("Error: " ++ httpErrorToString e) ]
|
||||||
|
|
||||||
|
Success i ->
|
||||||
|
[ introspectionView i ]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
|
||||||
|
introspectionView : Introspection -> Html Msg
|
||||||
|
introspectionView i =
|
||||||
|
let
|
||||||
|
allTypes =
|
||||||
|
List.filter
|
||||||
|
(\t ->
|
||||||
|
not
|
||||||
|
(List.member t.name <|
|
||||||
|
List.filterMap identity [ i.mutationType, i.queryType ]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
i.types
|
||||||
|
in
|
||||||
|
H.div [] (List.map typeView allTypes)
|
||||||
|
|
||||||
|
|
||||||
|
typeView : QLType -> Html Msg
|
||||||
|
typeView qt =
|
||||||
|
H.div []
|
||||||
|
[ H.text (qt.name ++ ": " ++ qt.kind) ]
|
||||||
|
|
||||||
|
|
||||||
|
httpErrorToString : Http.Error -> String
|
||||||
|
httpErrorToString e =
|
||||||
|
case e of
|
||||||
|
Http.BadUrl s ->
|
||||||
|
"Bad URL: " ++ s
|
||||||
|
|
||||||
|
Http.Timeout ->
|
||||||
|
"Timeout"
|
||||||
|
|
||||||
|
Http.NetworkError ->
|
||||||
|
"Network Error"
|
||||||
|
|
||||||
|
Http.BadStatus n ->
|
||||||
|
"Bad Status: " ++ String.fromInt n
|
||||||
|
|
||||||
|
Http.BadBody s ->
|
||||||
|
"Bad Body: " ++ s
|
||||||
|
|
||||||
|
|
||||||
|
decodeIntrospection : Decoder Introspection
|
||||||
|
decodeIntrospection =
|
||||||
|
J.field "data"
|
||||||
|
(J.field "__schema"
|
||||||
|
(J.map5 Introspection
|
||||||
|
(J.field "directives" (J.list decodeDirective))
|
||||||
|
(J.maybe (J.field "queryType" (J.field "name" J.string)))
|
||||||
|
(J.maybe (J.field "mutationType" (J.field "name" J.string)))
|
||||||
|
(J.maybe (J.field "subscriptionType" (J.field "name" J.string)))
|
||||||
|
(J.field "types" (J.list decodeType))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
decodeType : Decoder QLType
|
||||||
|
decodeType =
|
||||||
|
J.map8 QLType
|
||||||
|
(J.field "kind" J.string)
|
||||||
|
(J.field "name" J.string)
|
||||||
|
(J.maybe (J.field "description" J.string))
|
||||||
|
(J.maybe (J.field "fields" (J.list decodeField)))
|
||||||
|
(J.maybe (J.field "inputFields" (J.list decodeField)))
|
||||||
|
(J.maybe (J.field "interfaces" (J.list decodeInterface)))
|
||||||
|
(J.maybe (J.field "enumValues" (J.list decodeEnumValue)))
|
||||||
|
(J.maybe (J.field "possibleTypes" (J.list decodePossibleType)))
|
||||||
|
|
||||||
|
|
||||||
|
decodeField : Decoder Field
|
||||||
|
decodeField =
|
||||||
|
J.map6 Field
|
||||||
|
(J.field "name" J.string)
|
||||||
|
(J.maybe (J.field "description" J.string))
|
||||||
|
(J.field "args" (J.list decodeArg))
|
||||||
|
(J.field "type" decodeFieldType)
|
||||||
|
(J.field "isDeprecated" J.bool)
|
||||||
|
(J.maybe (J.field "deprecationReason" J.string))
|
||||||
|
|
||||||
|
|
||||||
|
decodePossibleType =
|
||||||
|
decodeArgType
|
||||||
|
|
||||||
|
|
||||||
|
decodeEnumValue =
|
||||||
|
J.value
|
||||||
|
|
||||||
|
|
||||||
|
decodeInterface =
|
||||||
|
J.value
|
||||||
|
|
||||||
|
|
||||||
|
decodeDirective : Decoder Directive
|
||||||
|
decodeDirective =
|
||||||
|
J.map4 Directive
|
||||||
|
(J.field "args" (J.list decodeArg))
|
||||||
|
(J.maybe (J.field "locations" (J.list J.string)))
|
||||||
|
(J.maybe (J.field "description" J.string))
|
||||||
|
(J.field "name" J.string)
|
||||||
|
|
||||||
|
|
||||||
|
decodeArg : Decoder Arg
|
||||||
|
decodeArg =
|
||||||
|
J.map4 Arg
|
||||||
|
(J.field "defaultValue" J.value)
|
||||||
|
(J.maybe (J.field "description" J.string))
|
||||||
|
(J.field "name" J.string)
|
||||||
|
(J.field "type" decodeArgType)
|
||||||
|
|
||||||
|
|
||||||
|
decodeFieldType =
|
||||||
|
J.map3 ArgType
|
||||||
|
(J.field "kind" J.string)
|
||||||
|
(J.maybe (J.field "name" J.string))
|
||||||
|
(J.field "ofType" decodeLazyArgType)
|
||||||
|
|
||||||
|
|
||||||
|
decodeArgType : Decoder ArgType
|
||||||
|
decodeArgType =
|
||||||
|
J.map3 ArgType
|
||||||
|
(J.field "kind" J.string)
|
||||||
|
(J.maybe (J.field "name" J.string))
|
||||||
|
(J.field "ofType" decodeLazyArgType)
|
||||||
|
|
||||||
|
|
||||||
|
decodeLazyArgType : Decoder OfArgType
|
||||||
|
decodeLazyArgType =
|
||||||
|
J.map OfArgType (J.maybe (J.lazy (\_ -> decodeArgType)))
|
||||||
|
|
||||||
|
|
||||||
|
introspectionQuery : Http.Body
|
||||||
|
introspectionQuery =
|
||||||
|
-- this query was copied from the one graphiql made
|
||||||
|
Http.jsonBody (Encode.object [ ( "query", Encode.string "query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description args { ...InputValue }}}} fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef }}fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue}fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name }}}}}}}}" ) ])
|
||||||
|
|
||||||
|
|
||||||
|
styleGroup : List ( String, String ) -> List (Attribute Msg)
|
||||||
|
styleGroup l =
|
||||||
|
List.map (\( k, v ) -> A.style k v) l
|
16
src/index.html
Normal file
16
src/index.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>GraphQL Introspector</title>
|
||||||
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="elm-main-mount"></div>
|
||||||
|
<script src="./main.js"></script>
|
||||||
|
<script>
|
||||||
|
Elm.Main.init({
|
||||||
|
node: document.getElementById('elm-main-mount')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
25
src/styles.styl
Normal file
25
src/styles.styl
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#app
|
||||||
|
background #111
|
||||||
|
color #fff
|
||||||
|
font-family Iosevka, monospace
|
||||||
|
display flex
|
||||||
|
flex-direction column
|
||||||
|
justify-content space-between
|
||||||
|
min-height 100vh
|
||||||
|
|
||||||
|
a
|
||||||
|
color #0af
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6
|
||||||
|
font-weight 400
|
||||||
|
|
||||||
|
input, select, button
|
||||||
|
font inherit
|
||||||
|
border 0
|
||||||
|
background #333
|
||||||
|
border-radius 0.25em
|
||||||
|
|
||||||
|
*
|
||||||
|
box-sizing border-box
|
||||||
|
margin 0
|
||||||
|
padding 0
|
Loading…
Reference in a new issue