elm-pages 2.1.7 → 2.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/generator/review/elm.json +34 -0
  2. package/generator/review/src/ReviewConfig.elm +10 -0
  3. package/generator/src/basepath-middleware.js +15 -9
  4. package/generator/src/build.js +77 -4
  5. package/generator/src/cli.js +13 -9
  6. package/generator/src/compile-elm.js +43 -0
  7. package/generator/src/dev-server.js +63 -11
  8. package/generator/src/error-formatter.js +62 -9
  9. package/generator/src/generate-template-module-connector.js +17 -4
  10. package/generator/src/init.js +4 -0
  11. package/generator/src/pre-render-html.js +19 -12
  12. package/generator/src/render-worker.js +0 -1
  13. package/generator/src/render.js +1 -2
  14. package/generator/src/seo-renderer.js +21 -2
  15. package/generator/static-code/hmr.js +43 -6
  16. package/generator/template/elm.json +13 -5
  17. package/generator/template/package.json +3 -2
  18. package/package.json +14 -8
  19. package/src/ApiRoute.elm +178 -0
  20. package/src/AriaLiveAnnouncer.elm +36 -0
  21. package/src/BuildError.elm +60 -0
  22. package/src/DataSource/File.elm +288 -0
  23. package/src/DataSource/Glob.elm +1050 -0
  24. package/src/DataSource/Http.elm +467 -0
  25. package/src/DataSource/Internal/Glob.elm +74 -0
  26. package/src/DataSource/Port.elm +87 -0
  27. package/src/DataSource/ServerRequest.elm +60 -0
  28. package/src/DataSource.elm +801 -0
  29. package/src/Head/Seo.elm +516 -0
  30. package/src/Head/Twitter.elm +109 -0
  31. package/src/Head.elm +452 -0
  32. package/src/HtmlPrinter.elm +27 -0
  33. package/src/Internal/ApiRoute.elm +89 -0
  34. package/src/Internal/OptimizedDecoder.elm +18 -0
  35. package/src/KeepOrDiscard.elm +6 -0
  36. package/src/OptimizedDecoder/Pipeline.elm +335 -0
  37. package/src/OptimizedDecoder.elm +818 -0
  38. package/src/Pages/ContentCache.elm +248 -0
  39. package/src/Pages/Flags.elm +26 -0
  40. package/src/Pages/Http.elm +10 -0
  41. package/src/Pages/Internal/ApplicationType.elm +6 -0
  42. package/src/Pages/Internal/NotFoundReason.elm +256 -0
  43. package/src/Pages/Internal/Platform/Cli.elm +1015 -0
  44. package/src/Pages/Internal/Platform/Effect.elm +14 -0
  45. package/src/Pages/Internal/Platform/StaticResponses.elm +540 -0
  46. package/src/Pages/Internal/Platform/ToJsPayload.elm +138 -0
  47. package/src/Pages/Internal/Platform.elm +745 -0
  48. package/src/Pages/Internal/RoutePattern.elm +122 -0
  49. package/src/Pages/Internal/Router.elm +116 -0
  50. package/src/Pages/Internal/StaticHttpBody.elm +54 -0
  51. package/src/Pages/Internal/String.elm +39 -0
  52. package/src/Pages/Manifest/Category.elm +240 -0
  53. package/src/Pages/Manifest.elm +412 -0
  54. package/src/Pages/PageUrl.elm +38 -0
  55. package/src/Pages/ProgramConfig.elm +73 -0
  56. package/src/Pages/Review/NoContractViolations.elm +397 -0
  57. package/src/Pages/Secrets.elm +83 -0
  58. package/src/Pages/SiteConfig.elm +13 -0
  59. package/src/Pages/StaticHttp/Request.elm +42 -0
  60. package/src/Pages/StaticHttpRequest.elm +320 -0
  61. package/src/Pages/Url.elm +60 -0
  62. package/src/Path.elm +96 -0
  63. package/src/QueryParams.elm +216 -0
  64. package/src/RenderRequest.elm +163 -0
  65. package/src/RequestsAndPending.elm +20 -0
  66. package/src/Secrets.elm +111 -0
  67. package/src/SecretsDict.elm +45 -0
  68. package/src/StructuredData.elm +236 -0
  69. package/src/TerminalText.elm +242 -0
  70. package/src/Test/Html/Internal/ElmHtml/Constants.elm +53 -0
  71. package/src/Test/Html/Internal/ElmHtml/Helpers.elm +17 -0
  72. package/src/Test/Html/Internal/ElmHtml/InternalTypes.elm +529 -0
  73. package/src/Test/Html/Internal/ElmHtml/Markdown.elm +56 -0
  74. package/src/Test/Html/Internal/ElmHtml/ToString.elm +197 -0
  75. package/src/Test/Internal/KernelConstants.elm +34 -0
@@ -0,0 +1,320 @@
1
+ module Pages.StaticHttpRequest exposing (Error(..), RawRequest(..), Status(..), WhatToDo(..), cacheRequestResolution, merge, resolve, resolveUrls, strippedResponsesEncode, toBuildError)
2
+
3
+ import BuildError exposing (BuildError)
4
+ import Dict exposing (Dict)
5
+ import Internal.OptimizedDecoder
6
+ import Json.Decode.Exploration
7
+ import Json.Encode
8
+ import KeepOrDiscard exposing (KeepOrDiscard)
9
+ import List.Extra
10
+ import OptimizedDecoder
11
+ import Pages.Internal.ApplicationType exposing (ApplicationType)
12
+ import Pages.StaticHttp.Request
13
+ import RequestsAndPending exposing (RequestsAndPending)
14
+ import Secrets
15
+ import TerminalText as Terminal
16
+
17
+
18
+ type RawRequest value
19
+ = Request (Dict String WhatToDo) ( List (Secrets.Value Pages.StaticHttp.Request.Request), KeepOrDiscard -> ApplicationType -> RequestsAndPending -> RawRequest value )
20
+ | RequestError Error
21
+ | ApiRoute (Dict String WhatToDo) value
22
+
23
+
24
+ type WhatToDo
25
+ = UseRawResponse
26
+ | CliOnly
27
+ | StripResponse (OptimizedDecoder.Decoder ())
28
+ | DistilledResponse Json.Encode.Value
29
+ | Error (List BuildError)
30
+
31
+
32
+ merge : String -> WhatToDo -> WhatToDo -> WhatToDo
33
+ merge key whatToDo1 whatToDo2 =
34
+ case ( whatToDo1, whatToDo2 ) of
35
+ ( Error buildErrors1, Error buildErrors2 ) ->
36
+ Error (buildErrors1 ++ buildErrors2)
37
+
38
+ ( Error buildErrors1, _ ) ->
39
+ Error buildErrors1
40
+
41
+ ( _, Error buildErrors1 ) ->
42
+ Error buildErrors1
43
+
44
+ ( StripResponse strip1, StripResponse strip2 ) ->
45
+ StripResponse (OptimizedDecoder.map2 (\_ _ -> ()) strip1 strip2)
46
+
47
+ ( StripResponse strip1, _ ) ->
48
+ StripResponse strip1
49
+
50
+ ( _, StripResponse strip1 ) ->
51
+ StripResponse strip1
52
+
53
+ ( _, CliOnly ) ->
54
+ whatToDo1
55
+
56
+ ( CliOnly, _ ) ->
57
+ whatToDo2
58
+
59
+ ( DistilledResponse distilled1, DistilledResponse distilled2 ) ->
60
+ if Json.Encode.encode 0 distilled1 == Json.Encode.encode 0 distilled2 then
61
+ DistilledResponse distilled1
62
+
63
+ else
64
+ Error
65
+ [ { title = "Non-Unique Distill Keys"
66
+ , message =
67
+ [ Terminal.text "I encountered DataSource.distill with two matching keys that had differing encoded values.\n\n"
68
+ , Terminal.text "Look for "
69
+ , Terminal.red <| "DataSource.distill"
70
+ , Terminal.text " with the key "
71
+ , Terminal.red <| ("\"" ++ key ++ "\"")
72
+ , Terminal.text "\n\n"
73
+ , Terminal.yellow <| "The first encoded value was:\n"
74
+ , Terminal.text <| Json.Encode.encode 2 distilled1
75
+ , Terminal.text "\n\n-------------------------------\n\n"
76
+ , Terminal.yellow <| "The second encoded value was:\n"
77
+ , Terminal.text <| Json.Encode.encode 2 distilled2
78
+ ]
79
+ , path = "" -- TODO wire in path here?
80
+ , fatal = True
81
+ }
82
+ ]
83
+
84
+ ( DistilledResponse distilled1, _ ) ->
85
+ DistilledResponse distilled1
86
+
87
+ ( _, DistilledResponse distilled1 ) ->
88
+ DistilledResponse distilled1
89
+
90
+ ( UseRawResponse, UseRawResponse ) ->
91
+ UseRawResponse
92
+
93
+
94
+ strippedResponses : ApplicationType -> RawRequest value -> RequestsAndPending -> Dict String WhatToDo
95
+ strippedResponses =
96
+ strippedResponsesHelp Dict.empty
97
+
98
+
99
+ strippedResponsesEncode : ApplicationType -> RawRequest value -> RequestsAndPending -> Result (List BuildError) (Dict String String)
100
+ strippedResponsesEncode appType rawRequest requestsAndPending =
101
+ strippedResponses appType rawRequest requestsAndPending
102
+ |> Dict.toList
103
+ |> List.map
104
+ (\( k, whatToDo ) ->
105
+ (case whatToDo of
106
+ UseRawResponse ->
107
+ Dict.get k requestsAndPending
108
+ |> Maybe.withDefault Nothing
109
+ |> Maybe.withDefault ""
110
+ |> Just
111
+ |> Ok
112
+
113
+ StripResponse decoder ->
114
+ Dict.get k requestsAndPending
115
+ |> Maybe.withDefault Nothing
116
+ |> Maybe.withDefault ""
117
+ |> Json.Decode.Exploration.stripString (Internal.OptimizedDecoder.jde decoder)
118
+ |> Result.withDefault "ERROR"
119
+ |> Just
120
+ |> Ok
121
+
122
+ CliOnly ->
123
+ Nothing
124
+ |> Ok
125
+
126
+ DistilledResponse value ->
127
+ value
128
+ |> Json.Encode.encode 0
129
+ |> Just
130
+ |> Ok
131
+
132
+ Error buildError ->
133
+ Err buildError
134
+ )
135
+ |> Result.map (Maybe.map (Tuple.pair k))
136
+ )
137
+ |> combineMultipleErrors
138
+ |> Result.map (List.filterMap identity)
139
+ |> Result.map Dict.fromList
140
+
141
+
142
+ combineMultipleErrors : List (Result (List error) a) -> Result (List error) (List a)
143
+ combineMultipleErrors results =
144
+ List.foldr
145
+ (\result soFarResult ->
146
+ case soFarResult of
147
+ Ok soFarOk ->
148
+ case result of
149
+ Ok value ->
150
+ value :: soFarOk |> Ok
151
+
152
+ Err error_ ->
153
+ Err error_
154
+
155
+ Err errorsSoFar ->
156
+ case result of
157
+ Ok _ ->
158
+ Err errorsSoFar
159
+
160
+ Err error_ ->
161
+ Err <| error_ ++ errorsSoFar
162
+ )
163
+ (Ok [])
164
+ results
165
+
166
+
167
+ strippedResponsesHelp : Dict String WhatToDo -> ApplicationType -> RawRequest value -> RequestsAndPending -> Dict String WhatToDo
168
+ strippedResponsesHelp usedSoFar appType request rawResponses =
169
+ case request of
170
+ RequestError _ ->
171
+ usedSoFar
172
+
173
+ Request partiallyStrippedResponses ( _, lookupFn ) ->
174
+ case lookupFn KeepOrDiscard.Keep appType rawResponses of
175
+ followupRequest ->
176
+ strippedResponsesHelp
177
+ (Dict.merge
178
+ (\key a -> Dict.insert key a)
179
+ (\key a b -> Dict.insert key (merge key a b))
180
+ (\key b -> Dict.insert key b)
181
+ usedSoFar
182
+ partiallyStrippedResponses
183
+ Dict.empty
184
+ )
185
+ appType
186
+ followupRequest
187
+ rawResponses
188
+
189
+ ApiRoute partiallyStrippedResponses _ ->
190
+ Dict.merge
191
+ (\key a -> Dict.insert key a)
192
+ (\key a b -> Dict.insert key (merge key a b))
193
+ (\key b -> Dict.insert key b)
194
+ usedSoFar
195
+ partiallyStrippedResponses
196
+ Dict.empty
197
+
198
+
199
+ type Error
200
+ = MissingHttpResponse String (List (Secrets.Value Pages.StaticHttp.Request.Request))
201
+ | DecoderError String
202
+ | UserCalledStaticHttpFail String
203
+
204
+
205
+ toBuildError : String -> Error -> BuildError
206
+ toBuildError path error =
207
+ case error of
208
+ MissingHttpResponse missingKey _ ->
209
+ { title = "Missing Http Response"
210
+ , message =
211
+ [ Terminal.text missingKey
212
+ ]
213
+ , path = path
214
+ , fatal = True
215
+ }
216
+
217
+ DecoderError decodeErrorMessage ->
218
+ { title = "Static Http Decoding Error"
219
+ , message =
220
+ [ Terminal.text decodeErrorMessage
221
+ ]
222
+ , path = path
223
+ , fatal = True
224
+ }
225
+
226
+ UserCalledStaticHttpFail decodeErrorMessage ->
227
+ { title = "Called Static Http Fail"
228
+ , message =
229
+ [ Terminal.text <| "I ran into a call to `DataSource.fail` with message: " ++ decodeErrorMessage
230
+ ]
231
+ , path = path
232
+ , fatal = True
233
+ }
234
+
235
+
236
+ resolve : ApplicationType -> RawRequest value -> RequestsAndPending -> Result Error value
237
+ resolve appType request rawResponses =
238
+ case request of
239
+ RequestError error ->
240
+ Err error
241
+
242
+ Request _ ( _, lookupFn ) ->
243
+ case lookupFn KeepOrDiscard.Keep appType rawResponses of
244
+ nextRequest ->
245
+ resolve appType nextRequest rawResponses
246
+
247
+ ApiRoute _ value ->
248
+ Ok value
249
+
250
+
251
+ resolveUrls : ApplicationType -> RawRequest value -> RequestsAndPending -> List (Secrets.Value Pages.StaticHttp.Request.Request)
252
+ resolveUrls appType request rawResponses =
253
+ resolveUrlsHelp appType rawResponses [] request
254
+
255
+
256
+ resolveUrlsHelp : ApplicationType -> RequestsAndPending -> List (Secrets.Value Pages.StaticHttp.Request.Request) -> RawRequest value -> List (Secrets.Value Pages.StaticHttp.Request.Request)
257
+ resolveUrlsHelp appType rawResponses soFar request =
258
+ case request of
259
+ RequestError error ->
260
+ case error of
261
+ MissingHttpResponse _ next ->
262
+ (soFar ++ next)
263
+ |> List.Extra.uniqueBy (Secrets.maskedLookup >> Pages.StaticHttp.Request.hash)
264
+
265
+ _ ->
266
+ soFar
267
+
268
+ Request _ ( urlList, lookupFn ) ->
269
+ resolveUrlsHelp appType
270
+ rawResponses
271
+ (soFar ++ urlList)
272
+ (lookupFn KeepOrDiscard.Keep appType rawResponses)
273
+
274
+ ApiRoute _ _ ->
275
+ soFar
276
+
277
+
278
+ cacheRequestResolution :
279
+ ApplicationType
280
+ -> RawRequest value
281
+ -> RequestsAndPending
282
+ -> Status value
283
+ cacheRequestResolution appType request rawResponses =
284
+ cacheRequestResolutionHelp [] appType rawResponses request
285
+
286
+
287
+ type Status value
288
+ = Incomplete (List (Secrets.Value Pages.StaticHttp.Request.Request))
289
+ | HasPermanentError Error
290
+ | Complete
291
+
292
+
293
+ cacheRequestResolutionHelp :
294
+ List (Secrets.Value Pages.StaticHttp.Request.Request)
295
+ -> ApplicationType
296
+ -> RequestsAndPending
297
+ -> RawRequest value
298
+ -> Status value
299
+ cacheRequestResolutionHelp foundUrls appType rawResponses request =
300
+ case request of
301
+ RequestError error ->
302
+ case error of
303
+ MissingHttpResponse _ _ ->
304
+ -- TODO do I need to pass through continuation URLs here? -- Incomplete (urlList ++ foundUrls)
305
+ Incomplete foundUrls
306
+
307
+ DecoderError _ ->
308
+ HasPermanentError error
309
+
310
+ UserCalledStaticHttpFail _ ->
311
+ HasPermanentError error
312
+
313
+ Request _ ( urlList, lookupFn ) ->
314
+ cacheRequestResolutionHelp urlList
315
+ appType
316
+ rawResponses
317
+ (lookupFn KeepOrDiscard.Keep appType rawResponses)
318
+
319
+ ApiRoute _ _ ->
320
+ Complete
@@ -0,0 +1,60 @@
1
+ module Pages.Url exposing (Url, external, fromPath, toAbsoluteUrl, toString)
2
+
3
+ {-| Some of the `elm-pages` APIs will take internal URLs and ensure that they have the `canonicalSiteUrl` prepended.
4
+
5
+ That's the purpose for this type. If you have an external URL, like `Pages.Url.external "https://google.com"`,
6
+ then the canonicalUrl will not be prepended when it is used in a head tag.
7
+
8
+ If you refer to a local page, like `Route.Index |> Route.toPath |> Pages.Url.fromPath`, or `Pages.Url.fromPath`
9
+
10
+ @docs Url, external, fromPath, toAbsoluteUrl, toString
11
+
12
+ -}
13
+
14
+ import Pages.Internal.String as String
15
+ import Path exposing (Path)
16
+
17
+
18
+ {-| -}
19
+ type Url
20
+ = Internal String
21
+ | External String
22
+
23
+
24
+ {-| -}
25
+ fromPath : Path -> Url
26
+ fromPath path =
27
+ path |> Path.toAbsolute |> Internal
28
+
29
+
30
+ {-| -}
31
+ external : String -> Url
32
+ external externalUrl =
33
+ External externalUrl
34
+
35
+
36
+ {-| -}
37
+ toString : Url -> String
38
+ toString path =
39
+ case path of
40
+ Internal rawPath ->
41
+ rawPath
42
+
43
+ External url ->
44
+ url
45
+
46
+
47
+ {-| -}
48
+ toAbsoluteUrl : String -> Url -> String
49
+ toAbsoluteUrl canonicalSiteUrl url =
50
+ case url of
51
+ External externalUrl ->
52
+ externalUrl
53
+
54
+ Internal internalUrl ->
55
+ join canonicalSiteUrl internalUrl
56
+
57
+
58
+ join : String -> String -> String
59
+ join base path =
60
+ String.chopEnd "/" base ++ "/" ++ String.chopStart "/" path
package/src/Path.elm ADDED
@@ -0,0 +1,96 @@
1
+ module Path exposing
2
+ ( Path, join, fromString
3
+ , toAbsolute, toRelative, toSegments
4
+ )
5
+
6
+ {-| Represents the path portion of a URL (not query parameters, fragment, protocol, port, etc.).
7
+
8
+ This helper lets you combine together path parts without worrying about having too many or too few slashes.
9
+ These two examples will result in the same URL, even though the first example has trailing and leading slashes, and the
10
+ second does not.
11
+
12
+ Path.join [ "/blog/", "/post-1/" ]
13
+ |> Path.toAbsolute
14
+ --> "/blog/post-1"
15
+
16
+ Path.join [ "blog", "post-1" ]
17
+ |> Path.toAbsolute
18
+ --> "/blog/post-1"
19
+
20
+ We can also safely join Strings that include multiple path parts, a single path part per string, or a mix of the two:
21
+
22
+ Path.join [ "/articles/archive/", "1977", "06", "10", "post-1" ]
23
+ |> Path.toAbsolute
24
+ --> "/articles/archive/1977/06/10/post-1"
25
+
26
+
27
+ ## Creating Paths
28
+
29
+ @docs Path, join, fromString
30
+
31
+
32
+ ## Turning Paths to String
33
+
34
+ @docs toAbsolute, toRelative, toSegments
35
+
36
+ -}
37
+
38
+ import Pages.Internal.String exposing (chopEnd, chopStart)
39
+
40
+
41
+ {-| The path portion of the URL, normalized to ensure that path segments are joined with `/`s in the right places (no doubled up or missing slashes).
42
+ -}
43
+ type Path
44
+ = Path String
45
+
46
+
47
+ {-| Create a Path from multiple path parts. Each part can either be a single path segment, like `blog`, or a
48
+ multi-part path part, like `blog/post-1`.
49
+ -}
50
+ join : List String -> Path
51
+ join parts =
52
+ parts
53
+ |> List.map normalize
54
+ |> String.join "/"
55
+ |> Path
56
+
57
+
58
+ {-| Create a Path from a path String.
59
+
60
+ Path.fromString "blog/post-1/"
61
+ |> Path.toAbsolute
62
+ |> Expect.equal "/blog/post-1"
63
+
64
+ -}
65
+ fromString : String -> Path
66
+ fromString path =
67
+ path
68
+ |> normalize
69
+ |> Path
70
+
71
+
72
+ {-| -}
73
+ toSegments : Path -> List String
74
+ toSegments (Path path) =
75
+ path |> String.split "/" |> List.filter ((/=) "")
76
+
77
+
78
+ {-| Turn a Path to an absolute URL (with no trailing slash).
79
+ -}
80
+ toAbsolute : Path -> String
81
+ toAbsolute (Path path) =
82
+ "/" ++ path
83
+
84
+
85
+ {-| Turn a Path to a relative URL.
86
+ -}
87
+ toRelative : Path -> String
88
+ toRelative (Path path) =
89
+ path
90
+
91
+
92
+ normalize : String -> String
93
+ normalize pathPart =
94
+ pathPart
95
+ |> chopEnd "/"
96
+ |> chopStart "/"
@@ -0,0 +1,216 @@
1
+ module QueryParams exposing
2
+ ( QueryParams
3
+ , Parser
4
+ , andThen, fail, fromResult, fromString, optionalString, parse, string, strings, succeed
5
+ , map2, oneOf
6
+ , toDict, toString
7
+ )
8
+
9
+ {-| Represents the query portion of a URL. You can use `toDict` or `toString` to turn it into basic types, or you can
10
+ parse it into a custom type using the other functions in this module.
11
+
12
+ @docs QueryParams
13
+
14
+
15
+ ## Parsing
16
+
17
+ @docs Parser
18
+
19
+ @docs andThen, fail, fromResult, fromString, optionalString, parse, string, strings, succeed
20
+
21
+
22
+ ## Combining
23
+
24
+ @docs map2, oneOf
25
+
26
+
27
+ ## Accessing as Built-In Types
28
+
29
+ @docs toDict, toString
30
+
31
+ -}
32
+
33
+ import Dict exposing (Dict)
34
+ import Url
35
+
36
+
37
+ {-| -}
38
+ type QueryParams
39
+ = QueryParams String
40
+
41
+
42
+ {-| -}
43
+ type Parser a
44
+ = Parser (Dict String (List String) -> Result String a)
45
+
46
+
47
+ {-| -}
48
+ succeed : a -> Parser a
49
+ succeed value =
50
+ Parser (\_ -> Ok value)
51
+
52
+
53
+ {-| -}
54
+ fail : String -> Parser a
55
+ fail errorMessage =
56
+ Parser (\_ -> Err errorMessage)
57
+
58
+
59
+ {-| -}
60
+ fromResult : Result String a -> Parser a
61
+ fromResult result =
62
+ Parser (\_ -> result)
63
+
64
+
65
+ {-| -}
66
+ andThen : (a -> Parser b) -> Parser a -> Parser b
67
+ andThen andThenFn (Parser parser) =
68
+ Parser
69
+ (\dict ->
70
+ case Result.map andThenFn (parser dict) of
71
+ Ok (Parser result) ->
72
+ result dict
73
+
74
+ Err error ->
75
+ Err error
76
+ )
77
+
78
+
79
+ {-| -}
80
+ oneOf : List (Parser a) -> Parser a
81
+ oneOf parsers =
82
+ Parser
83
+ (tryParser parsers)
84
+
85
+
86
+ {-| -}
87
+ tryParser : List (Parser a) -> Dict String (List String) -> Result String a
88
+ tryParser parsers dict =
89
+ case parsers of
90
+ [] ->
91
+ Err ""
92
+
93
+ (Parser nextParser) :: otherParsers ->
94
+ case nextParser dict of
95
+ Ok okValue ->
96
+ Ok okValue
97
+
98
+ Err _ ->
99
+ tryParser otherParsers dict
100
+
101
+
102
+ {-| -}
103
+ map2 : (a -> b -> combined) -> Parser a -> Parser b -> Parser combined
104
+ map2 func (Parser a) (Parser b) =
105
+ Parser <|
106
+ \dict ->
107
+ Result.map2 func (a dict) (b dict)
108
+
109
+
110
+ {-| -}
111
+ optionalString : String -> Parser (Maybe String)
112
+ optionalString key =
113
+ custom key
114
+ (\stringList ->
115
+ case stringList of
116
+ str :: _ ->
117
+ Ok (Just str)
118
+
119
+ _ ->
120
+ Ok Nothing
121
+ )
122
+
123
+
124
+ {-| -}
125
+ string : String -> Parser String
126
+ string key =
127
+ custom key
128
+ (\stringList ->
129
+ case stringList of
130
+ [ str ] ->
131
+ Ok str
132
+
133
+ _ ->
134
+ Err ("Missing key " ++ key)
135
+ )
136
+
137
+
138
+ {-| -}
139
+ custom : String -> (List String -> Result String a) -> Parser a
140
+ custom key customFn =
141
+ Parser <|
142
+ \dict ->
143
+ customFn (Maybe.withDefault [] (Dict.get key dict))
144
+
145
+
146
+ {-| -}
147
+ strings : String -> Parser (List String)
148
+ strings key =
149
+ custom key
150
+ (\stringList -> Ok stringList)
151
+
152
+
153
+ {-| -}
154
+ fromString : String -> QueryParams
155
+ fromString =
156
+ QueryParams
157
+
158
+
159
+ {-| -}
160
+ toString : QueryParams -> String
161
+ toString (QueryParams queryParams) =
162
+ queryParams
163
+
164
+
165
+ {-| -}
166
+ parse : Parser a -> QueryParams -> Result String a
167
+ parse (Parser queryParser) queryParams =
168
+ queryParams
169
+ |> toDict
170
+ |> queryParser
171
+
172
+
173
+ {-| -}
174
+ toDict : QueryParams -> Dict String (List String)
175
+ toDict (QueryParams queryParams) =
176
+ prepareQuery (Just queryParams)
177
+
178
+
179
+ prepareQuery : Maybe String -> Dict String (List String)
180
+ prepareQuery maybeQuery =
181
+ case maybeQuery of
182
+ Nothing ->
183
+ Dict.empty
184
+
185
+ Just qry ->
186
+ List.foldr addParam Dict.empty (String.split "&" qry)
187
+
188
+
189
+ addParam : String -> Dict String (List String) -> Dict String (List String)
190
+ addParam segment dict =
191
+ case String.split "=" segment of
192
+ [ rawKey, rawValue ] ->
193
+ case Url.percentDecode rawKey of
194
+ Nothing ->
195
+ dict
196
+
197
+ Just key ->
198
+ case Url.percentDecode rawValue of
199
+ Nothing ->
200
+ dict
201
+
202
+ Just value ->
203
+ Dict.update key (addToParametersHelp value) dict
204
+
205
+ _ ->
206
+ dict
207
+
208
+
209
+ addToParametersHelp : a -> Maybe (List a) -> Maybe (List a)
210
+ addToParametersHelp value maybeList =
211
+ case maybeList of
212
+ Nothing ->
213
+ Just [ value ]
214
+
215
+ Just list ->
216
+ Just (value :: list)