mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-11-24 15:12:01 +03:00
Use ilias's latest json library, update docs site, use 19.1 for docs site.
This commit is contained in:
parent
b0213189ca
commit
1b2d08bc54
3
elm.json
3
elm.json
@ -35,7 +35,8 @@
|
||||
"mgold/elm-nonempty-list": "4.0.2 <= v < 5.0.0",
|
||||
"miniBill/elm-codec": "1.2.0 <= v < 2.0.0",
|
||||
"noahzgordon/elm-color-extra": "1.0.2 <= v < 2.0.0",
|
||||
"tripokey/elm-fuzzy": "5.2.1 <= v < 6.0.0"
|
||||
"tripokey/elm-fuzzy": "5.2.1 <= v < 6.0.0",
|
||||
"zwilias/json-decode-exploration": "6.0.0 <= v < 7.0.0"
|
||||
},
|
||||
"test-dependencies": {
|
||||
"avh4/elm-program-test": "3.1.0 <= v < 4.0.0",
|
||||
|
@ -5,13 +5,13 @@
|
||||
"../../src",
|
||||
"gen"
|
||||
],
|
||||
"elm-version": "0.19.0",
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"avh4/elm-color": "1.0.0",
|
||||
"dillonkearns/elm-markdown": "1.1.0",
|
||||
"dillonkearns/elm-markdown": "1.1.3",
|
||||
"dillonkearns/elm-oembed": "1.0.0",
|
||||
"elm/browser": "1.0.1",
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.2",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/http": "2.0.0",
|
||||
@ -32,22 +32,23 @@
|
||||
"miniBill/elm-codec": "1.2.0",
|
||||
"noahzgordon/elm-color-extra": "1.0.2",
|
||||
"rtfeldman/elm-hex": "1.0.0",
|
||||
"tripokey/elm-fuzzy": "5.2.1"
|
||||
"tripokey/elm-fuzzy": "5.2.1",
|
||||
"zwilias/json-decode-exploration": "6.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/bytes": "1.0.8",
|
||||
"elm/file": "1.0.5",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/regex": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2",
|
||||
"fredcy/elm-parseint": "2.0.1"
|
||||
"fredcy/elm-parseint": "2.0.1",
|
||||
"mgold/elm-nonempty-list": "4.0.2"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {
|
||||
"elm-explorations/test": "1.2.2"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/random": "1.0.0"
|
||||
}
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12755
examples/docs/package-lock.json
generated
12755
examples/docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@
|
||||
"prismjs": "^1.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"elm": "^0.19.0-no-deps",
|
||||
"elm": "^0.19.1-3",
|
||||
"elm-oembed": "0.0.6",
|
||||
"elm-pages": "file:../..",
|
||||
"elm-test": "^0.19.0-rev6",
|
||||
|
@ -16,6 +16,7 @@ import Html exposing (Html)
|
||||
import Html.Attributes as Attr
|
||||
import Index
|
||||
import Json.Decode as Decode exposing (Decoder)
|
||||
import Json.Decode.Exploration as D
|
||||
import MarkdownRenderer
|
||||
import Metadata exposing (Metadata)
|
||||
import Pages exposing (images, pages)
|
||||
@ -27,6 +28,7 @@ import Pages.Manifest.Category
|
||||
import Pages.PagePath as PagePath exposing (PagePath)
|
||||
import Pages.Platform exposing (Page)
|
||||
import Palette
|
||||
import Secrets
|
||||
import StaticHttp
|
||||
|
||||
|
||||
@ -111,183 +113,178 @@ view :
|
||||
, head : List (Head.Tag Pages.PathKey)
|
||||
}
|
||||
view siteMetadata page =
|
||||
let
|
||||
viewFn =
|
||||
case page.frontmatter of
|
||||
Metadata.Page metadata ->
|
||||
StaticHttp.map3
|
||||
(\elmPagesStars elmPagesStarterStars netlifyStars ->
|
||||
{ view =
|
||||
\model viewForPage ->
|
||||
{ title = metadata.title
|
||||
, body =
|
||||
"elm-pages ⭐️'s: "
|
||||
++ String.fromInt elmPagesStars
|
||||
++ "\n\nelm-pages-starter ⭐️'s: "
|
||||
++ String.fromInt elmPagesStarterStars
|
||||
++ "\n\nelm-markdown ⭐️'s: "
|
||||
++ String.fromInt netlifyStars
|
||||
|> Element.text
|
||||
|> wrapBody
|
||||
}
|
||||
, head = head page.frontmatter
|
||||
}
|
||||
)
|
||||
(StaticHttp.jsonRequest "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||
(Decode.field "stargazers_count" Decode.int)
|
||||
)
|
||||
(StaticHttp.jsonRequest "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
||||
(Decode.field "stargazers_count" Decode.int)
|
||||
)
|
||||
(StaticHttp.jsonRequest "https://api.github.com/repos/dillonkearns/elm-markdown"
|
||||
(Decode.field "stargazers_count" Decode.int)
|
||||
)
|
||||
|
||||
-- StaticHttp.withData "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||
-- (Decode.field "stargazers_count" Decode.int)
|
||||
_ ->
|
||||
StaticHttp.map
|
||||
(\staticData ->
|
||||
{ view =
|
||||
\model viewForPage ->
|
||||
{ title = "Other"
|
||||
, body =
|
||||
"The value is: "
|
||||
++ String.fromInt staticData
|
||||
|> Element.text
|
||||
|> wrapBody
|
||||
}
|
||||
, head = head page.frontmatter
|
||||
}
|
||||
)
|
||||
(StaticHttp.jsonRequest "https://api.github.com/repos/dillonkearns/elm-pages-starter" (Decode.field "stargazers_count" Decode.int))
|
||||
|
||||
-- _ ->
|
||||
-- ( StaticHttp.get "" (\_ -> 456)
|
||||
-- , \staticData model viewForPage ->
|
||||
-- { title = "Test"
|
||||
-- , body =
|
||||
-- "The value is: "
|
||||
-- ++ String.fromInt staticData
|
||||
-- |> Element.text
|
||||
-- |> wrapBody
|
||||
-- }
|
||||
-- )
|
||||
-- [
|
||||
-- header page.path
|
||||
-- , Element.column
|
||||
-- [ Element.padding 50
|
||||
-- , Element.spacing 60
|
||||
-- , Element.Region.mainContent
|
||||
-- ]
|
||||
-- (Tuple.second viewForPage)
|
||||
-- ]
|
||||
-- |> Element.textColumn
|
||||
-- [ Element.width Element.fill
|
||||
-- ]
|
||||
-- }
|
||||
-- Metadata.Article metadata ->
|
||||
-- { title = metadata.title
|
||||
-- , body =
|
||||
-- Element.column [ Element.width Element.fill ]
|
||||
-- [ header page.path
|
||||
-- , Element.column
|
||||
-- [ Element.padding 30
|
||||
-- , Element.spacing 40
|
||||
-- , Element.Region.mainContent
|
||||
-- , Element.width (Element.fill |> Element.maximum 800)
|
||||
-- , Element.centerX
|
||||
-- ]
|
||||
-- (Element.column [ Element.spacing 10 ]
|
||||
-- [ Element.row [ Element.spacing 10 ]
|
||||
-- [ Author.view [] metadata.author
|
||||
-- , Element.column [ Element.spacing 10, Element.width Element.fill ]
|
||||
-- [ Element.paragraph [ Font.bold, Font.size 24 ]
|
||||
-- [ Element.text metadata.author.name
|
||||
-- ]
|
||||
-- , Element.paragraph [ Font.size 16 ]
|
||||
-- [ Element.text metadata.author.bio ]
|
||||
-- ]
|
||||
-- ]
|
||||
-- ]
|
||||
-- :: (publishedDateView metadata |> Element.el [ Font.size 16, Font.color (Element.rgba255 0 0 0 0.6) ])
|
||||
-- :: Palette.blogHeading metadata.title
|
||||
-- :: articleImageView metadata.image
|
||||
-- :: Tuple.second viewForPage
|
||||
-- )
|
||||
-- ]
|
||||
-- }
|
||||
--
|
||||
-- Metadata.Doc metadata ->
|
||||
-- { title = metadata.title
|
||||
-- , body =
|
||||
-- [ header page.path
|
||||
-- , Element.row []
|
||||
-- [ DocSidebar.view page.path siteMetadata
|
||||
-- |> Element.el [ Element.width (Element.fillPortion 2), Element.alignTop, Element.height Element.fill ]
|
||||
-- , Element.column [ Element.width (Element.fillPortion 8), Element.padding 35, Element.spacing 15 ]
|
||||
-- [ Palette.heading 1 [ Element.text metadata.title ]
|
||||
-- , Element.column [ Element.spacing 20 ]
|
||||
-- [ tocView (Tuple.first viewForPage)
|
||||
-- , Element.column
|
||||
-- [ Element.padding 50
|
||||
-- , Element.spacing 30
|
||||
-- , Element.Region.mainContent
|
||||
-- ]
|
||||
-- (Tuple.second viewForPage)
|
||||
-- ]
|
||||
-- ]
|
||||
-- ]
|
||||
-- ]
|
||||
-- |> Element.textColumn
|
||||
-- [ Element.width Element.fill
|
||||
-- , Element.height Element.fill
|
||||
-- ]
|
||||
-- }
|
||||
--
|
||||
-- Metadata.Author author ->
|
||||
-- { title = author.name
|
||||
-- , body =
|
||||
-- Element.column
|
||||
-- [ Element.width Element.fill
|
||||
-- ]
|
||||
-- [ header page.path
|
||||
-- , Element.column
|
||||
-- [ Element.padding 30
|
||||
-- , Element.spacing 20
|
||||
-- , Element.Region.mainContent
|
||||
-- , Element.width (Element.fill |> Element.maximum 800)
|
||||
-- , Element.centerX
|
||||
-- ]
|
||||
-- [ Palette.blogHeading author.name
|
||||
-- , Author.view [] author
|
||||
-- , Element.paragraph [ Element.centerX, Font.center ] (Tuple.second viewForPage)
|
||||
-- ]
|
||||
-- ]
|
||||
-- }
|
||||
--
|
||||
-- Metadata.BlogIndex ->
|
||||
-- { title = "elm-pages blog"
|
||||
-- , body =
|
||||
-- Element.column [ Element.width Element.fill ]
|
||||
-- [ header page.path
|
||||
-- , Element.column [ Element.padding 20, Element.centerX ] [ Index.view siteMetadata ]
|
||||
-- ]
|
||||
-- }
|
||||
-- )
|
||||
-- |> wrapBody
|
||||
in
|
||||
viewFn
|
||||
StaticHttp.succeed
|
||||
{ view =
|
||||
\model viewForPage ->
|
||||
pageView model siteMetadata page viewForPage
|
||||
|> wrapBody
|
||||
, head = head page.frontmatter
|
||||
}
|
||||
|
||||
|
||||
wrapBody body =
|
||||
body
|
||||
|> Element.layout
|
||||
[ Element.width Element.fill
|
||||
, Font.size 20
|
||||
, Font.family [ Font.typeface "Roboto" ]
|
||||
, Font.color (Element.rgba255 0 0 0 0.8)
|
||||
]
|
||||
|
||||
--let
|
||||
-- viewFn =
|
||||
-- case page.frontmatter of
|
||||
-- Metadata.Page metadata ->
|
||||
-- StaticHttp.map3
|
||||
-- (\elmPagesStars elmPagesStarterStars netlifyStars ->
|
||||
-- { view =
|
||||
-- \model viewForPage ->
|
||||
-- { title = metadata.title
|
||||
-- , body =
|
||||
-- "elm-pages ⭐️'s: "
|
||||
-- ++ String.fromInt elmPagesStars
|
||||
-- ++ "\n\nelm-pages-starter ⭐️'s: "
|
||||
-- ++ String.fromInt elmPagesStarterStars
|
||||
-- ++ "\n\nelm-markdown ⭐️'s: "
|
||||
-- ++ String.fromInt netlifyStars
|
||||
-- |> Element.text
|
||||
-- |> wrapBody
|
||||
-- }
|
||||
-- , head = head page.frontmatter
|
||||
-- }
|
||||
-- )
|
||||
-- (StaticHttp.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
|
||||
-- (D.field "stargazers_count" D.int)
|
||||
-- )
|
||||
-- (StaticHttp.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages-starter")
|
||||
-- (D.field "stargazers_count" D.int)
|
||||
-- )
|
||||
-- (StaticHttp.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-markdown")
|
||||
-- (D.field "stargazers_count" D.int)
|
||||
-- )
|
||||
--
|
||||
-- _ ->
|
||||
-- StaticHttp.withData "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||
-- (Decode.field "stargazers_count" Decode.int)
|
||||
|
||||
|
||||
pageView :
|
||||
Model
|
||||
-> List ( PagePath Pages.PathKey, Metadata )
|
||||
-> { path : PagePath Pages.PathKey, frontmatter : Metadata }
|
||||
-> ( MarkdownRenderer.TableOfContents, List (Element Msg) )
|
||||
-> { title : String, body : Element Msg }
|
||||
pageView model siteMetadata page viewForPage =
|
||||
case page.frontmatter of
|
||||
Metadata.Page metadata ->
|
||||
{ title = metadata.title
|
||||
, body =
|
||||
[ header page.path
|
||||
, Element.column
|
||||
[ Element.padding 50
|
||||
, Element.spacing 60
|
||||
, Element.Region.mainContent
|
||||
]
|
||||
(Tuple.second viewForPage)
|
||||
]
|
||||
|> Element.textColumn
|
||||
[ Element.width Element.fill
|
||||
]
|
||||
}
|
||||
|
||||
Metadata.Article metadata ->
|
||||
{ title = metadata.title
|
||||
, body =
|
||||
Element.column [ Element.width Element.fill ]
|
||||
[ header page.path
|
||||
, Element.column
|
||||
[ Element.padding 30
|
||||
, Element.spacing 40
|
||||
, Element.Region.mainContent
|
||||
, Element.width (Element.fill |> Element.maximum 800)
|
||||
, Element.centerX
|
||||
]
|
||||
(Element.column [ Element.spacing 10 ]
|
||||
[ Element.row [ Element.spacing 10 ]
|
||||
[ Author.view [] metadata.author
|
||||
, Element.column [ Element.spacing 10, Element.width Element.fill ]
|
||||
[ Element.paragraph [ Font.bold, Font.size 24 ]
|
||||
[ Element.text metadata.author.name
|
||||
]
|
||||
, Element.paragraph [ Font.size 16 ]
|
||||
[ Element.text metadata.author.bio ]
|
||||
]
|
||||
]
|
||||
]
|
||||
:: (publishedDateView metadata |> Element.el [ Font.size 16, Font.color (Element.rgba255 0 0 0 0.6) ])
|
||||
:: Palette.blogHeading metadata.title
|
||||
:: articleImageView metadata.image
|
||||
:: Tuple.second viewForPage
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
Metadata.Doc metadata ->
|
||||
{ title = metadata.title
|
||||
, body =
|
||||
[ header page.path
|
||||
, Element.row []
|
||||
[ DocSidebar.view page.path siteMetadata
|
||||
|> Element.el [ Element.width (Element.fillPortion 2), Element.alignTop, Element.height Element.fill ]
|
||||
, Element.column [ Element.width (Element.fillPortion 8), Element.padding 35, Element.spacing 15 ]
|
||||
[ Palette.heading 1 [ Element.text metadata.title ]
|
||||
, Element.column [ Element.spacing 20 ]
|
||||
[ tocView (Tuple.first viewForPage)
|
||||
, Element.column
|
||||
[ Element.padding 50
|
||||
, Element.spacing 30
|
||||
, Element.Region.mainContent
|
||||
]
|
||||
(Tuple.second viewForPage)
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|> Element.textColumn
|
||||
[ Element.width Element.fill
|
||||
, Element.height Element.fill
|
||||
]
|
||||
}
|
||||
|
||||
Metadata.Author author ->
|
||||
{ title = author.name
|
||||
, body =
|
||||
Element.column
|
||||
[ Element.width Element.fill
|
||||
]
|
||||
[ header page.path
|
||||
, Element.column
|
||||
[ Element.padding 30
|
||||
, Element.spacing 20
|
||||
, Element.Region.mainContent
|
||||
, Element.width (Element.fill |> Element.maximum 800)
|
||||
, Element.centerX
|
||||
]
|
||||
[ Palette.blogHeading author.name
|
||||
, Author.view [] author
|
||||
, Element.paragraph [ Element.centerX, Font.center ] (Tuple.second viewForPage)
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
Metadata.BlogIndex ->
|
||||
{ title = "elm-pages blog"
|
||||
, body =
|
||||
Element.column [ Element.width Element.fill ]
|
||||
[ header page.path
|
||||
, Element.column [ Element.padding 20, Element.centerX ] [ Index.view siteMetadata ]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
wrapBody record =
|
||||
{ body =
|
||||
record.body
|
||||
|> Element.layout
|
||||
[ Element.width Element.fill
|
||||
, Font.size 20
|
||||
, Font.family [ Font.typeface "Roboto" ]
|
||||
, Font.color (Element.rgba255 0 0 0 0.8)
|
||||
]
|
||||
, title = record.title
|
||||
}
|
||||
|
||||
|
||||
articleImageView : ImagePath Pages.PathKey -> Element msg
|
||||
|
@ -33,7 +33,8 @@
|
||||
"miniBill/elm-codec": "1.2.0",
|
||||
"noahzgordon/elm-color-extra": "1.0.2",
|
||||
"rtfeldman/elm-hex": "1.0.0",
|
||||
"tripokey/elm-fuzzy": "5.2.1"
|
||||
"tripokey/elm-fuzzy": "5.2.1",
|
||||
"zwilias/json-decode-exploration": "6.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/bytes": "1.0.8",
|
||||
|
@ -23,6 +23,7 @@ import Pages.Platform exposing (Page)
|
||||
import Palette
|
||||
import Secrets
|
||||
import StaticHttp
|
||||
import Time
|
||||
|
||||
|
||||
|
||||
@ -145,7 +146,7 @@ get url decoder =
|
||||
pokemonDetailRequest : StaticHttp.Request (List Pokemon)
|
||||
pokemonDetailRequest =
|
||||
get
|
||||
"https://pokeapi.co/api/v2/pokemon/?limit=10"
|
||||
"https://pokeapi.co/api/v2/pokemon/?limit=3"
|
||||
(Decode.field "results"
|
||||
(Decode.list
|
||||
(Decode.map2 Tuple.pair
|
||||
@ -185,6 +186,8 @@ view siteMetadata page =
|
||||
{ title = "Landing Page"
|
||||
, body =
|
||||
[ header starCount
|
||||
, Element.text "Built at: "
|
||||
, Element.text <| String.fromInt <| Time.posixToMillis Pages.buildTime
|
||||
, pokemon
|
||||
|> List.map pokemonView
|
||||
|> Element.column
|
||||
|
@ -34,12 +34,13 @@ module.exports = class AddFilesPlugin {
|
||||
// https://github.com/jantimon/html-webpack-plugin/blob/35a154186501fba3ecddb819b6f632556d37a58f/index.js#L470-L478
|
||||
|
||||
const staticRequests = this.pagesWithRequests[`/${file.baseRoute}`];
|
||||
console.log('staticData', staticRequests);
|
||||
|
||||
const filename = path.join(file.baseRoute, "content.json");
|
||||
compilation.fileDependencies.add(filename);
|
||||
const rawContents = JSON.stringify({
|
||||
body: file.content,
|
||||
staticData: staticRequests
|
||||
staticData: staticRequests || {}
|
||||
});
|
||||
|
||||
compilation.assets[filename] = {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,92 +0,0 @@
|
||||
module Json.Decode.Exploration.Located exposing (Located(..), toString, map)
|
||||
|
||||
{-| A type that gives one or more pieces of information, tagged with a path
|
||||
through a datastructure with fields and indices.
|
||||
|
||||
Most importantly, it is used for both `Warnings` and `Errors` in `Json.Decode.Exploration`.
|
||||
|
||||
@docs Located, toString, map
|
||||
|
||||
-}
|
||||
|
||||
import List.Nonempty as Nonempty exposing (Nonempty(..))
|
||||
|
||||
|
||||
{-| -}
|
||||
type Located a
|
||||
= InField String (Nonempty (Located a))
|
||||
| AtIndex Int (Nonempty (Located a))
|
||||
| Here a
|
||||
|
||||
|
||||
{-| Allows turning a non-empty list of `Located a` into a flat list of human
|
||||
readable strings, provided we have a way to turn an `a` into some lines of text.
|
||||
|
||||
Each string represents a line. This allows arbitrary indentation by mapping over
|
||||
the lines and prepending some whitespace.
|
||||
|
||||
-}
|
||||
toString : (a -> List String) -> Nonempty (Located a) -> List String
|
||||
toString itemToString locatedItems =
|
||||
locatedItems
|
||||
|> gather ""
|
||||
|> List.map (\( x, vals ) -> render itemToString x vals)
|
||||
|> intercalate ""
|
||||
|
||||
|
||||
{-| -}
|
||||
map : (a -> b) -> Located a -> Located b
|
||||
map op located =
|
||||
case located of
|
||||
InField f val ->
|
||||
InField f <| Nonempty.map (map op) val
|
||||
|
||||
AtIndex i val ->
|
||||
AtIndex i <| Nonempty.map (map op) val
|
||||
|
||||
Here v ->
|
||||
Here (op v)
|
||||
|
||||
|
||||
intercalate : a -> List (List a) -> List a
|
||||
intercalate sep lists =
|
||||
lists |> List.intersperse [ sep ] |> List.concat
|
||||
|
||||
|
||||
render : (a -> List String) -> String -> List a -> List String
|
||||
render itemToString path errors =
|
||||
let
|
||||
formattedErrors : List String
|
||||
formattedErrors =
|
||||
List.concatMap itemToString errors
|
||||
|> List.map indent
|
||||
in
|
||||
if String.isEmpty path then
|
||||
formattedErrors
|
||||
|
||||
else
|
||||
("At path " ++ path) :: "" :: formattedErrors
|
||||
|
||||
|
||||
indent : String -> String
|
||||
indent =
|
||||
(++) " "
|
||||
|
||||
|
||||
flatten : Located a -> List ( String, List a )
|
||||
flatten located =
|
||||
case located of
|
||||
Here v ->
|
||||
[ ( "", [ v ] ) ]
|
||||
|
||||
InField s vals ->
|
||||
gather ("/" ++ s) vals
|
||||
|
||||
AtIndex i vals ->
|
||||
gather ("/" ++ String.fromInt i) vals
|
||||
|
||||
|
||||
gather : String -> Nonempty (Located a) -> List ( String, List a )
|
||||
gather prefix (Nonempty first rest) =
|
||||
List.concatMap flatten (first :: rest)
|
||||
|> List.map (Tuple.mapFirst ((++) prefix))
|
@ -1,437 +0,0 @@
|
||||
module Json.Decode.Exploration.Pipeline exposing
|
||||
( required, requiredAt, optional, optionalAt, hardcoded, custom
|
||||
, checked, checkedAt, ignored, ignoredAt
|
||||
, decode, resolve
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Json.Decode.Pipeline
|
||||
|
||||
Use the `(|>)` operator to build JSON decoders.
|
||||
|
||||
|
||||
## Decoding fields
|
||||
|
||||
@docs required, requiredAt, optional, optionalAt, hardcoded, custom
|
||||
|
||||
|
||||
## Validating the JSON without using the values
|
||||
|
||||
@docs checked, checkedAt, ignored, ignoredAt
|
||||
|
||||
|
||||
## Beginning and ending pipelines
|
||||
|
||||
@docs decode, resolve
|
||||
|
||||
|
||||
### Verified docs
|
||||
|
||||
The examples all expect imports set up like this:
|
||||
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
import Json.Decode.Exploration.Pipeline exposing (..)
|
||||
import Json.Decode.Exploration.Located exposing (Located(..))
|
||||
import Json.Encode as Encode
|
||||
import List.Nonempty as Nonempty
|
||||
|
||||
For automated verification of these examples, this import is also required.
|
||||
Please ignore it.
|
||||
|
||||
import DocVerificationHelpers exposing (User)
|
||||
|
||||
-}
|
||||
|
||||
import Json.Decode.Exploration as Decode exposing (Decoder)
|
||||
|
||||
|
||||
{-| Decode a required field.
|
||||
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, email : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> required "name" string
|
||||
|> required "email" string
|
||||
|
||||
""" {"id": 123, "email": "sam@example.com", "name": "Sam"} """
|
||||
|> decodeString userDecoder
|
||||
--> Success { id = 123, name = "Sam", email = "sam@example.com" }
|
||||
|
||||
-}
|
||||
required : String -> Decoder a -> Decoder (a -> b) -> Decoder b
|
||||
required key valDecoder decoder =
|
||||
decoder |> Decode.andMap (Decode.field key valDecoder)
|
||||
|
||||
|
||||
{-| Decode a required nested field.
|
||||
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, email : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> requiredAt [ "profile", "name" ] string
|
||||
|> required "email" string
|
||||
|
||||
"""
|
||||
{
|
||||
"id": 123,
|
||||
"email": "sam@example.com",
|
||||
"profile": { "name": "Sam" }
|
||||
}
|
||||
"""
|
||||
|> decodeString userDecoder
|
||||
--> Success { id = 123, name = "Sam", email = "sam@example.com" }
|
||||
|
||||
-}
|
||||
requiredAt : List String -> Decoder a -> Decoder (a -> b) -> Decoder b
|
||||
requiredAt path valDecoder decoder =
|
||||
decoder |> Decode.andMap (Decode.at path valDecoder)
|
||||
|
||||
|
||||
{-| Decode a field that may be missing or have a null value. If the field is
|
||||
missing, then it decodes as the `fallback` value. If the field is present,
|
||||
then `valDecoder` is used to decode its value. If `valDecoder` fails on a
|
||||
`null` value, then the `fallback` is used as if the field were missing
|
||||
entirely.
|
||||
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, email : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> optional "name" string "blah"
|
||||
|> required "email" string
|
||||
|
||||
""" { "id": 123, "email": "sam@example.com" } """
|
||||
|> decodeString userDecoder
|
||||
--> Success { id = 123, name = "blah", email = "sam@example.com" }
|
||||
|
||||
Because `valDecoder` is given an opportunity to decode `null` values before
|
||||
resorting to the `fallback`, you can distinguish between missing and `null`
|
||||
values if you need to:
|
||||
|
||||
userDecoder2 =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> optional "name" (oneOf [ string, null "NULL" ]) "MISSING"
|
||||
|> required "email" string
|
||||
|
||||
Note also that this behaves _slightly_ different than the stock pipeline
|
||||
package.
|
||||
|
||||
In the stock pipeline package, running the following decoder with an array as
|
||||
the input would _succeed_.
|
||||
|
||||
fooDecoder =
|
||||
decode identity
|
||||
|> optional "foo" (maybe string) Nothing
|
||||
|
||||
In this package, such a decoder will error out instead, saying that it expected
|
||||
the input to be an object. The _key_ `"foo"` is optional, but it really does
|
||||
have to be an object before we even consider trying your decoder or returning
|
||||
the fallback.
|
||||
|
||||
-}
|
||||
optional : String -> Decoder a -> a -> Decoder (a -> b) -> Decoder b
|
||||
optional key valDecoder fallback decoder =
|
||||
Decode.andMap (optionalField key valDecoder fallback) decoder
|
||||
|
||||
|
||||
{-| Decode an optional nested field.
|
||||
-}
|
||||
optionalAt : List String -> Decoder a -> a -> Decoder (a -> b) -> Decoder b
|
||||
optionalAt path valDecoder fallback decoder =
|
||||
Decode.andMap
|
||||
(List.foldr (\f d -> optionalField f d fallback) valDecoder path)
|
||||
decoder
|
||||
|
||||
|
||||
optionalField : String -> Decoder a -> a -> Decoder a
|
||||
optionalField field decoder fallback =
|
||||
Decode.isObject
|
||||
|> Decode.andThen
|
||||
(\_ ->
|
||||
Decode.oneOf
|
||||
[ Decode.field field (Decode.null <| Decode.succeed fallback)
|
||||
, Decode.field field (Decode.succeed <| Decode.field field decoder)
|
||||
, Decode.succeed (Decode.succeed fallback)
|
||||
]
|
||||
|> resolve
|
||||
)
|
||||
|
||||
|
||||
{-| Rather than decoding anything, use a fixed value for the next step in the
|
||||
pipeline. `harcoded` does not look at the JSON at all.
|
||||
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, email : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> hardcoded "Alex"
|
||||
|> required "email" string
|
||||
|
||||
""" { "id": 123, "email": "sam@example.com" } """
|
||||
|> decodeString userDecoder
|
||||
--> Success { id = 123, name = "Alex", email = "sam@example.com" }
|
||||
|
||||
-}
|
||||
hardcoded : a -> Decoder (a -> b) -> Decoder b
|
||||
hardcoded =
|
||||
Decode.andMap << Decode.succeed
|
||||
|
||||
|
||||
{-| `check` a field in the JSON to ensure it has a certain value before allowing
|
||||
the decoder to succeed.
|
||||
|
||||
import List.Nonempty as Nonempty
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
import Json.Decode.Exploration.Located exposing (Located(..))
|
||||
import Json.Encode as Encode
|
||||
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, email : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> required "name" string
|
||||
|> required "email" string
|
||||
|> checked "enabled" bool True
|
||||
|
||||
"""
|
||||
{
|
||||
"id": 123,
|
||||
"email": "sam@example.com",
|
||||
"name": "Sam",
|
||||
"enabled": true
|
||||
}
|
||||
"""
|
||||
|> decodeString userDecoder
|
||||
--> Success { id = 123, name = "Sam", email = "sam@example.com" }
|
||||
|
||||
"""
|
||||
{
|
||||
"id": 123,
|
||||
"email": "sam@example.com",
|
||||
"name": "Sam",
|
||||
"enabled": false
|
||||
}
|
||||
"""
|
||||
|> decodeString userDecoder
|
||||
--> Errors expectedErrors
|
||||
|
||||
expectedErrors : Errors
|
||||
expectedErrors =
|
||||
Failure "Verification failed" (Just <| Encode.bool False)
|
||||
|> Here
|
||||
|> Nonempty.fromElement
|
||||
|> InField "enabled"
|
||||
|> Nonempty.fromElement
|
||||
|
||||
-}
|
||||
checked : String -> Decoder a -> a -> Decoder b -> Decoder b
|
||||
checked fieldName =
|
||||
checkedAt [ fieldName ]
|
||||
|
||||
|
||||
{-| Check a field at a certain path and validate its content before allowing
|
||||
decoding to succeed.
|
||||
-}
|
||||
checkedAt : List String -> Decoder a -> a -> Decoder b -> Decoder b
|
||||
checkedAt path decoder expectedValue next =
|
||||
Decode.succeed ()
|
||||
|> Decode.check decoder expectedValue
|
||||
|> Decode.at path
|
||||
|> Decode.andThen (\_ -> next)
|
||||
|
||||
|
||||
{-| Ignore a field from decoding. By using `ignored`, you acknowledge that it is
|
||||
in the JSON so warnings don't show up.
|
||||
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, email : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> required "name" string
|
||||
|> required "email" string
|
||||
|> ignored "enabled"
|
||||
|
||||
"""
|
||||
{
|
||||
"id": 123,
|
||||
"email": "sam@example.com",
|
||||
"name": "Sam",
|
||||
"enabled": "No one cares, it's ignored."
|
||||
}
|
||||
"""
|
||||
|> decodeString userDecoder
|
||||
--> Success { id = 123, name = "Sam", email = "sam@example.com" }
|
||||
|
||||
-}
|
||||
ignored : String -> Decoder a -> Decoder a
|
||||
ignored field =
|
||||
ignoredAt [ field ]
|
||||
|
||||
|
||||
{-| Ignore a value at a certain path from decoding. By using `ignoredAt`, you
|
||||
acknowledge that it is in the JSON so warnings don't show up.
|
||||
-}
|
||||
ignoredAt : List String -> Decoder a -> Decoder a
|
||||
ignoredAt path decoder =
|
||||
Decode.at path Decode.value |> Decode.andThen (\_ -> decoder)
|
||||
|
||||
|
||||
{-| Run the given decoder and feed its result into the pipeline at this point.
|
||||
|
||||
Consider this example.
|
||||
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, email : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> custom (at [ "profile", "name" ] string)
|
||||
|> required "email" string
|
||||
|
||||
"""
|
||||
{
|
||||
"id": 123,
|
||||
"email": "sam@example.com",
|
||||
"profile": {"name": "Sam"}
|
||||
}
|
||||
"""
|
||||
|> decodeString userDecoder
|
||||
--> Success { id = 123, name = "Sam", email = "sam@example.com" }
|
||||
|
||||
-}
|
||||
custom : Decoder a -> Decoder (a -> b) -> Decoder b
|
||||
custom =
|
||||
Decode.andMap
|
||||
|
||||
|
||||
{-| Convert a `Decoder (Result x a)` into a `Decoder a`. Useful when you want
|
||||
to perform some custom processing just before completing the decoding operation.
|
||||
|
||||
import Json.Decode.Exploration exposing (..)
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, email : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
let
|
||||
-- toDecoder gets run *after* all the
|
||||
-- (|> required ...) steps are done.
|
||||
toDecoder : Int -> String -> String -> Int -> Decoder User
|
||||
toDecoder id name email version =
|
||||
if version >= 2 then
|
||||
succeed (User id name email)
|
||||
else
|
||||
fail "This JSON is from a deprecated source. Please upgrade!"
|
||||
in
|
||||
decode toDecoder
|
||||
|> required "id" int
|
||||
|> required "name" string
|
||||
|> required "email" string
|
||||
|> required "version" int
|
||||
-- version is part of toDecoder,
|
||||
-- but it is not a part of User
|
||||
|> resolve
|
||||
|
||||
"""
|
||||
{
|
||||
"id": 123,
|
||||
"name": "Sam",
|
||||
"email": "sam@example.com",
|
||||
"version": 3
|
||||
}
|
||||
"""
|
||||
|> decodeString userDecoder
|
||||
--> Success { id = 123, name = "Sam", email = "sam@example.com" }
|
||||
|
||||
-}
|
||||
resolve : Decoder (Decoder a) -> Decoder a
|
||||
resolve =
|
||||
Decode.andThen identity
|
||||
|
||||
|
||||
{-| Begin a decoding pipeline. This is a synonym for [Json.Decode.succeed](http://package.elm-lang.org/packages/elm-lang/core/latest/Json-Decode#succeed),
|
||||
intended to make things read more clearly.
|
||||
|
||||
type alias User =
|
||||
{ id : Int
|
||||
, email : String
|
||||
, name : String
|
||||
}
|
||||
|
||||
userDecoder : Decoder User
|
||||
userDecoder =
|
||||
decode User
|
||||
|> required "id" int
|
||||
|> required "email" string
|
||||
|> optional "name" string ""
|
||||
|
||||
-}
|
||||
decode : a -> Decoder a
|
||||
decode =
|
||||
Decode.succeed
|
Loading…
Reference in New Issue
Block a user