This commit is contained in:
Daniel Flanagan 2020-04-03 12:50:27 -05:00
parent 343020f450
commit 6c34e829fd
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
3 changed files with 214 additions and 36 deletions

View file

@ -1,5 +1,7 @@
module Main exposing (main) module Main exposing (main)
-- import Html.Lazy as HLazy
import Browser import Browser
import Dict import Dict
import Html as H exposing (Attribute, Html) import Html as H exposing (Attribute, Html)
@ -9,9 +11,9 @@ import Http
import Json.Decode as J exposing (Decoder) import Json.Decode as J exposing (Decoder)
import Json.Encode as Encode import Json.Encode as Encode
import OrderedDict exposing (OrderedDict) import OrderedDict exposing (OrderedDict)
import Url
main : Program () Model Msg
main = main =
Browser.element Browser.element
{ init = init { init = init
@ -166,44 +168,98 @@ view model =
header : Model -> Html Msg header : Model -> Html Msg
header model = header model =
H.header (styleGroup [ ( "display", "flex" ), ( "align-items", "center" ) ]) H.header
[ H.h1 (styleGroup [ ( "font-size", "inherit" ), ( "padding", "0.5em" ), ( "width", "100%" ), ( "white-space", "nowrap" ), ( "flex", "1" ) ]) [ H.text "GraphQL Introspector" ] (styleGroup [ ( "display", "flex" ), ( "align-items", "center" ) ])
, H.input [ H.h1
(styleGroup [ ( "width", "100%" ), ( "padding", "0.5em" ), ( "margin", "0.25em 0 0.25em 0" ) ] (styleGroup
++ [ A.value model.introspectUrl, Ev.onInput UpdateIntrospectUrl, A.placeholder "https://your-graphql-endpoint.example.com/graphql" ] [ ( "font-size", "inherit" )
, ( "padding", "0.5em" )
, ( "width", "100%" )
, ( "white-space", "nowrap" )
, ( "flex", "1" )
]
) )
[] [ H.text "GraphQL Introspector" ]
, H.button (Ev.onClick RequestIntrospection :: styleGroup [ ( "padding", "0.5em" ), ( "margin", "0.25em" ) ]) [ H.text "Add" ] , H.form
(Ev.onSubmit RequestIntrospection
:: styleGroup
[ ( "width", "100%" )
, ( "display", "flex" )
, ( "align-items", "center" )
]
)
[ 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 : String -> Maybe IntrospectionResult -> Maybe (Html Msg)
introspectionResultView url mir = introspectionResultView url mir =
case mir of Maybe.map
Just ir -> (\ir ->
Just H.li
(H.li [] (styleGroup
([ H.text url ] [ ( "background-color", lighten )
++ (case ir of , ( "padding", "0.5em" )
Loading -> , ( "border-radius", "0.5em" )
[ H.text "Loading..." ] , ( "margin", "0 0.25em 0.25em 0.25em" )
]
Error e ->
[ H.text ("Error: " ++ httpErrorToString e) ]
Success i ->
[ introspectionView i ]
)
)
) )
(H.header (styleGroup [ ( "margin-bottom", "0.5em" ), ( "border-bottom", "solid 1px #888" ), ( "padding-bottom", "0.75em" ) ])
[ H.text ""
, H.a
[ A.href url
, A.target "_blank"
, A.title (Debug.toString mir)
]
[ H.text url ]
]
:: (case ir of
Loading ->
[ H.text "Loading..." ]
Nothing -> Error e ->
Nothing [ H.text ("Error: " ++ httpErrorToString e) ]
Success i ->
[ introspectionView i ]
)
)
)
mir
introspectionView : Introspection -> Html Msg introspectionView : Introspection -> Html Msg
introspectionView i = introspectionView i =
let let
types =
keyBy .name i.types
queryTypes =
List.filter (\t -> Just t.name == i.queryType) i.types
mutationTypes =
List.filter (\t -> Just t.name == i.mutationType) i.types
allTypes = allTypes =
List.filter List.filter
(\t -> (\t ->
@ -211,20 +267,118 @@ introspectionView i =
(List.member t.name <| (List.member t.name <|
List.filterMap identity [ i.mutationType, i.queryType ] List.filterMap identity [ i.mutationType, i.queryType ]
) )
&& not (String.startsWith "__" t.kind)
) )
i.types i.types
sg =
styleGroup [ ( "margin", "1em 0" ) ]
in in
H.div [] (List.map typeView allTypes) H.div []
(List.filterMap
identity
[ i.queryType
|> Maybe.andThen
(\q ->
Dict.get q types.dict
|> Maybe.andThen
(\t ->
Maybe.andThen t.fields (\f -> H.div [] (List.map fieldView f))
)
)
, H.div sg
[ H.h2 [] [ H.text "Query Fields" ]
, H.div [] (List.map typeView queryTypes)
]
, H.div sg
[ H.h2 [] [ H.text "Mutations" ]
, H.div [] (List.map typeView mutationTypes)
]
, H.div sg
[ H.h2 [] [ H.text "All Types" ]
, H.div [] (List.map typeView allTypes)
]
]
)
kindColor : String -> String
kindColor s =
case s of
"OBJECT" ->
"#0af"
"SCALAR" ->
"#fa0"
"ENUM" ->
"#f0a"
"INTERFACE" ->
"#af0"
"UNION" ->
"#0fa"
"INPUT_OBJECT" ->
"#a0f"
_ ->
"#fff"
typeView : QLType -> Html Msg typeView : QLType -> Html Msg
typeView qt = typeView qt =
H.div [] H.div []
[ H.text (qt.name ++ ": " ++ qt.kind) ] ([ H.div
(styleGroup
[ ( "display", "flex" )
, ( "align-items", "center" )
, ( "font-size", "16px" )
, ( "margin", "0.25em 0" )
]
)
[ H.code
(styleGroup
[ ( "margin-right", "0.5em" )
, ( "color", "#111" )
, ( "font-size", "75%" )
, ( "display", "inline-block" )
, ( "padding", "0.25em" )
, ( "border-radius", "0.25em" )
, ( "line-height", "1" )
, ( "background-color", kindColor qt.kind )
]
)
[ H.text qt.kind ]
, H.code [ A.title (Debug.toString qt) ] [ H.text qt.name ]
]
]
++ (case qt.description of
Just s ->
[ H.p (styleGroup [ ( "margin-left", "2em" ) ]) [ H.text s ] ]
Nothing ->
[]
)
++ (case qt.fields of
Just fields ->
[ H.div (styleGroup [ ( "margin-left", "4em" ) ]) <| List.map fieldView fields ]
Nothing ->
[]
)
)
fieldView : Field -> Html Msg
fieldView f =
H.li [ A.title <| Debug.toString f ] [ H.text f.name ]
httpErrorToString : Http.Error -> String httpErrorToString : Http.Error -> String
httpErrorToString e = httpErrorToString e =
-- TODO: refactor as expect function
case e of case e of
Http.BadUrl s -> Http.BadUrl s ->
"Bad URL: " ++ s "Bad URL: " ++ s
@ -248,8 +402,8 @@ decodeIntrospection =
(J.field "__schema" (J.field "__schema"
(J.map5 Introspection (J.map5 Introspection
(J.field "directives" (J.list decodeDirective)) (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 "mutationType" (J.field "name" J.string)))
(J.maybe (J.field "queryType" (J.field "name" J.string)))
(J.maybe (J.field "subscriptionType" (J.field "name" J.string))) (J.maybe (J.field "subscriptionType" (J.field "name" J.string)))
(J.field "types" (J.list decodeType)) (J.field "types" (J.list decodeType))
) )
@ -280,14 +434,17 @@ decodeField =
(J.maybe (J.field "deprecationReason" J.string)) (J.maybe (J.field "deprecationReason" J.string))
decodePossibleType : Decoder PossibleType
decodePossibleType = decodePossibleType =
decodeArgType decodeArgType
decodeEnumValue : Decoder J.Value
decodeEnumValue = decodeEnumValue =
J.value J.value
decodeInterface : Decoder J.Value
decodeInterface = decodeInterface =
J.value J.value
@ -310,6 +467,7 @@ decodeArg =
(J.field "type" decodeArgType) (J.field "type" decodeArgType)
decodeFieldType : Decoder ArgType
decodeFieldType = decodeFieldType =
J.map3 ArgType J.map3 ArgType
(J.field "kind" J.string) (J.field "kind" J.string)
@ -333,9 +491,24 @@ decodeLazyArgType =
introspectionQuery : Http.Body introspectionQuery : Http.Body
introspectionQuery = introspectionQuery =
-- this query was copied from the one graphiql made -- 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 }}}}}}}}" ) ]) 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 : List ( String, String ) -> List (Attribute Msg)
styleGroup l = styleGroup l =
List.map (\( k, v ) -> A.style k v) l List.map (\( k, v ) -> A.style k v) l
lighten : String
lighten =
"rgba(255, 255, 255, 0.05)"
keyBy : (a -> comparable) -> List a -> OrderedDict comparable a
keyBy f l =
List.foldl (\t -> \d -> OrderedDict.insert (f t) t d) OrderedDict.empty l
getByKey : comparable -> OrderedDict comparable v -> Maybe v
getByKey k d =
Dict.get k d.dict

View file

@ -1,6 +1,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GraphQL Introspector</title> <title>GraphQL Introspector</title>
<link rel="stylesheet" href="./styles.css" /> <link rel="stylesheet" href="./styles.css" />
</head> </head>

View file

@ -1,11 +1,14 @@
#app html, body
background #111 background #111
color #fff color #fff
font-family Iosevka, monospace
display flex #app
flex-direction column font-family sans-serif
justify-content space-between font-size 16px
min-height 100vh line-height 1.6em
pre, code
font-family monospace
a a
color #0af color #0af
@ -15,6 +18,7 @@
input, select, button input, select, button
font inherit font inherit
color inherit
border 0 border 0
background #333 background #333
border-radius 0.25em border-radius 0.25em