Add part5

This commit is contained in:
Richard Feldman
2018-04-28 16:55:52 -04:00
parent d4718c64b4
commit b0f8f8ee42
576 changed files with 97325 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# elm-package generated files
elm-stuff/
# elm-repl generated files
repl-temp-*

View File

@@ -0,0 +1,27 @@
Copyright (c) 2016, NoRedInk
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of elm-decode-pipeline nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,108 @@
# elm-decode-pipeline
A library for building decoders using the pipeline [`(|>)`](http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Basics#|>)
operator and plain function calls.
## Motivation
It's common to decode into a record that has a `type alias`. Here's an example
of this from the [`object3`](http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Json-Decode#object3)
docs:
```elm
type alias Job = { name : String, id : Int, completed : Bool }
point : Decoder Job
point =
object3 Job
("name" := string)
("id" := int)
("completed" := bool)
```
This works because a record type alias can be called as a normal function. In
that case it accepts one argument for each field (in whatever order the fields
are declared in the type alias) and then returns an appropriate record built
with those arguments.
The `objectN` decoders are straightforward, but require manually changing N
whenever the field count changes. This library provides functions designed to
be used with the `|>` operator, with the goal of having decoders that are both
easy to read and easy to modify.
## Examples
Here is a decoder built with this library.
```elm
import Json.Decode exposing (int, string, float, Decoder)
import Json.Decode.Pipeline exposing (decode, required, optional, hardcoded)
type alias User =
{ id : Int
, email : Maybe String
, name : String
, percentExcited : Float
}
userDecoder : Decoder User
userDecoder =
decode User
|> required "id" int
|> required "email" (nullable string) -- `null` decodes to `Nothing`
|> optional "name" string "(fallback if name is `null` or not present)"
|> hardcoded 1.0
```
In this example:
* `decode` is a synonym for [`succeed`](http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Json-Decode#succeed) (it just reads better here)
* `required "id" int` is similar to `("id" := int)`
* `optional` is like `required`, but if the field is either `null` or not present, decoding does not fail; instead it succeeds with the provided fallback value.
* `hardcoded` does not look at the provided JSON, and instead always decodes to the same value.
You could use this decoder as follows:
```elm
Json.Decode.decodeString
userDecoder
"""
{"id": 123, "email": "sam@example.com", "name": "Sam Sample"}
"""
```
The result would be:
```elm
{ id = 123
, email = "sam@example.com"
, name = "Sam Sample"
, percentExcited = 1.0
}
```
Alternatively, you could use it like so:
```elm
Json.Decode.decodeString
userDecoder
"""
{"id": 123, "email": "sam@example.com", "percentExcited": "(hardcoded)"}
"""
```
In this case, the result would be:
```elm
{ id = 123
, email = "sam@example.com"
, name = "(fallback if name not present)"
, percentExcited = 1.0
}
```
---
[![NoRedInk](https://cloud.githubusercontent.com/assets/1094080/9069346/99522418-3a9d-11e5-8175-1c2bfd7a2ffe.png)][team]
[team]: http://noredink.com/about/team

View File

@@ -0,0 +1,16 @@
{
"version": "3.0.0",
"summary": "A pipeline-friendly library for building JSON decoders.",
"repository": "https://github.com/NoRedInk/elm-decode-pipeline.git",
"license": "BSD-3-Clause",
"source-directories": [
"src"
],
"exposed-modules": [
"Json.Decode.Pipeline"
],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

View File

@@ -0,0 +1,19 @@
module Example exposing (..)
import Json.Decode exposing (int, string, float, Decoder)
import Json.Decode.Pipeline exposing (decode, required, optional, hardcoded)
type alias User =
{ id : Int
, name : String
, percentExcited : Float
}
userDecoder : Decoder User
userDecoder =
decode User
|> required "id" int
|> optional "name" string "(fallback if name not present)"
|> hardcoded 1.0

View File

@@ -0,0 +1,292 @@
module Json.Decode.Pipeline exposing (required, requiredAt, optional, optionalAt, resolve, decode, hardcoded, custom)
{-| # Json.Decode.Pipeline
Use the `(|>)` operator to build JSON decoders.
## Decoding fields
@docs required, requiredAt, optional, optionalAt, hardcoded, custom
## Beginning and ending pipelines
@docs decode, resolve
-}
import Json.Decode as Decode exposing (Decoder)
{-| Decode a required field.
import Json.Decode exposing (int, string, Decoder)
import Decode.Pipeline exposing (decode, required)
type alias User =
{ id : Int
, name : String
, email : String
}
userDecoder : Decoder User
userDecoder =
decode User
|> required "id" int
|> required "name" string
|> required "email" string
result : Result String User
result =
Decode.decodeString
userDecoder
"""
{"id": 123, "email": "sam@example.com", "name": "Sam"}
"""
-- Ok { id = 123, name = "Sam", email = "sam@example.com" }
-}
required : String -> Decoder a -> Decoder (a -> b) -> Decoder b
required key valDecoder decoder =
custom (Decode.field key valDecoder) decoder
{-| Decode a required nested field.
-}
requiredAt : List String -> Decoder a -> Decoder (a -> b) -> Decoder b
requiredAt path valDecoder decoder =
custom (Decode.at path valDecoder) decoder
{-| 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 exposing (int, string, null, oneOf, Decoder)
import Decode.Pipeline exposing (decode, required, optional)
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
result : Result String User
result =
Decode.decodeString
userDecoder
"""
{"id": 123, "email": "sam@example.com" }
"""
-- Ok { 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
-}
optional : String -> Decoder a -> a -> Decoder (a -> b) -> Decoder b
optional key valDecoder fallback decoder =
custom (optionalDecoder (Decode.field key Decode.value) valDecoder fallback) decoder
{-| Decode an optional nested field.
-}
optionalAt : List String -> Decoder a -> a -> Decoder (a -> b) -> Decoder b
optionalAt path valDecoder fallback decoder =
custom (optionalDecoder (Decode.at path Decode.value) valDecoder fallback) decoder
optionalDecoder : Decoder Decode.Value -> Decoder a -> a -> Decoder a
optionalDecoder pathDecoder valDecoder fallback =
let
nullOr decoder =
Decode.oneOf [ decoder, Decode.null fallback ]
handleResult input =
case Decode.decodeValue pathDecoder input of
Ok rawValue ->
-- The field was present, so now let's try to decode that value.
-- (If it was present but fails to decode, this should and will fail!)
case Decode.decodeValue (nullOr valDecoder) rawValue of
Ok finalResult ->
Decode.succeed finalResult
Err finalErr ->
Decode.fail finalErr
Err _ ->
-- The field was not present, so use the fallback.
Decode.succeed fallback
in
Decode.value
|> Decode.andThen handleResult
{-| 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 exposing (int, string, Decoder)
import Decode.Pipeline exposing (decode, required)
type alias User =
{ id : Int
, email : String
, followers : Int
}
userDecoder : Decoder User
userDecoder =
decode User
|> required "id" int
|> required "email" string
|> hardcoded 0
result : Result String User
result =
Decode.decodeString
userDecoder
"""
{"id": 123, "email": "sam@example.com"}
"""
-- Ok { id = 123, email = "sam@example.com", followers = 0 }
-}
hardcoded : a -> Decoder (a -> b) -> Decoder b
hardcoded =
Decode.succeed >> custom
{-| Run the given decoder and feed its result into the pipeline at this point.
Consider this example.
import Json.Decode exposing (int, string, at, Decoder)
import Decode.Pipeline exposing (decode, required, custom)
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
result : Result String User
result =
Decode.decodeString
userDecoder
"""
{
"id": 123,
"email": "sam@example.com",
"profile": {"name": "Sam"}
}
"""
-- Ok { id = 123, name = "Sam", email = "sam@example.com" }
-}
custom : Decoder a -> Decoder (a -> b) -> Decoder b
custom =
Decode.map2 (|>)
{-| 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 exposing (int, string, float, Decoder)
import Decode.Pipeline exposing
(decode, required, resolve)
type alias User =
{ id : Int
, email : String
}
userDecoder : Decoder User
userDecoder =
let
-- toDecoder gets run *after* all the
-- (|> required ...) steps are done.
toDecoder : Int -> String -> Int -> Decoder User
toDecoder id email version =
if version > 2 then
succeed (User id email)
else
fail "This JSON is from a deprecated source. Please upgrade!"
in
decode toDecoder
|> required "id" int
|> required "email" string
|> required "version" int -- version is part of toDecoder,
|> resolve -- but it is not a part of User
result : Result String User
result =
Decode.decodeString
userDecoder
"""
{"id": 123, "email": "sam@example.com", "version": 1}
"""
-- Err "This JSON is from a deprecated source. Please upgrade!"
-}
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.
import Json.Decode exposing (int, string, float, Decoder)
import Json.Decode.Pipeline exposing (decode, required, optional)
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

View File

@@ -0,0 +1 @@
/elm-stuff/

View File

@@ -0,0 +1,19 @@
port module Main exposing (..)
{-|
Run the tests with node-test-runner:
https://github.com/rtfeldman/node-test-runner
-}
import Tests
import Test.Runner.Node exposing (run)
import Json.Encode exposing (Value)
main : Program Never
main =
run emit Tests.all
port emit : ( String, Value ) -> Cmd msg

View File

@@ -0,0 +1,114 @@
module Tests exposing (..)
import Test exposing (..)
import Expect exposing (Expectation)
import Json.Decode.Pipeline
exposing
( decode
, required
, requiredAt
, optional
, optionalAt
, resolveResult
)
import Json.Decode exposing (Decoder, string, null)
{-| Run some JSON through a Decoder and return the result.
-}
runWith : String -> Decoder a -> Result String a
runWith =
flip Json.Decode.decodeString
isError : Result err ok -> Bool
isError result =
case result of
Err _ ->
True
Ok _ ->
False
expectErr : Result err ok -> Expectation
expectErr result =
isError result
|> Expect.true ("Expected an Err but got " ++ toString result)
all : Test
all =
describe
"Json.Decode.Pipeline"
[ test "should decode basic example" <|
\() ->
decode (,)
|> required "a" string
|> required "b" string
|> runWith """{"a":"foo","b":"bar"}"""
|> Expect.equal (Ok ( "foo", "bar" ))
, test "should decode requiredAt fields" <|
\() ->
decode (,)
|> requiredAt [ "a" ] string
|> requiredAt [ "b", "c" ] string
|> runWith """{"a":"foo","b":{"c":"bar"}}"""
|> Expect.equal (Ok ( "foo", "bar" ))
, test "should decode optionalAt fields" <|
\() ->
decode (,)
|> optionalAt [ "a", "b" ] string "--"
|> optionalAt [ "x", "y" ] string "--"
|> runWith """{"a":{},"x":{"y":"bar"}}"""
|> Expect.equal (Ok ( "--", "bar" ))
, test "optional succeeds if the field is not present" <|
\() ->
decode (,)
|> optional "a" string "--"
|> optional "x" string "--"
|> runWith """{"x":"five"}"""
|> Expect.equal (Ok ( "--", "five" ))
, test "optional succeeds with fallback if the field is present but null" <|
\() ->
decode (,)
|> optional "a" string "--"
|> optional "x" string "--"
|> runWith """{"a":null,"x":"five"}"""
|> Expect.equal (Ok ( "--", "five" ))
, test "optional succeeds with result of the given decoder if the field is null and the decoder decodes nulls" <|
\() ->
decode (,)
|> optional "a" (null "null") "--"
|> optional "x" string "--"
|> runWith """{"a":null,"x":"five"}"""
|> Expect.equal (Ok ( "null", "five" ))
, test "optional fails if the field is present but doesn't decode" <|
\() ->
decode (,)
|> optional "a" string "--"
|> optional "x" string "--"
|> runWith """{"x":5}"""
|> expectErr
, test "optionalAt fails if the field is present but doesn't decode" <|
\() ->
decode (,)
|> optionalAt [ "a", "b" ] string "--"
|> optionalAt [ "x", "y" ] string "--"
|> runWith """{"a":{},"x":{"y":5}}"""
|> expectErr
, test "resolveResult bubbles up decoded Err results" <|
\() ->
decode Err
|> required "error" string
|> resolveResult
|> runWith """{"error":"invalid"}"""
|> expectErr
, test "resolveResult bubbles up decoded Ok results" <|
\() ->
decode Ok
|> required "ok" string
|> resolveResult
|> runWith """{"ok":"valid"}"""
|> Expect.equal (Ok "valid")
]

View File

@@ -0,0 +1,17 @@
{
"version": "1.0.0",
"summary": "Sample Elm Test",
"repository": "https://github.com/user/project.git",
"license": "BSD-3-Clause",
"source-directories": [
".",
"../src"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0",
"elm-community/elm-test": "2.0.0 <= v < 3.0.0",
"rtfeldman/node-test-runner": "1.0.0 <= v < 2.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}