diff --git a/dormouse-uri.cabal b/dormouse-uri.cabal
--- a/dormouse-uri.cabal
+++ b/dormouse-uri.cabal
@@ -1,13 +1,11 @@
 cabal-version: 1.18
 
--- This file has been generated from package.yaml by hpack version 0.33.0.
+-- This file has been generated from package.yaml by hpack version 0.37.0.
 --
 -- see: https://github.com/sol/hpack
---
--- hash: b8a506a500d16ae04b32a32f96f93aa0b0c0993f219d0beed44057eda0b8f0c7
 
 name:           dormouse-uri
-version:        0.2.0.0
+version:        0.3.0.0
 synopsis:       Library for type-safe representations of Uri/Urls
 description:    Dormouse-Uri provides type safe handling of `Uri`s and `Url`s.
                 .
@@ -19,7 +17,7 @@
                 .
                   - The `Uri` and `Url` data types use `Data.Text` internally, this allows you to freely include percent-decoded characters which will be properly rendered when the `Url`/`Uri` is encoded.
                   - Quasiquoters to allow safe construction of `Uri`/`Url`s from string literals.
-                  - `DataKinds` allow `Url`s to be restricted to the `http` or `https` schemes are the type level.
+                  - `DataKinds` allow `Url`s to be restricted to the `http` or `https` schemes at the type level.
                   - A UrlBuilder syntax to allow type-safe construction/concatenation of `Url`s from their components, e.g. path and query parameters.
                 .
                 Please see https://dormouse.io for full documentation.
@@ -61,18 +59,22 @@
       Paths_dormouse_uri
   hs-source-dirs:
       src
-  default-extensions: OverloadedStrings MultiParamTypeClasses ScopedTypeVariables FlexibleContexts
+  default-extensions:
+      OverloadedStrings
+      MultiParamTypeClasses
+      ScopedTypeVariables
+      FlexibleContexts
   ghc-options: -Wall
   build-depends:
-      attoparsec >=0.13.2.4 && <0.14
+      attoparsec >=0.13.2.4 && <0.15
     , base >=4.7 && <5
-    , bytestring >=0.10.8 && <0.11.0
+    , bytestring >=0.10.8 && <0.12.0
     , case-insensitive >=1.2.1.0 && <2.0.0
     , containers >=0.6.2.1 && <0.7
     , http-types >=0.12.3 && <0.13
     , safe-exceptions >=0.1.7 && <0.2.0
     , template-haskell >=2.15.0 && <3.0.0
-    , text >=1.2.4 && <2.0.0
+    , text >=2.0.0 && <3.0.0
   default-language: Haskell2010
 
 test-suite dormouse-uri-test
@@ -100,22 +102,26 @@
   hs-source-dirs:
       src
       test
-  default-extensions: OverloadedStrings MultiParamTypeClasses ScopedTypeVariables FlexibleContexts
+  default-extensions:
+      OverloadedStrings
+      MultiParamTypeClasses
+      ScopedTypeVariables
+      FlexibleContexts
   ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -fdicts-strict
   build-depends:
-      attoparsec >=0.13.2.4 && <0.14
+      attoparsec >=0.13.2.4 && <0.15
     , base >=4.7 && <5
-    , bytestring >=0.10.8 && <0.11.0
+    , bytestring >=0.10.8 && <0.12.0
     , case-insensitive >=1.2.1.0 && <2.0.0
     , containers >=0.6.2.1 && <0.7
-    , hedgehog >=1.0.1 && <2
+    , hedgehog
     , hspec >=2.0.0 && <3
     , hspec-discover >=2.0.0 && <3
-    , hspec-hedgehog >=0.0.1.2 && <0.1
+    , hspec-hedgehog
     , http-types >=0.12.3 && <0.13
     , safe-exceptions >=0.1.7 && <0.2.0
     , scientific >=0.3.6.2 && <0.4
     , template-haskell >=2.15.0 && <3.0.0
-    , text >=1.2.4 && <2.0.0
-    , vector >=0.12.0.3 && <0.13
+    , text >=2.0.0 && <3.0.0
+    , vector
   default-language: Haskell2010
diff --git a/src/Dormouse/Uri/Parser.hs b/src/Dormouse/Uri/Parser.hs
--- a/src/Dormouse/Uri/Parser.hs
+++ b/src/Dormouse/Uri/Parser.hs
@@ -73,13 +73,13 @@
   xs <- takeWhileW8 (\x -> isUserInfoChar x || x == '%')
   xs' <- maybe (fail "Failed to percent-decode") pure $ percentDecode xs
   _ <- char '@'
-  return $ UserInfo (TE.decodeUtf8 xs')
+  return $ UserInfo (TE.decodeUtf8Lenient xs')
 
 pRegName :: Parser T.Text
 pRegName = do
   xs <- takeWhileW8 (\x -> isRegNameChar x || x == '%')
   xs' <- maybe (fail "Failed to percent-decode") pure $ percentDecode xs
-  return . TE.decodeUtf8 $ xs'
+  return . TE.decodeUtf8Lenient $ xs'
 
 pIPv4 :: Parser T.Text
 pIPv4 = do
@@ -124,7 +124,7 @@
 pPathAbsAuth = do
   p <- takeWhileW8 (\x -> isPathChar x || x == '%' || x == '/')
   p' <- maybe (fail "Failed to percent-decode") pure $ percentDecode p
-  let ps = PathSegment <$> T.split (== '/') (TE.decodeUtf8 p')
+  let ps = PathSegment <$> T.split (== '/') (TE.decodeUtf8Lenient p')
   case ps of -- begins with "/" is empty
     (PathSegment x):xs | T.null x -> return $ Path xs
     (PathSegment _):_             -> fail "must begin with /"
@@ -134,7 +134,7 @@
 pPathAbsNoAuth = do
   p <- takeWhileW8 (\x -> isPathChar x || x == '%' || x == '/')
   p' <- maybe (fail "Failed to percent-decode") pure $ percentDecode p
-  let ps = PathSegment <$> T.split (== '/') (TE.decodeUtf8 p')
+  let ps = PathSegment <$> T.split (== '/') (TE.decodeUtf8Lenient p')
   case ps of -- begins with "/" but not "//" OR begins with segment OR empty
     (PathSegment x1):(PathSegment x2):_ | T.null x1 && T.null x2 -> fail "cannot begin with //"
     (PathSegment x):xs                  | T.null x               -> return $ Path xs
@@ -144,7 +144,7 @@
 pPathRel = do
   p <- takeWhileW8 (\x -> isPathChar x || x == '%' || x == '/')
   p' <- maybe (fail "Failed to percent-decode") pure $ percentDecode p
-  let ps = PathSegment <$> T.split (== '/') (TE.decodeUtf8 p')
+  let ps = PathSegment <$> T.split (== '/') (TE.decodeUtf8Lenient p')
   case ps of
     (PathSegment x1):(PathSegment x2):_ | T.null x1 && T.null x2 -> fail "cannot begin with //"
     (PathSegment x):_                   | T.isPrefixOf ":" x     -> fail "first character of a relative path cannot be :"
@@ -159,7 +159,7 @@
     Nothing           -> return ()
     Just c | c == '#' -> return ()
     c                 -> fail $ "Invalid query termination character: " <> show c <> ", must be # or end of input"
-  return . Query . TE.decodeUtf8 $ queryText
+  return . Query . TE.decodeUtf8Lenient $ queryText
 
 pFragment :: Parser Fragment
 pFragment = do
@@ -168,14 +168,14 @@
   _ <- peekChar >>= \case
     Nothing           -> return ()
     c                 -> fail $ "Invalid fragment termination character: " <> show c <> ", must be end of input"
-  return . Fragment . TE.decodeUtf8 $ fragmentText
+  return . Fragment . TE.decodeUtf8Lenient $ fragmentText
 
 pScheme :: Parser Scheme
 pScheme = do
   x <- pAsciiAlpha
   xs <- A.takeWhile isSchemeChar
   _ <- char ':'
-  return $ Scheme (T.toLower . TE.decodeUtf8 $ B.cons (BS.c2w x) xs)
+  return $ Scheme (T.toLower . TE.decodeUtf8Lenient $ B.cons (BS.c2w x) xs)
 
 pAbsolutePart :: Parser (Scheme, Maybe Authority)
 pAbsolutePart = do
diff --git a/test/Dormouse/Generators/UriComponents.hs b/test/Dormouse/Generators/UriComponents.hs
--- a/test/Dormouse/Generators/UriComponents.hs
+++ b/test/Dormouse/Generators/UriComponents.hs
@@ -86,7 +86,7 @@
   valids <- Gen.list (Range.constant 0 15) genUserInfoChar
   fmap (B.intercalate "") $ Gen.shuffle $ invalids ++ valids
   where
-    genInvalidUserInfoChar = fmap (B8.pack . return) $ Gen.filter (\x -> (not $ isUserInfoChar x) && x /= '%' && C.isPrint x) Gen.ascii
+    genInvalidUserInfoChar = fmap (B8.pack . return) $ Gen.filter (\x -> (not $ isUserInfoChar x) && x /= '%' && x /= '@' && C.isPrint x) Gen.ascii
 
 genValidIPv4 :: Gen B.ByteString
 genValidIPv4 = do
