Skip to content

Commit 9436bbd

Browse files
authored
Merge pull request #55 from slamdata/nes-match
Add matcher for `NonEmptyString`
2 parents 97b638d + a4fb706 commit 9436bbd

File tree

3 files changed

+105
-57
lines changed

3 files changed

+105
-57
lines changed

bower.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
},
4444
"devDependencies": {
4545
"purescript-console": "^3.0.0",
46-
"purescript-assert": "^3.0.0",
47-
"purescript-record": "^0.2.6"
46+
"purescript-assert": "^3.1.0",
47+
"purescript-record": "^0.2.6",
48+
"purescript-generics-rep": "^5.4.0"
4849
}
4950
}

src/Routing/Match.purs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
1-
module Routing.Match where
1+
module Routing.Match
2+
( module Routing.Match
3+
, module Routing.Match.Class
4+
) where
25

36
import Prelude
47

58
import Control.Alt (class Alt, (<|>))
69
import Control.Alternative (class Alternative)
710
import Control.Plus (class Plus)
8-
911
import Data.Either (Either(..))
1012
import Data.Foldable (foldl)
1113
import Data.Int (fromString)
1214
import Data.List (List(..), reverse)
1315
import Data.Map as M
14-
import Data.Maybe (Maybe(..))
16+
import Data.Maybe (Maybe(..), maybe)
1517
import Data.Newtype (class Newtype, unwrap)
1618
import Data.Semiring.Free (Free, free)
19+
import Data.String.NonEmpty (NonEmptyString)
20+
import Data.String.NonEmpty as NES
1721
import Data.Tuple (Tuple(..), snd)
1822
import Data.Validation.Semiring (V, invalid, unV)
19-
2023
import Global (readFloat, isNaN)
21-
22-
import Routing.Match.Class (class MatchClass)
24+
import Routing.Match.Class (class MatchClass, bool, end, fail, int, lit, num, param, params, root, str)
2325
import Routing.Match.Error (MatchError(..), showMatchError)
2426
import Routing.Types (Route, RoutePart(..))
2527

@@ -125,6 +127,11 @@ instance matchApply :: Apply Match where
125127
instance matchApplicative :: Applicative Match where
126128
pure a = Match \r -> pure $ Tuple r a
127129

130+
-- | Matches a non-empty string.
131+
nonempty :: Match NonEmptyString
132+
nonempty =
133+
eitherMatch $ maybe (Left "Empty string") Right <<< NES.fromString <$> str
134+
128135
-- | Matches list of matchers. Useful when argument can easy fail (not `str`)
129136
-- | returns `Match Nil` if no matches
130137
list :: forall a. Match a -> Match (List a)
@@ -137,9 +144,6 @@ list (Match r2a) =
137144
(\(Tuple rs a) -> go (Cons a accum) rs)
138145
(r2a r)
139146

140-
141-
142-
143147
-- It groups `Free MatchError` -> [[MatchError]] -map with showMatchError ->
144148
-- [[String]] -fold with semicolon-> [String] -fold with newline-> String
145149
runMatch :: forall a. Match a -> Route -> Either String a

test/Test/Main.purs

Lines changed: 89 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,111 @@ module Test.Main where
22

33
import Prelude
44

5-
import Control.Alt ((<|>))
65
import Control.Monad.Eff (Eff)
7-
import Control.Monad.Eff.Console (CONSOLE, log)
6+
import Control.Monad.Eff.Console (CONSOLE)
7+
import Data.Bifunctor (lmap)
88
import Data.Either (Either(..))
9+
import Data.Foldable (oneOf)
10+
import Data.Generic.Rep (class Generic)
11+
import Data.Generic.Rep.Show (genericShow)
912
import Data.List (List)
1013
import Data.List as L
1114
import Data.Map as M
15+
import Data.String.NonEmpty (NonEmptyString)
16+
import Data.String.NonEmpty as NES
1217
import Data.Tuple (Tuple(..))
18+
import Partial.Unsafe (unsafePartial)
1319
import Routing (match)
14-
import Routing.Match (Match, list)
15-
import Routing.Match.Class (bool, end, int, lit, num, str, param, params)
16-
import Test.Assert (ASSERT, assert')
20+
import Routing.Match (Match, bool, end, int, list, lit, nonempty, num, param, params, str)
21+
import Test.Assert (ASSERT, assertEqual)
1722

18-
data FooBar
23+
data MyRoutes
1924
= Foo Number (M.Map String String)
2025
| Bar Boolean String
2126
| Baz (List Number)
2227
| Quux Int
2328
| Corge String
29+
| Corge' NonEmptyString
2430
| End Int
2531

26-
derive instance eqFooBar :: Eq FooBar
27-
28-
instance showFooBar :: Show FooBar where
29-
show (Foo num q) = "(Foo " <> show num <> " " <> show q <> ")"
30-
show (Bar bool str) = "(Bar " <> show bool <> " " <> show str <> ")"
31-
show (Baz lst) = "(Baz " <> show lst <> ")"
32-
show (Quux i) = "(Quux " <> show i <> ")"
33-
show (Corge str) = "(Corge " <> show str <> ")"
34-
show (End i) = "(End " <> show i <> ")"
35-
36-
routing :: Match FooBar
37-
routing =
38-
Foo <$> (lit "foo" *> num) <*> params
39-
<|> Bar <$> (lit "bar" *> bool) <*> (param "baz")
40-
<|> Quux <$> (lit "" *> lit "quux" *> int)
41-
<|> Corge <$> (lit "corge" *> str)
42-
-- Order matters here. `list` is greedy, and `end` wont match after it
43-
<|> End <$> (lit "" *> int <* end)
44-
<|> Baz <$> (list num)
32+
derive instance eqMyRoutes :: Eq MyRoutes
33+
derive instance genericMyRoutes :: Generic MyRoutes _
34+
instance showMyRoutes :: Show MyRoutes where show = genericShow
4535

36+
routing :: Match MyRoutes
37+
routing = oneOf
38+
[ Foo <$> (lit "foo" *> num) <*> params
39+
, Bar <$> (lit "bar" *> bool) <*> (param "baz")
40+
, Baz <$> (lit "list" *> list num)
41+
, Quux <$> (lit "" *> lit "quux" *> int)
42+
, Corge <$> (lit "corge" *> str)
43+
, Corge' <$> (lit "corge'" *> nonempty)
44+
, End <$> (lit "" *> int <* end)
45+
]
4646

4747
main :: Eff (assert :: ASSERT, console :: CONSOLE) Unit
4848
main = do
49-
assertEq (match routing "foo/12/?welp='hi'&b=false") (Right (Foo 12.0 (M.fromFoldable [Tuple "welp" "'hi'", Tuple "b" "false"])))
50-
assertEq (match routing "foo/12?welp='hi'&b=false") (Right (Foo 12.0 (M.fromFoldable [Tuple "welp" "'hi'", Tuple "b" "false"])))
51-
assertEq (match routing "bar/true?baz=test") (Right (Bar true "test"))
52-
assertEq (match routing "bar/false?baz=%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%84%D0%B0%D0%B9%D0%BB") (Right (Bar false "временный файл"))
53-
assertEq (match routing "corge/test") (Right (Corge "test"))
54-
assertEq (match routing "corge/%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%84%D0%B0%D0%B9%D0%BB") (Right (Corge "временный файл"))
55-
assertEq (match routing "/quux/42") (Right (Quux 42))
56-
assertEq (match routing "123/") (Right (Baz (L.fromFoldable [123.0])))
57-
assertEq (match routing "/1") (Right (End 1))
58-
assertEq (match routing "foo/0/?test=a/b/c") (Right (Foo 0.0 (M.fromFoldable [Tuple "test" "a/b/c"])))
59-
60-
assertEq
61-
:: forall a eff
62-
. Eq a
63-
=> Show a
64-
=> a
65-
-> a
66-
-> Eff (assert :: ASSERT, console :: CONSOLE | eff) Unit
67-
assertEq actual expected
68-
| actual /= expected = assert' ("Equality assertion failed\n\nActual: " <> show actual <> "\n\nExpected: " <> show expected) false
69-
| otherwise = log ("Equality assertion passed for " <> show actual)
49+
assertEqual
50+
{ actual: match routing "foo/12/?welp='hi'&b=false"
51+
, expected: Right (Foo 12.0 (M.fromFoldable [Tuple "welp" "'hi'", Tuple "b" "false"]))
52+
}
53+
assertEqual
54+
{ actual: match routing "foo/12?welp='hi'&b=false"
55+
, expected: Right (Foo 12.0 (M.fromFoldable [Tuple "welp" "'hi'", Tuple "b" "false"]))
56+
}
57+
assertEqual
58+
{ actual: match routing "bar/true?baz=test"
59+
, expected: Right (Bar true "test")
60+
}
61+
assertEqual
62+
{ actual: match routing "bar/false?baz=%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%84%D0%B0%D0%B9%D0%BB"
63+
, expected: Right (Bar false "временный файл")
64+
}
65+
assertEqual
66+
{ actual: match routing "corge/test"
67+
, expected: Right (Corge "test")
68+
}
69+
assertEqual
70+
{ actual: match routing "corge/%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%84%D0%B0%D0%B9%D0%BB"
71+
, expected: Right (Corge "временный файл")
72+
}
73+
assertEqual
74+
{ actual: match routing "corge'/test"
75+
, expected: Right (Corge' (unsafePartial NES.unsafeFromString "test"))
76+
}
77+
assertEqual
78+
{ actual: match routing "corge'/%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9%20%D1%84%D0%B0%D0%B9%D0%BB"
79+
, expected: Right (Corge' (unsafePartial NES.unsafeFromString "временный файл"))
80+
}
81+
assertEqual
82+
{ actual: lmap (const unit) (match routing "corge'/")
83+
, expected: Left unit
84+
}
85+
assertEqual
86+
{ actual: match routing "/quux/42"
87+
, expected: Right (Quux 42)
88+
}
89+
assertEqual
90+
{ actual: match routing "list/123/"
91+
, expected: Right (Baz (L.fromFoldable [123.0]))
92+
}
93+
assertEqual
94+
{ actual: match routing "list/123/456"
95+
, expected: Right (Baz (L.fromFoldable [123.0, 456.0]))
96+
}
97+
assertEqual
98+
{ actual: match routing "list/"
99+
, expected: Right (Baz (L.fromFoldable []))
100+
}
101+
assertEqual
102+
{ actual: match routing "list"
103+
, expected: Right (Baz (L.fromFoldable []))
104+
}
105+
assertEqual
106+
{ actual: match routing "/1"
107+
, expected: Right (End 1)
108+
}
109+
assertEqual
110+
{ actual: match routing "foo/0/?test=a/b/c"
111+
, expected: Right (Foo 0.0 (M.fromFoldable [Tuple "test" "a/b/c"]))
112+
}

0 commit comments

Comments
 (0)