GithubHelp home page GithubHelp logo

modern-uri's Introduction

Modern URI

License BSD3 Hackage Stackage Nightly Stackage LTS CI

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 URI data type. The correctness is ensured by making sure that every sub-component of the URI record cannot be invalid.
  • Textual components in the URI data type are represented as Text rather than ByteString, because they are percent-decoded and so they can contain characters outside of ASCII range (i.e. Unicode). This allows for easier manipulation of URIs, 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 the URI is relative, otherwise it's absolute.
  • A Megaparsec parser that can be used as a standalone smart constructor for the URI data type (see mkURI) as well as seamlessly integrated into a bigger Megaparsec parser that consumes a strict Text (see parser) or strict ByteString (see parserBs).
  • The parser performs some normalization, for example it collapses consecutive slashes. Some smart constructors such as mkScheme and mkHost also perform normalization. So in a sense URIs are also “normalized by construction” to some extent.
  • Fast rendering to strict Text and ByteString as well as to their respective Builder types and to String/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 URI data type and refined text types (see Text.URI.QQ).

Quick start

The modern-uri package serves three main purposes:

  • Construction of the URI data type.
  • Inspection and manipulation of the URI data 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. Note that monads such as Maybe are also instances of the MonadThrow type class, and so the smart constructors can be used in pure environment 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 the 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 components.

Finally, the package provides two Megaparsec parsers: parser and parserBs. The first works on strict Text, while the other one works on strict ByteStrings. You can use the parsers in a bigger Megaparsec parser to parse URIs.

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:

  • render for rendering to strict Text.
  • render' for rendering to text Builder. It's possible to turn that into lazy Text by using the toLazyText function from Data.Text.Lazy.Builder.
  • renderBs for rendering to strict ByteString.
  • renderBs' for rendering to byte string Builder. Similarly it's possible to get a lazy ByteString from that by using the toLazyByteString function from Data.ByteString.Builder.
  • renderStr can be used to render to String. Sometimes it's handy. The render uses difference lists internally so it's not that slow, but in general I'd advise avoiding Strings.
  • renderStr' returns ShowS, which is just a synonym for String -> String—a function that prepends the result of rendering to a given String. This is useful when the URI you 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.

License

Copyright © 2017–present Mark Karpov

Distributed under BSD 3 clause license.

modern-uri's People

Contributors

ajchapman avatar chris-martin avatar csasarak avatar dependabot[bot] avatar duairc avatar etorreborre avatar mjepronk avatar mlang avatar mrkkrp avatar recursion-ninja avatar rikvantoor avatar spacekitteh avatar ysangkok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

modern-uri's Issues

Bug parsing numeric subdomains

This gives weird behaviour:

> mkURI "https://104.155.144.4.sslip.io:443/"

The host gets chopped and half of it along with the port end up in the uriPath:

URI {
  uriScheme = Just "https",
  uriAuthority = Right (Authority {authUserInfo = Nothing, authHost = "104.155.144.4", authPort = Nothing}),
  uriPath = Just (True,".sslip.io:443" :| []),
  uriQuery = [],
  uriFragment = Nothing
}

I think it may be true that according to some RFCs there shouldn't be numbers in subdomains, but in practice these types of domains exist and work fine everywhere.

In any case, when I roundtrip the current result we get back the following, which is different than the original:

> render uri
"https://104.155.144.4/.sslip.io%3a443/"

Btw the example url I am using is a real url, part of the sslip.io service: https://104.155.144.4.sslip.io:443/

Thank you

Trailing slashes for URLs with no path

Hey,

Thanks for maintaining URI!

Is it intentional that a URI representing http://example.com (e.g. when parsed with mkURI) is rendered as http://example.com/? I find the trailing slashes a little ugly when showing URLs to people.

Cheers

Add an `updateQueryParams` function

Heya, thanks for the great library. Working with it I need to "update" the value of an existing QueryParam from a URI, and while writing a function to do this, it surprised me that something like it didn't exist in the library.

I'm not sure if you think this would be a nice thing to add to this library, or perhaps this is easy to do somehow with lenses (I do not know lenses very well, so it might be and I just don't know).

Doesn't build with GHC-9.0

The modern-uri library doesn't build with ghc-9.0-rc1. When trying to build with the new compiler, the following compilation errors occur:

Text/URI/Types.hs:126:10: error:
    • Couldn't match type ‘m’ with ‘TH.Q’
      Expected: URI -> m TH.Exp
        Actual: URI -> TH.Q TH.Exp
      ‘m’ is a rigid type variable bound by
        the type signature for:
          TH.lift :: forall (m :: * -> *). TH.Quote m => URI -> m TH.Exp
        at Text/URI/Types.hs:126:3-6
    • In the expression: liftData
      In an equation for ‘TH.lift’: TH.lift = liftData
      In the instance declaration for ‘TH.Lift URI’
    • Relevant bindings include
        lift :: URI -> m TH.Exp (bound at Text/URI/Types.hs:126:3)
    |
126 |   lift = liftData
    |          ^^^^^^^^

Text/URI/Types.hs:129:15: error:
    • Couldn't match type ‘TH.TExp a0’ with ‘URI’
      Expected: URI -> TH.Code m URI
        Actual: URI -> TH.Code m (TH.TExp a0)
    • In the expression: TH.unsafeTExpCoerce . TH.lift
      In an equation for ‘TH.liftTyped’:
          TH.liftTyped = TH.unsafeTExpCoerce . TH.lift
      In the instance declaration for ‘TH.Lift URI’
    |
129 |   liftTyped = TH.unsafeTExpCoerce . TH.lift
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Text/URI/Types.hs:169:10: error:
    • Couldn't match type ‘m’ with ‘TH.Q’
      Expected: Authority -> m TH.Exp
        Actual: Authority -> TH.Q TH.Exp
      ‘m’ is a rigid type variable bound by
        the type signature for:
          TH.lift :: forall (m :: * -> *).
                     TH.Quote m =>
                     Authority -> m TH.Exp
        at Text/URI/Types.hs:169:3-6
    • In the expression: liftData
      In an equation for ‘TH.lift’: TH.lift = liftData
      In the instance declaration for ‘TH.Lift Authority’
    • Relevant bindings include
        lift :: Authority -> m TH.Exp (bound at Text/URI/Types.hs:169:3)
    |
169 |   lift = liftData
    |          ^^^^^^^^

Text/URI/Types.hs:172:15: error:
    • Couldn't match type ‘TH.TExp a1’ with ‘Authority’
      Expected: Authority -> TH.Code m Authority
        Actual: Authority -> TH.Code m (TH.TExp a1)
    • In the expression: TH.unsafeTExpCoerce . TH.lift
      In an equation for ‘TH.liftTyped’:
          TH.liftTyped = TH.unsafeTExpCoerce . TH.lift
      In the instance declaration for ‘TH.Lift Authority’
    |
172 |   liftTyped = TH.unsafeTExpCoerce . TH.lift
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Text/URI/Types.hs:195:10: error:
    • Couldn't match type ‘m’ with ‘TH.Q’
      Expected: UserInfo -> m TH.Exp
        Actual: UserInfo -> TH.Q TH.Exp
      ‘m’ is a rigid type variable bound by
        the type signature for:
          TH.lift :: forall (m :: * -> *). TH.Quote m => UserInfo -> m TH.Exp
        at Text/URI/Types.hs:195:3-6
    • In the expression: liftData
      In an equation for ‘TH.lift’: TH.lift = liftData
      In the instance declaration for ‘TH.Lift UserInfo’
    • Relevant bindings include
        lift :: UserInfo -> m TH.Exp (bound at Text/URI/Types.hs:195:3)
    |
195 |   lift = liftData
    |          ^^^^^^^^

Text/URI/Types.hs:198:15: error:
    • Couldn't match type ‘TH.TExp a2’ with ‘UserInfo’
      Expected: UserInfo -> TH.Code m UserInfo
        Actual: UserInfo -> TH.Code m (TH.TExp a2)
    • In the expression: TH.unsafeTExpCoerce . TH.lift
      In an equation for ‘TH.liftTyped’:
          TH.liftTyped = TH.unsafeTExpCoerce . TH.lift
      In the instance declaration for ‘TH.Lift UserInfo’
    |
198 |   liftTyped = TH.unsafeTExpCoerce . TH.lift
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Text/URI/Types.hs:221:10: error:
    • Couldn't match type ‘m’ with ‘TH.Q’
      Expected: QueryParam -> m TH.Exp
        Actual: QueryParam -> TH.Q TH.Exp
      ‘m’ is a rigid type variable bound by
        the type signature for:
          TH.lift :: forall (m :: * -> *).
                     TH.Quote m =>
                     QueryParam -> m TH.Exp
        at Text/URI/Types.hs:221:3-6
    • In the expression: liftData
      In an equation for ‘TH.lift’: TH.lift = liftData
      In the instance declaration for ‘TH.Lift QueryParam’
    • Relevant bindings include
        lift :: QueryParam -> m TH.Exp (bound at Text/URI/Types.hs:221:3)
    |
221 |   lift = liftData
    |          ^^^^^^^^

Text/URI/Types.hs:224:15: error:
    • Couldn't match type ‘TH.TExp a3’ with ‘QueryParam’
      Expected: QueryParam -> TH.Code m QueryParam
        Actual: QueryParam -> TH.Code m (TH.TExp a3)
    • In the expression: TH.unsafeTExpCoerce . TH.lift
      In an equation for ‘TH.liftTyped’:
          TH.liftTyped = TH.unsafeTExpCoerce . TH.lift
      In the instance declaration for ‘TH.Lift QueryParam’
    |
224 |   liftTyped = TH.unsafeTExpCoerce . TH.lift
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Text/URI/Types.hs:267:10: error:
    • Couldn't match type ‘m’ with ‘TH.Q’
      Expected: RText l -> m TH.Exp
        Actual: RText l -> TH.Q TH.Exp
      ‘m’ is a rigid type variable bound by
        the type signature for:
          TH.lift :: forall (m :: * -> *). TH.Quote m => RText l -> m TH.Exp
        at Text/URI/Types.hs:267:3-6
    • In the expression: liftData
      In an equation for ‘TH.lift’: TH.lift = liftData
      In the instance declaration for ‘TH.Lift (RText l)’
    • Relevant bindings include
        lift :: RText l -> m TH.Exp (bound at Text/URI/Types.hs:267:3)
    |
267 |   lift = liftData
    |          ^^^^^^^^

Text/URI/Types.hs:270:15: error:
    • Couldn't match type: TH.TExp a4
                     with: RText l
      Expected: RText l -> TH.Code m (RText l)
        Actual: RText l -> TH.Code m (TH.TExp a4)
    • In the expression: TH.unsafeTExpCoerce . TH.lift
      In an equation for ‘TH.liftTyped’:
          TH.liftTyped = TH.unsafeTExpCoerce . TH.lift
      In the instance declaration for ‘TH.Lift (RText l)’
    • Relevant bindings include
        liftTyped :: RText l -> TH.Code m (RText l)
          (bound at Text/URI/Types.hs:270:3)
    |
270 |   liftTyped = TH.unsafeTExpCoerce . TH.lift
    |

Empty path with a trailing slash

I don't see a way to represent an URLs with the trailing slash only in the path, e.g. https://example.com/. Parsing these URLs and rendering them gives different results, so the following 2 tests fail:

    it "1" $
      render [QQ.uri|https://example.com/|] `shouldBe` "https://example.com/"
    it "2" $
      let given = "https://example.com/"
       in (render <$> mkURI given) `shouldBe` Just given

If uriPath is Nothing I can't specify whether I want a trailing slash or not. Otherwise, I can't give an empty path since path pieces are a NonEmpty list.

Hashable instance

In your next release, it would be great if you could include a Hashable instance for URI :)

Colons in path are escaped

Version: modern-uri-0.3.4.4 (also occurs on older)

Colons in path pieces are percent-encoded, while it seems to me from https://www.rfc-editor.org/rfc/rfc3986#section-3.3 that they can appear unencoded from the second path piece on.

[nix-shell:~/]$ ghci
GHCi, version 9.0.2: https://www.haskell.org/ghc/  :? for help
ghci> import Data.Text
ghci> import Text.URI
ghci> Right u = mkURI (pack "https://mybusinessbusinessinformation.googleapis.com/v1/categories:batchGet")
ghci> render u
"https://mybusinessbusinessinformation.googleapis.com/v1/categories%3abatchGet"

This gives issues with f.e. Google, which uses colons in paths but does not accept the percent-encoded variant.

How to concatenate parts of URI?

I wonder if there some concatenating operators for parts of complex URI? Similar to </> from filepath library. For example, I want to have:

  1. https://markkarpov.com
  2. https://markkarpov.com/foo
  3. https://markkarpov.com/bar
  4. https://markkarpov.com/bar/baz

Sure, I can write four hardcoded quasi quotes. But I wonder what is preferred and simplest way to do this reusing first URI?

I found mkPathPiece smart constructor but have no idea how to reuse it...

Support for "data:" URI scheme?

I realised that round tripping a data uri breaks it. E.g.

import Text.URI

let uri = "data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
in render <$> mkURI uri /= (pure uri) :: Maybe URI 

I assume the data-scheme is not RFCs compliant - so it's probably not a bug.

Nevertheless I wanted to let you know, since it took me half a day to figure out why some of these uri's are broken. Maybe it is possible to have them round-trip correctly (e.g. by allowing empty path elements) or at least fail completely? Silently dropping slashes seems to me like unintended behaviour.

`mkURI` fails to correctly parse uri when hostname contains a "_"

Is this the expected behavior?

image

I might be missing something, but the hostname of that uri is auth_service. mkURI seems to be splitting it at the underscore for some reason.

Here is a screenshot showing it correctly handles the uri when I replace auth_service with auth-service:

image

Add tutorial or at least some examples

For person who never worked previously with uri or uri-bytestring or Haskell beginner it's not very easy to understand how to use modern-uri package and why one should do it.

Expose parsers of certain classes of URIs

e.g. for absolute URIs only, etc. One can always validate after the fact, but that is potentially less efficient. Might be easier to add a Parser or Internal module exposing all the bits for people to make their own sub parsers.

CC @luigy

File URIs are not parsed

I wasn't sure if file:///... is actually a valid URI so checked the RFC and it gives this example: file:///etc/hosts (section 1.1). However modern-uri can't parse this:

Prelude Text.URI> mkURI "file:///etc/hosts"
*** Exception: ParseException "file:///etc/hosts" (TrivialError (SourcePos {sourceName = "", sourceLine = Pos 1, sourceColumn = Pos 8} :| []) (Just (Tokens ('/' :| ""))) (fromList [Tokens ('%' :| ""),Tokens ('[' :| ""),Label ('A' :| "SCII alpha-numeric character"),Label ('i' :| "nteger"),Label ('u' :| "sername")]))

For comparison, network-uri parses this:

Prelude Network.URI> parseURI "file:///etc/hosts"
Just file:///etc/hosts

appending paths to urls

One thing I am having a hard time seeing is how does one add a relative path to a uri.

For me it would be quite helpful to have a simple combinator like:

url +/+ dir (ie 'https://example.org/pub' +/+ 'some/subdir' == 'https://example.org/pub/some/subdir')

I dunno if the lens module makes this easier - but I generally try to avoid them.

Actually when I use relativeTo it replaces the path from the base URI with that of the relative URI path.
Is there any smarter way to handle such changes?

Allow `[` and `]` in the query component

This is a follow up to mrkkrp/req#102. I am going to try to convince you that modern-uri implements RFC 3986 too naively.

I will base my case on the two main arguments:

  1. The specification is ambiguous (or, at least, it is unclear for me personally how to interpret it).
  2. There is practice and it differs from what is currently implemented.

The specification

The Reserved Characters section lists [ and ] as gen-delims, which is part of reserved. Then it says:

URI producing applications should percent-encode data octets that
correspond to characters in the reserved set unless these characters
are specifically allowed by the URI scheme to represent data in that
component.

This specification does not explicitly state that it is using RFC 2119 keywords, so it is not entirely clear to me how to interpret this “should”, but, I believe, the best option we have is to interpret it as per RFC 2119, and thus web-apps not escaping square brackets are likely in compliance. This alone should be enough to allow them.

It then goes on:

If a reserved character is found in a URI component and
no delimiting role is known for that character, then it must be
interpreted as representing the data octet corresponding to that
character's encoding in US-ASCII.

Now this “no delimiting role is known” part is pretty mysterious. The paragraph right above the one I have been quoting seems to be an attempt at explaining what this means, but I am having a hard time understanding it.

If we go now to the Query section, it reads:

The query component is indicated by the first question
mark ("?") character and terminated by a number sign ("#") character
or by the end of the URI.

which suggest that anything that is not a # is not a delimiter or, at least, the delimiting role of [ and ] is unknown (to me 🙂). This part of text may be non-normative and it contradicts the ABNF right below it, but the specification never explains what is normative and what is informative. The intent seems to be to disallow the square brackets, but it is not that obvious, in my opinion.

Given how ambiguous all this is, I am not convinced that implementing exactly the ABNFs from the specification is enough.

The real world

The discussion in the req issue was based around the handling of square brackets in real-world browsers. A couple of years ago there was a discussion about this in the Firefox issue tracker and there is a fantastic survey of behaviours of various browsers.

They surveyed specifically what the browsers were sending to the server, while the discussion on req was about “my browser converts” – I am not sure what exactly you were testing. Note that the actual URL location in that issue was already encoded (likely, by GitHub’s markdown processor): the HTML code of the link was <a href="https://gitlab.com/morley-framework/morley/-/issues/new?issue%5Btitle%5D=Indigo%20website:%20" rel="nofollow">https://gitlab.com/morley-framework/morley/-/issues/new?issue[title]=Indigo%20website:%20</a>. And even when I follow this encoded link in Firefox, it decodes the brackets. This is a result of that survey I linked – as you can see, Firefox was the only browser forcefully encoding square brackets, so this was considered a bug and was changed.

I can’t make a hyperlink with unencoded brackets here on GitHub, but if you paste https://gitlab.com/morley-framework/morley/-/issues/new?issue[title]=Indigo%20website:%20 into the address bar of your Google Chrome it will (I hope – at least that is what I see testing in Chromium) not encode the brackets and send them as is.

Thus, we can see that, regardless of what the specification has to say, in practice the square brackets are thought to be allowed in the query component and not allowing them was fixed as a bug in a major web-browser.

Port numbers validation

I don't see any limitations on port numbers in https://tools.ietf.org/html/rfc3986 itself, but I'd expect parsing to fail when port numbers are >= 2^16, at least for the schemes that declare ports to be 16-bit numbers. Is this a bug?

> mkURI "http://localhost:65535"
URI {uriScheme = Just "http", uriAuthority = Right (Authority {authUserInfo = Nothing, authHost = "localhost", authPort = Just 65535}), uriPath = Nothing, uriQuery = [], uriFragment = Nothing}

> mkURI "http://localhost:65536"
URI {uriScheme = Just "http", uriAuthority = Right (Authority {authUserInfo = Nothing, authHost = "localhost", authPort = Just 65536}), uriPath = Nothing, uriQuery = [], uriFragment = Nothing}

> mkURI "http://localhost:65536000"
URI {uriScheme = Just "http", uriAuthority = Right (Authority {authUserInfo = Nothing, authHost = "localhost", authPort = Just 65536000}), uriPath = Nothing, uriQuery = [], uriFragment = Nothing}

Use newtypes instead of `RText`

Hi, would you accept a patch that replaces the RText machinery with a bunch of newtypes? i.e.

newtype Scheme = Scheme { unScheme :: Text }
newtype Host = Host { unHost :: Text }
... etc ...

I think it would make the library a bit friendlier to beginners :)

relativeTo and quasi-quotation

In the current implementation, relativeTo returns Nothing if the base URI doesn't have a scheme. If the base URI is provided at compile time, I can know that it has a scheme, but there's no way for relativeTo to know. That leaves me in an awkward situation with a Maybe URI that I "know" will contain a value, but unwrapping it with a partial function is brittle, because either the base URI or the implementation of relativeTo could change in the future. Would it be possible to generate a function for a base URI provided at compile time, so that it always succeeds at runtime (no Maybe)? Something like this:

relativeToTest :: URI -> URI
relativeToTest = [relativeToUri|http://test.com/|]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.