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