modern-uri
Modern library for working with URIs
https://github.com/mrkkrp/modern-uri
| Version on this page: | 0.2.2.0 |
| LTS Haskell 24.16: | 0.3.6.1@rev:3 |
| Stackage Nightly 2025-10-25: | 0.3.6.1@rev:3 |
| Latest on Hackage: | 0.3.6.1@rev:3 |
modern-uri-0.2.2.0@sha256:acb2aef8bf1bfc4beaee82a276cdbe638d4b0904df32fd37e96157d89aaa4abd,4515Module documentation for 0.2.2.0
Modern URI
This is a modern library for working with URIs in Haskell as per RFC 3986:
https://tools.ietf.org/html/rfc3986
Features
The modern-uri package features:
- Correct by construction
URIdata type. Correctness is ensured by guaranteeing that every sub-component of theURIrecord is by itself cannot be invalid. This boils down to careful use of types and a set of smart constructors for things like scheme, host, etc. - Textual components in the
URIdata type represented asTextrather thanByteString, because they are percent-decoded and so they can contain characters outside of ASCII range (i.e. Unicode). This allows for easier manipulation ofURIs, while encoding and decoding headaches are handled by the parsers and renders for you. - Absolute and relative URIs differ only by the scheme component: if it’s
Nothing, then URI is relative, otherwise it’s absolute. - Megaparsec parser that can be used as a standalone smart constructor for
the
URIdata type (seemkURI) as well as be seamlessly integrated into a bigger Megaparsec parser that consumes strictText(seeparser) or strictByteString(seeparserBs). - The parser performs some normalization, for example it collapses
consecutive slashes. Some smart constructors such as
mkSchemeandmkHostalso perform normalization. So in a sense URIs are also “normalized by construction” to some extent. - Fast rendering to strict
TextandByteStringas well as to their respectiveBuildertypes and toString/ShowS. - Extensive set of lensy helpers for easier manipulation of the nested data
types (see
Text.URI.Lens). - Quasi-quoters for compile-time construction of the
URIdata type and refined text types (seeText.URI.QQ).
Quick start
The modern-uri package serves three main purposes:
- Construction of the
URIdata type. - Inspection and manipulation of the
URIdata type (in the sense of changing its parts). - Rendering of
URIs.
Let’s walk through every operation quickly.
Construction of URIs
There are four ways to create a URI value. First off, one could assemble
it manually like so:
λ> :set -XOverloadedStrings
λ> import qualified Text.URI as URI
λ> scheme <- URI.mkScheme "https"
λ> scheme
"https"
λ> host <- URI.mkHost "markkarpov.com"
λ> host
"markkarpov.com"
λ> let uri = URI.URI (Just scheme) (Right (URI.Authority Nothing host Nothing)) Nothing [] Nothing
λ> uri
URI
{ uriScheme = Just "https"
, uriAuthority = Right
(Authority
{ authUserInfo = Nothing
, authHost = "markkarpov.com"
, authPort = Nothing })
, uriPath = Nothing
, uriQuery = []
, uriFragment = Nothing }
In this library we use quite a few refined text values. They only can be
constructed by using smart constructors like mkScheme :: MonadThrow m => Text -> m (RText 'Scheme). For example, if argument to mkScheme is not a
valid scheme, an exception will be thrown. Actually this is not necessarily
so because there are pure monads that are instances of the MonadThrow type
class, and so the smart constructors may be used in e.g. the Maybe monad
as well.
There is a smart constructor that can make an entire URI too, it’s called
(unsurprisingly) mkURI:
λ> uri <- URI.mkURI "https://markkarpov.com"
λ> uri
URI
{ uriScheme = Just "https"
, uriAuthority = Right
(Authority
{ authUserInfo = Nothing
, authHost = "markkarpov.com"
, authPort = Nothing })
, uriPath = Nothing
, uriQuery = []
, uriFragment = Nothing }
If argument of mkURI is not a valid URI, then an exception will be thrown.
The exception will contain full context and the actual parse error.
If some refined text value or URI is known statically at compile time, we
can use Template Haskell, namely the “quasi quotes” feature. To do so import
the Text.URI.QQ module and enable the QuasiQuotes language extension,
like so:
λ> :set -XQuasiQuotes
λ> import qualified Text.URI.QQ as QQ
λ> let uri = [QQ.uri|https://markkarpov.com|]
λ> uri
URI
{ uriScheme = Just "https"
, uriAuthority = Right
(Authority
{ authUserInfo = Nothing
, authHost = "markkarpov.com"
, authPort = Nothing })
, uriPath = Nothing
, uriQuery = []
, uriFragment = Nothing }
Note how the value returned by the url quasi quote is pure, its
construction cannot fail because when there is an invalid URI inside the
quote it’s a compilation error.
The Text.URI.QQ module has quasi quoters for scheme, host, and other
things, check it out.
Finally the package provides two Megaparsec parsers: parser and
parserBs. The first works on strict Text, while other one works on
strict ByteStrings. You can use the parsers in a bigger Megaparsec parser
to parse URIs. To get started with Megaparsec, see its Hackage
page.
Inspection and manipulation
Although one could use record syntax directly, possibly with language
extensions like RecordWildcards, the best way to inspect and edit parts of
URI is with lenses. The lenses can be found in the Text.URI.Lens module.
If you have never used the
lens library, you could
probably start by reading/watching materials suggested in the library
description on Hackage.
Here are some examples, just to show off what you can do:
λ> import Text.URI.Lens
λ> uri <- URI.mkURI "https://example.com/some/path?foo=bar&baz=quux&foo=foo"
λ> uri ^. uriScheme
Just "https"
λ> uri ^? uriAuthority . _Right . authHost
Just "example.com"
λ> uri ^. isPathAbsolute
True
λ> uri ^. uriPath
["some","path"]
λ> k <- URI.mkQueryKey "foo"
λ> uri ^.. uriQuery . queryParam k
["bar","foo"]
-- etc.
Rendering
Rendering turns a URI into a sequence of bytes or characters. Currently
the following options are available:
renderfor rendering to strictText.render'for rendering to textBuilder. It’s possible to turn that into lazyTextby using thetoLazyTextfunction fromData.Text.Lazy.Builder.renderBsfor rendering to strictByteString.renderBs'for rendering to byte stringBuilder. Similarly it’s possible to get a lazyByteStringfrom that by using thetoLazyByteStringfunction fromData.ByteString.Builder.renderStrcan be used to render toString. Sometimes it’s handy. The render uses difference lists internally so it’s not that slow, but in general I’d advise avoidingStrings.renderStr'returnsShowS, which is just a synonym forString -> String—a function that prepends the result of rendering to a givenString. This is useful when theURIyou want to render is a part of a bigger output, just like with the builders mentioned above.
Examples:
λ> uri <- mkURI "https://markkarpov.com/posts.html"
λ> render uri
"https://markkarpov.com/posts.html"
λ> renderBs uri
"https://markkarpov.com/posts.html"
λ> renderStr uri
"https://markkarpov.com/posts.html"
-- etc.
Contribution
Issues, bugs, and questions may be reported in the GitHub issue tracker for this project.
Pull requests are also welcome and will be reviewed quickly.
License
Copyright © 2017–2018 Mark Karpov
Distributed under BSD 3 clause license.
Changes
Modern URI 0.2.2.0
-
Removed a potentially overlapping instance
Arbitrary (NonEmpty (RText 'PathPiece)). -
Fixed a bug that made it impossible to have empty host names. This allows us to parse URIs like
file:///etc/hosts.
Modern URI 0.2.1.0
- Added
emptyURI—URIvalue representing the empty URI.
Modern URI 0.2.0.0
-
Changed the type of
uriPathfield of theURIrecord from[RText 'PathPiece]toMaybe (Bool, NonEmpty (RText 'PathPiece)). This allows us to store whether there is a trailing slash in the path or not. See the updated documentation for more information. -
Added the
relativeTofunction. -
Added the
uriTrailingSlash0-1 traversal inText.URI.Lens.
Modern URI 0.1.2.1
- Allow Megaparsec 6.4.0.
Modern URI 0.1.2.0
- Fixed handling of
+in query strings. Now+is parsed as space and serialized as%2bas per RFC 1866 (paragraph 8.2.1). White space in query parameters is serialized as+.
Modern URI 0.1.1.1
- Fixed implementation of
Text.URI.Lens.queryParamtraversal.
Modern URI 0.1.1.0
-
Derived
NFDataforParseException. -
Adjusted percent-encoding in renders so it’s only used when absolutely necessary. Previously we percent-escaped a bit too much, which, strictly speaking, did not make the renders incorrect, but that didn’t look nice either.
Modern URI 0.1.0.1
- Updated the readme to include “Quick start” instructions and some examples.
Modern URI 0.1.0.0
-
Changed the type of
uriAuthorityfromMaybe AuthoritytoEither Bool Authority. This allows to know if URI path is absolute or not without duplication of information, i.e. when theAuthoritycomponent is present the path is necessarily absolute, otherwise theBoolvalue tells if it’s absolute (True) or relative (False). -
Added
isPathAbsoluteinText.URIand the corresponding getter inText.URI.Lens.
Modern URI 0.0.2.0
-
Added the
renderStrandrenderStr'functions for efficient rendering toStringandShowS. -
Added the
parserBsthat can consume strictByteStringstreams.
Modern URI 0.0.1.0
- Initial release.