Add DataSource.validate.

This commit is contained in:
Dillon Kearns 2021-06-16 15:23:16 -07:00
parent e83d6caf8a
commit b809af6e45
5 changed files with 167 additions and 41 deletions

View File

@ -5,6 +5,7 @@ module DataSource exposing
, Body, emptyBody, stringBody, jsonBody
, andThen, resolve, combine
, map2, map3, map4, map5, map6, map7, map8, map9
, validate
)
{-| StaticHttp requests are an alternative to doing Elm HTTP requests the traditional way using the `elm/http` package.
@ -63,7 +64,8 @@ and describe your use case!
import Dict exposing (Dict)
import Dict.Extra
import Json.Encode as Encode
import Pages.Internal.ApplicationType exposing (ApplicationType)
import KeepOrDiscard exposing (KeepOrDiscard)
import Pages.Internal.ApplicationType as ApplicationType exposing (ApplicationType)
import Pages.Internal.StaticHttpBody as Body
import Pages.Secrets
import Pages.StaticHttp.Request as HashRequest
@ -144,14 +146,56 @@ map fn requestInfo =
Request partiallyStripped ( urls, lookupFn ) ->
Request partiallyStripped
( urls
, \appType rawResponses ->
map fn (lookupFn appType rawResponses)
, \keepOrDiscard appType rawResponses ->
map fn (lookupFn keepOrDiscard appType rawResponses)
)
Done stripped value ->
Done stripped (fn value)
dontSaveData : DataSource a -> DataSource a
dontSaveData requestInfo =
case requestInfo of
RequestError _ ->
requestInfo
Request partiallyStripped ( urls, lookupFn ) ->
Request partiallyStripped
( urls
, \keepOrDiscard appType rawResponses ->
lookupFn KeepOrDiscard.Discard appType rawResponses
)
Done _ _ ->
requestInfo
validate :
(unvalidated -> validated)
-> (unvalidated -> DataSource (Result String ()))
-> DataSource unvalidated
-> DataSource validated
validate markValidated validateDataSource unvalidatedDataSource =
unvalidatedDataSource
|> andThen
(\unvalidated ->
unvalidated
|> validateDataSource
|> andThen
(\result ->
case result of
Ok () ->
succeed <| markValidated unvalidated
Err error ->
fail error
)
|> dontSaveData
)
|> dontSaveData
{-| Helper to remove an inner layer of Request wrapping.
-}
resolve : DataSource (List (DataSource value)) -> DataSource (List value)
@ -234,28 +278,28 @@ map2 fn request1 request2 =
( Request newDict1 ( urls1, lookupFn1 ), Request newDict2 ( urls2, lookupFn2 ) ) ->
Request (combineReducedDicts newDict1 newDict2)
( urls1 ++ urls2
, \appType rawResponses ->
, \keepOrDiscard appType rawResponses ->
map2 fn
(lookupFn1 appType rawResponses)
(lookupFn2 appType rawResponses)
(lookupFn1 keepOrDiscard appType rawResponses)
(lookupFn2 keepOrDiscard appType rawResponses)
)
( Request dict1 ( urls1, lookupFn1 ), Done stripped2 value2 ) ->
Request dict1
( urls1
, \appType rawResponses ->
, \keepOrDiscard appType rawResponses ->
map2 fn
(lookupFn1 appType rawResponses)
(lookupFn1 keepOrDiscard appType rawResponses)
(Done stripped2 value2)
)
( Done stripped2 value2, Request dict1 ( urls1, lookupFn1 ) ) ->
Request dict1
( urls1
, \appType rawResponses ->
, \keepOrDiscard appType rawResponses ->
map2 fn
(Done stripped2 value2)
(lookupFn1 appType rawResponses)
(lookupFn1 keepOrDiscard appType rawResponses)
)
( Done stripped1 value1, Done stripped2 value2 ) ->
@ -274,21 +318,22 @@ combineReducedDicts dict1 dict2 =
|> Dict.Extra.fromListDedupe Pages.StaticHttpRequest.merge
lookup : ApplicationType -> DataSource value -> RequestsAndPending -> Result Pages.StaticHttpRequest.Error ( Dict String WhatToDo, value )
lookup : KeepOrDiscard -> ApplicationType -> DataSource value -> RequestsAndPending -> Result Pages.StaticHttpRequest.Error ( Dict String WhatToDo, value )
lookup =
lookupHelp Dict.empty
lookupHelp : Dict String WhatToDo -> ApplicationType -> DataSource value -> RequestsAndPending -> Result Pages.StaticHttpRequest.Error ( Dict String WhatToDo, value )
lookupHelp strippedSoFar appType requestInfo rawResponses =
lookupHelp : Dict String WhatToDo -> KeepOrDiscard -> ApplicationType -> DataSource value -> RequestsAndPending -> Result Pages.StaticHttpRequest.Error ( Dict String WhatToDo, value )
lookupHelp strippedSoFar keepOrDiscard appType requestInfo rawResponses =
case requestInfo of
RequestError error ->
Err error
Request strippedResponses ( urls, lookupFn ) ->
lookupHelp (combineReducedDicts strippedResponses strippedSoFar)
keepOrDiscard
appType
(addUrls urls (lookupFn appType rawResponses))
(addUrls urls (lookupFn keepOrDiscard appType rawResponses))
rawResponses
Done stripped value ->
@ -354,8 +399,10 @@ andThen fn requestInfo =
-- TODO should this be non-empty Dict? Or should it be passed down some other way?
Request Dict.empty
( lookupUrls requestInfo
, \appType rawResponses ->
lookup appType
, \keepOrDiscard appType rawResponses ->
lookup
keepOrDiscard
appType
requestInfo
rawResponses
|> (\result ->
@ -407,7 +454,7 @@ succeed : a -> DataSource a
succeed value =
Request Dict.empty
( []
, \_ _ ->
, \_ _ _ ->
Done Dict.empty value
)

View File

@ -69,6 +69,7 @@ import Internal.OptimizedDecoder
import Json.Decode
import Json.Decode.Exploration
import Json.Encode as Encode
import KeepOrDiscard
import OptimizedDecoder as Decode exposing (Decoder)
import Pages.Internal.ApplicationType as ApplicationType
import Pages.Internal.StaticHttpBody as Body
@ -246,7 +247,7 @@ unoptimizedRequest requestWithSecrets expect =
ExpectJson decoder ->
Request Dict.empty
( [ requestWithSecrets ]
, \appType rawResponseDict ->
, \keepOrDiscard appType rawResponseDict ->
case appType of
ApplicationType.Cli ->
rawResponseDict
@ -256,8 +257,13 @@ unoptimizedRequest requestWithSecrets expect =
Just rawResponse ->
Ok
( Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
(Pages.StaticHttpRequest.StripResponse
(Decode.map (\_ -> ()) decoder)
(case keepOrDiscard of
KeepOrDiscard.Keep ->
Pages.StaticHttpRequest.StripResponse
(Decode.map (\_ -> ()) decoder)
KeepOrDiscard.Discard ->
Pages.StaticHttpRequest.CliOnly
)
, rawResponse
)
@ -291,12 +297,20 @@ unoptimizedRequest requestWithSecrets expect =
)
|> Result.map
(\finalRequest ->
( strippedResponses
|> Dict.insert
(Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
(Pages.StaticHttpRequest.StripResponse
(Decode.map (\_ -> ()) decoder)
)
( case keepOrDiscard of
KeepOrDiscard.Keep ->
strippedResponses
|> Dict.insert
(Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
(Pages.StaticHttpRequest.StripResponse
(Decode.map (\_ -> ()) decoder)
)
KeepOrDiscard.Discard ->
strippedResponses
|> Dict.insert
(Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
Pages.StaticHttpRequest.CliOnly
, finalRequest
)
)
@ -310,7 +324,8 @@ unoptimizedRequest requestWithSecrets expect =
case maybeResponse of
Just rawResponse ->
Ok
( Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
( -- TODO should this be an empty Dict? Shouldn't matter in the browser.
Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
Pages.StaticHttpRequest.UseRawResponse
, rawResponse
)
@ -353,14 +368,15 @@ unoptimizedRequest requestWithSecrets expect =
ExpectUnoptimizedJson decoder ->
Request Dict.empty
( [ requestWithSecrets ]
, \_ rawResponseDict ->
, \keepOrDiscard _ rawResponseDict ->
rawResponseDict
|> RequestsAndPending.get (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|> (\maybeResponse ->
case maybeResponse of
Just rawResponse ->
Ok
( Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
( -- TODO check keepOrDiscard
Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
Pages.StaticHttpRequest.UseRawResponse
, rawResponse
)
@ -388,7 +404,8 @@ unoptimizedRequest requestWithSecrets expect =
)
|> Result.map
(\finalRequest ->
( strippedResponses
( -- TODO check keepOrDiscard
strippedResponses
|> Dict.insert
(Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
Pages.StaticHttpRequest.UseRawResponse
@ -402,14 +419,15 @@ unoptimizedRequest requestWithSecrets expect =
ExpectString mapStringFn ->
Request Dict.empty
( [ requestWithSecrets ]
, \_ rawResponseDict ->
, \keepOrDiscard _ rawResponseDict ->
rawResponseDict
|> RequestsAndPending.get (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|> (\maybeResponse ->
case maybeResponse of
Just rawResponse ->
Ok
( Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash) Pages.StaticHttpRequest.UseRawResponse
( -- TODO check keepOrDiscard
Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash) Pages.StaticHttpRequest.UseRawResponse
, rawResponse
)
@ -426,7 +444,8 @@ unoptimizedRequest requestWithSecrets expect =
|> Result.mapError Pages.StaticHttpRequest.DecoderError
|> Result.map
(\finalRequest ->
( strippedResponses
( -- TODO check keepOrDiscard
strippedResponses
|> Dict.insert (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash) Pages.StaticHttpRequest.UseRawResponse
, finalRequest
)

6
src/KeepOrDiscard.elm Normal file
View File

@ -0,0 +1,6 @@
module KeepOrDiscard exposing (KeepOrDiscard(..))
type KeepOrDiscard
= Keep
| Discard

View File

@ -2,8 +2,10 @@ module Pages.StaticHttpRequest exposing (Error(..), RawRequest(..), Status(..),
import BuildError exposing (BuildError)
import Dict exposing (Dict)
import Dict.Extra
import Internal.OptimizedDecoder
import Json.Decode.Exploration
import KeepOrDiscard exposing (KeepOrDiscard)
import OptimizedDecoder
import Pages.Internal.ApplicationType exposing (ApplicationType)
import Pages.StaticHttp.Request
@ -16,7 +18,7 @@ type RawRequest value
= Request
(Dict String WhatToDo)
( List (Secrets.Value Pages.StaticHttp.Request.Request)
, ApplicationType -> RequestsAndPending -> RawRequest value
, KeepOrDiscard -> ApplicationType -> RequestsAndPending -> RawRequest value
)
| RequestError Error
| Done (Dict String WhatToDo) value
@ -24,6 +26,7 @@ type RawRequest value
type WhatToDo
= UseRawResponse
| CliOnly
| StripResponse (OptimizedDecoder.Decoder ())
@ -39,7 +42,13 @@ merge whatToDo1 whatToDo2 =
( _, StripResponse strip1 ) ->
StripResponse strip1
_ ->
( _, CliOnly ) ->
whatToDo1
( CliOnly, _ ) ->
whatToDo2
( UseRawResponse, UseRawResponse ) ->
UseRawResponse
@ -51,13 +60,14 @@ strippedResponses =
strippedResponsesEncode : ApplicationType -> RawRequest value -> RequestsAndPending -> Dict String String
strippedResponsesEncode appType rawRequest requestsAndPending =
strippedResponses appType rawRequest requestsAndPending
|> Dict.map
|> Dict.Extra.filterMap
(\k whatToDo ->
case whatToDo of
UseRawResponse ->
Dict.get k requestsAndPending
|> Maybe.withDefault Nothing
|> Maybe.withDefault ""
|> Just
StripResponse decoder ->
Dict.get k requestsAndPending
@ -65,6 +75,10 @@ strippedResponsesEncode appType rawRequest requestsAndPending =
|> Maybe.withDefault ""
|> Json.Decode.Exploration.stripString (Internal.OptimizedDecoder.jde decoder)
|> Result.withDefault "ERROR"
|> Just
CliOnly ->
Nothing
)
@ -75,7 +89,7 @@ strippedResponsesHelp usedSoFar appType request rawResponses =
usedSoFar
Request partiallyStrippedResponses ( _, lookupFn ) ->
case lookupFn appType rawResponses of
case lookupFn KeepOrDiscard.Keep appType rawResponses of
followupRequest ->
strippedResponsesHelp
(Dict.merge
@ -144,7 +158,7 @@ resolve appType request rawResponses =
Err error
Request _ ( _, lookupFn ) ->
case lookupFn appType rawResponses of
case lookupFn KeepOrDiscard.Keep appType rawResponses of
nextRequest ->
resolve appType nextRequest rawResponses
@ -166,7 +180,7 @@ resolveUrlsHelp appType request rawResponses soFar =
)
Request _ ( urlList, lookupFn ) ->
case lookupFn appType rawResponses of
case lookupFn KeepOrDiscard.Keep appType rawResponses of
nextRequest ->
resolveUrlsHelp appType nextRequest rawResponses (soFar ++ urlList)
@ -210,7 +224,7 @@ cacheRequestResolutionHelp foundUrls appType request rawResponses =
HasPermanentError error
Request _ ( urlList, lookupFn ) ->
case lookupFn appType rawResponses of
case lookupFn KeepOrDiscard.Keep appType rawResponses of
nextRequest ->
cacheRequestResolutionHelp urlList appType nextRequest rawResponses

View File

@ -29,7 +29,7 @@ import SimulatedEffect.Cmd
import SimulatedEffect.Http as Http
import SimulatedEffect.Ports
import SimulatedEffect.Task
import Test exposing (Test, describe, test)
import Test exposing (Test, describe, only, test)
import Test.Http
@ -767,6 +767,46 @@ TODO
]
)
]
, test "validate DataSource is not stored for any pages" <|
\() ->
startWithRoutes [ "hello" ]
[ [ "hello" ] ]
[]
[ ( [ "hello" ]
, DataSource.succeed "hello"
|> DataSource.validate identity
(\word ->
DataSource.Http.get (Secrets.succeed ("https://api.spellchecker.com?word=" ++ word))
(Decode.field "isCorrect" Decode.bool
|> Decode.map
(\isCorrect ->
if isCorrect then
Ok ()
else
Err "Spelling error"
)
)
)
|> DataSource.map (\_ -> ())
)
]
|> ProgramTest.simulateHttpOk
"GET"
"https://api.spellchecker.com?word=hello"
"""{ "isCorrect": true }"""
|> ProgramTest.expectOutgoingPortValues
"toJsPort"
(Codec.decoder (ToJsPayload.successCodecNew2 "" ""))
(\actualPorts ->
case actualPorts of
[ ToJsPayload.PageProgress portData ] ->
portData.contentJson
|> Expect.equalDicts Dict.empty
_ ->
Expect.fail <| "Expected exactly 1 port of type PageProgress. Instead, got \n" ++ Debug.toString actualPorts
)
]
, describe "generateFiles"
[ test "initial requests are sent out" <|